为什么注销ConnectivityManager.NetworkCallback后会出现错误"Unable to execute HTTP request"?

Why does the error "Unable to execute HTTP request" occur after unregistering ConnectivityManager.NetworkCallback?

在使用 AWS 服务的 Android 应用程序中,如果我注销已注册的 ConnectivityManager.NetworkCallback,该应用程序将无法再联系 AWS 服务。我不确定为什么会发生这种情况,或者如何再次联系 AWS 服务。目前,重新连接到 AWS 的唯一方法是终止应用程序并重新启动它。

详细来说,该应用程序的一个功能是将用户的 Android 设备连接到不同的 WiFi 网络热点,以便设置物联网设备。在使用时,用户将使用 AWS 的 Cognito 服务登录。因为从 API 级别 29 开始,应用程序连接到 WiFi 的方式发生了变化,因此仅在此类设备上调用此关联代码,并且问题与所述设备隔离。以下是有关如何创建连接的相关片段:

    NetworkRequest request =
        new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_WIFI)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .setNetworkSpecifier(specifier)
                .build();

    ConnectivityManager connectivityManager = (ConnectivityManager) viewModel
            .getApplication()
            .getSystemService(Context.CONNECTIVITY_SERVICE);

    ConnectivityManager.NetworkCallback networkCallback = viewModel.getNetworkCallback();

    if (connectivityManager != null && request != null) {
        // Request the network.
        connectivityManager.requestNetwork(request, networkCallback, TIMEOUT_AMOUNT);
    }

如果设置完成、遇到错误或用户决定向后导航退出此设置,则会取消注册回调。无论如何,这是用于完成注销的代码:

    ConnectivityManager connectivityManager = (ConnectivityManager) viewModel
        .getApplication()
        .getApplicationContext()
        .getSystemService(Context.CONNECTIVITY_SERVICE);
    WiFiNetworkCallback networkCallback = viewModel.getNetworkCallback();
    if (connectivityManager != null) {
        try {
            connectivityManager.unregisterNetworkCallback(networkCallback);
        } catch (IllegalArgumentException e) {
            // Network is already unregistered.
            e.printStackTrace();
        }
    }

这段代码在所有情况下都能成功调用。此时,用户不再连接到 WiFi 热点。在我的测试中,我现在可以连接回我的家庭 WiFi,同时我的移动网络也可用。但是,该应用程序无法再联系 AWS。考虑到我的设备仍然连接到 WiFi 网络和我的移动网络,我不希望出现这种结果。下面是一个尝试联系 Cognito 的示例堆栈跟踪(例如,如果联系 DynamoDB 会发生相同类型的错误):

W/System.err: com.amazonaws.AmazonClientException: Unable to execute HTTP request: Unable to resolve host "cognito-idp.us-east-2.amazonaws.com": No address associated with hostname
W/System.err:     at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:456)
W/System.err:     at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:229)
W/System.err:     at com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProviderClient.invoke(AmazonCognitoIdentityProviderClient.java:6329)
W/System.err:     at com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProviderClient.getUser(AmazonCognitoIdentityProviderClient.java:4052)
W/System.err:     at com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser.getUserDetailsInternal(CognitoUser.java:1487)
W/System.err:     at com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser.access0(CognitoUser.java:133)
W/System.err:     at com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser.run(CognitoUser.java:1433)
W/System.err:     at java.lang.Thread.run(Thread.java:923)
W/System.err: Caused by: java.net.UnknownHostException: Unable to resolve host "cognito-idp.us-east-2.amazonaws.com": No address associated with hostname
W/System.err:     at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:124)
W/System.err:     at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
W/System.err:     at java.net.InetAddress.getAllByName(InetAddress.java:1152)
W/System.err:     at com.android.okhttp.Dns.lookup(Dns.java:41)
W/System.err:     at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:178)
W/System.err:     at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:144)
W/System.err:     at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:86)
W/System.err:     at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:192)
W/System.err:     at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:144)
W/System.err:     at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:106)
W/System.err:     at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:400)
W/System.err:     at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:333)
W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465)
W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131)
W/System.err:     at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:262)
W/System.err:     at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getOutputStream(DelegatingHttpsURLConnection.java:219)
W/System.err:     at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:30)
W/System.err:     at com.amazonaws.http.UrlHttpClient.writeContentToConnection(UrlHttpClient.java:162)
W/System.err:     at com.amazonaws.http.UrlHttpClient.execute(UrlHttpClient.java:75)
W/System.err:     at com.amazonaws.http.AmazonHttpClient.executeHelper(AmazonHttpClient.java:386)
W/System.err:   ... 7 more

我已将其隔离到特定的应用程序执行 .unregisterNetworkCallback() 原因。如果不调用它,应用程序将不会失去联系 AWS 的能力。

此外,在注销后尝试任何 AWS 连接时,在记录上述异常之前,其中许多语句将打印到日志中:

I/System.out: (HTTPLog)-Static: isSBSettingEnabled false

根据我的研究,它似乎与三星设备有关。我正在三星 Galaxy S10 Lite 上对此进行测试。我不确定这是否与手头的问题有关。

为什么会这样?我是否以某种方式错误地使用了 ConnectivityManager.NetworkCallback ,无论是在注册还是注销时?是 AWS 的问题吗?

我意识到这个问题涉及这个问题中共享的代码之外的一段代码。 NetworkCallback 的 onAvailable() 是这样执行的:

    @Override
    public void onAvailable(@NonNull Network network) {
        connectivityManager.bindProcessToNetwork(network);
    }

我们调用 .bindProcessToNetwork() 的原因是应用程序仍然可以访问 Internet,而不仅仅是 WiFi 网络。

这是为什么应用程序在调用 .unregisterNetworkCallback() 后失去与 AWS 连接的关键;它正在取消注册,但它仍然绑定到我们刚刚取消注册的网络。因此,当要取消注册 NetworkCallback 时,请务必先调用 .bindProcessToNetwork() 并传递 null 以清除绑定:

    ConnectivityManager connectivityManager = (ConnectivityManager) viewModel
        .getApplication()
        .getApplicationContext()
        .getSystemService(Context.CONNECTIVITY_SERVICE);
    WiFiNetworkCallback networkCallback = viewModel.getNetworkCallback();
    if (connectivityManager != null) {
        try {
            // Remove the app from the network callback first before deregistering it. Otherwise,
            // the app will be stuck on the deregistered network (giving it no connectivity).
            connectivityManager.bindProcessToNetwork(null);
            connectivityManager.unregisterNetworkCallback(networkCallback);
        } catch (IllegalArgumentException e) {
            // Network is already unregistered.
            e.printStackTrace();
        }
    }

我通过阅读 得出了这个答案。它很好地概述了整个 WiFi connection/management 过程如何为 Android API 29 及更高版本工作,包括断开连接并允许用户继续其应用程序体验的最后一部分普通 WiFi 或蜂窝网络。