Android HTTP 请求在模拟器中有效但在 Wear 设备上无效

Android HTTP Requests Working In Simulator But Not On Wear Device

我正在制作一个简单的 Android Wear 应用程序来控制我的恒温器,我正在向 Volley 发送 POST 请求来控制它们。 Android Wear 模拟器中的一切都很好(请求有效),但是,当应用程序确实加载到我的 Moto 360 上时,截击请求被调用但总是超时。

为什么我的截击请求在我的手表上会失败但在模拟器上却可以?其他应用在我的手表上请求成功(例如,内置的天气应用可以在大约 3 秒内加载天气数据)。而且,最奇怪的部分:我的手表上有应用程序 working(成功发出截击请求),大约一天后,我从 Android Studio 将它安装到我的手表上, 它突然无故停止加载数据。

到目前为止我尝试过的:

这是我的代码(来自我的主视图的适配器):

public void refreshThermostatsRecyclerView(RequestQueue queue) {
    String url = "https://mobile.skyport.io:9090/login"; // login call to the thermostats server Skyport
     Log.w("myApp", "Starting /login call to Skyport"); // this gets called on simulator and watch
     // Request a string response from the provided URL.
     StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
 Response.Listener<String>() {
       @Override
       public void onResponse(String response) {
           // Display the response string.
           Log.w("myApp", "Response is: " + response); // this gets called on the simulator but not the watch
           try {
               // there's some code to parse the data.
           } catch (JSONException e) {
                Log.w("myApp", "catching an error parsing the json."); // never gets called.
                e.printStackTrace();
            }
            }
      }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.w("myApp", "Skyport request didn't work! " + error);  // this always gets called on the watch, with the error being a timeout error (com.Android.Volley.timeouterror) but never gets called in the simulator
            }
        }) {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> m = new HashMap<>();
                m.put("Referer", "app:/VenstarCloud.swf");
                // here I put some more headers
                return m;
            }

            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String, String> m = new HashMap<>();
                m.put("version", "3.0.5");
                m.put("email", userEmail);
                m.put("password", userToken);
                return m;
            }
        };
        // Add the request to the RequestQueue.
        int socketTimeout1 = 30000; // times out 30 seconds after the request starts on the watch
        RetryPolicy policy1 = new DefaultRetryPolicy(socketTimeout1, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
        stringRequest.setRetryPolicy(policy1);
        queue.add(stringRequest);
    }

这是从我的 Main Activity 中的 onCreate() 方法调用的,代码如下:

RequestQueue queue = Volley.newRequestQueue(this);
refreshThermostatsRecyclerView(queue);

如果您想在模拟器和手表上查看由 运行 创建的日志,它们是 on Google Drive here


编辑 1:我的手表重新启动暂时解决了这个问题并允许手表再次发出 HTTP 请求,但是一旦手表与蓝牙断开连接,连接到WiFi,断开与 WiFi 的连接,然后重新连接到蓝牙(所以每次我在没有 phone 然后 return 的情况下穿过我的公寓时它都会中断)。

编辑 2: 我将 volley 请求全部切换到异步线程中的 HTTPURLConnection 请求,出现与 volley 相同的问题。


tl;dr: 我的应用程序的 Volley 请求在模拟器中有效,但在我的 Android Wear 手表上无效(尽管从 Play 商店下载的应用程序有类似的请求工作),我怎样才能收到 volley 请求以在手表上再次使用我的应用程序?

我还在自己构建的 Android wear 应用程序上使用 volley,我 运行 在 Moto 360 上使用它,我 运行 遇到了同样的问题次。尝试重新启动设备。转到“设置”>“重新启动”。这听起来很傻,但对我有用。

这不只是手表通过蓝牙连接到 phone 时互联网无法工作的情况,因为 wifi 已关闭。如果手表使用 wifi 连接到 phone 那么它将工作。

我正在开发 Wear 2.0 应用程序,只需关闭我的 phone 手表上的蓝牙即可连接互联网。

如果您可以排除连接的问题,您可以尝试 volley 的替代方法:

compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:support-v4:23.1.0'
compile 'com.android.support:design:23.1.0'
compile 'com.google.code.gson:gson:2.2.4'
compile 'com.google.api-client:google-api-client:1.20.0'

版本很重要。

那么对您的要求:

Map<String, String> contentParams = new HashMap<>();
InputStream is = null;
NetHttpTransport transport = null;
HttpRequest request = null;
HttpResponse resp = null;
HttpHeaders headers = new HttpHeaders();
JSONObject json = null;

    try {
        transport = new NetHttpTransport();
        HttpRequestFactory factory = transport.createRequestFactory();
        request = factory.buildPostRequest(new GenericUrl(url), null);
        contentParams = getContentParameters();
        headers.putAll(getHeaderParameters());
        request.setHeaders(headers);
        request.getUrl().putAll(contentParams);
        resp = request.execute();
        is = resp.getContent();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (is != null) {
                string = getJSONFromInputStream(is);
                json = new JSONObject(string);
            }
         } catch (Exception e) {
            e.printStackTrace();
        }
    }
    transport.shutdown();

protected Map<String, String> getContentParameters() {
     Map<String, String> m = new HashMap<>();
     m.put("version", "3.0.5");
     m.put("email", userEmail);
     m.put("password", userToken);
     return m;
}

protected Map<String, String> getHeaderParameters() {
     Map<String, String> m = new HashMap<>();
     m.put("Referer", "app:/VenstarCloud.swf");
     return m;
}

protected String getJSONFromInputStream(InputStream is) {
    if (is == null)
        throw new NullPointerException();
    //instantiates a reader with max size
    BufferedReader reader = new BufferedReader(new InputStreamReader(is), 8 * 1024);

    StringBuilder sb = new StringBuilder();

    try {
        //reads the response line by line (and separates by a line-break)
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line + "\n");
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            //closes the inputStream
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return sb.toString();
}

然后只需从 thread/asynctask/have 执行您的代码,它会稍微延迟您的前端

编辑: 以防万一附加地图出现问题:

for (Entry<String, String> entry : getHeaderParameters()) {
    headers.put(entry.getKey(), entry.getValue());
}

for (Entry<String, String> entry : getContentParameters()) {
    request.getUrl().put(entry.getKey(), entry.getValue());
}

另请注意,请确保将这两种方法上的 return 类型从 void 更改为 Map

根据下面这两个对话,WiFi 连接似乎只允许 Android Wear 通过 WiFi 连接到 phone,而不是直接连接到互联网。但是,Android Wear 2.0 允许您使用常规网络 API。

Direct internet connection on Android Wear?

因此,对于 Android Wear 2.0+ Volley 来自可穿戴应用程序的请求应该有效。

如果你想用Android穿<2.0,那么:

在可穿戴设备上,在 onCreate() 添加一个键,指示 phone 是否应该开始收集数据。

PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/shouldStart");
putDataMapReq.getDataMap().putBoolean(SHOULD_START_KEY, true);
PutDataRequest putDataReq = putDataMapReq.asPutDataRequest();
PendingResult pendingResult = Wearable.DataApi.putDataItem(mGoogleApiClient, putDataReq);

在 phone,在 onDataChanged,检查可穿戴设备是否要开始收集数据。如果是,开始Volley请求。

for (DataEvent event : dataEvents) {
        if (event.getType() == DataEvent.TYPE_CHANGED) {
            // DataItem changed
            DataItem item = event.getDataItem();
            if (item.getUri().getPath().compareTo("/shouldStart") == 0) {
                DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
                boolean shouldStart = dataMap.getBoolean(SHOULD_START_KEY));
                if(shouldStart) {
                    Volley.newRequestQueue(this).add(request);
                }

            }
        } else if (event.getType() == DataEvent.TYPE_DELETED) {
            // DataItem deleted
        }
    }

然后,您的 Volley 请求的 onResponse 应该将数据传回 Wearable。

public void onResponse(String response) {

    PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/data");
    putDataMapReq.getDataMap().putString(DATA_KEY, true);
    PutDataRequest putDataReq = putDataMapReq.asPutDataRequest();
    PendingResult pendingResult = Wearable.DataApi.putDataItem(mGoogleApiClient, putDataReq);

 }

最后,您可以使用 onDataChanged 访问可穿戴设备中的数据并将其存储在您的模型中以将其传递到适配器:

for (DataEvent event : dataEvents) {
    if (event.getType() == DataEvent.TYPE_CHANGED) {
         // DataItem changed
         DataItem item = event.getDataItem();
         if (item.getUri().getPath().compareTo("/data") == 0) {
             DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
             parseAndpassToAdapter(dataMap.getString(DATA_KEY));
         }
    } else if (event.getType() == DataEvent.TYPE_DELETED) {
            // DataItem deleted
    }
}

您需要 Wearable.API 来实现它,您的 class 应该实现 DataApi.DataListener。有关入门的更多信息,请参阅 Accessing the Wearable Data Layer and Syncing Data Items

希望这对您有所帮助。