Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force WebView to operate on a certain Network

Is it possible to force a WebView load an URL via a specified Network?

My device has two network connections open: wifi and mobile data. The mobile data network is set to be the default network for outgoing connections (so the connectivity manager returns the mobile data network for connectivityManager.getActiveNetwork()).

The webpage that I'm trying to load (from http://10.0.0.1:80) is running on a web server which is connected over wifi. So the web view fails to load to page because it's unavailable via mobile data.

like image 760
winklerrr Avatar asked Sep 15 '17 15:09

winklerrr


1 Answers

So I dug a little bit more in this topic by myself and found some helpful information:

  • If a connection should use a given network, you need to bind the sockets manually to that network or directly create the sockets via a socket factory.

  • There is no possibility with the standard implementation of WebView to set a socket factory or to let it use given sockets.

  • But it's possible to bind the complete app process to a network. This will ensure that from that moment on all newly created sockets will be bound to that network. (The original method to set a default network was deprecated at level 23, just have a look at the method bindProcessToNetwork() in my code below.)

  • Multiple network connections are only possible for Android devices running API level 21 and higher.

  • The NetworkCallback can be used to listen for any newly connected networks which fulfil a specified NetworkRequest.

With this knowledge I was able to finally find a working solution:

ConnectionFragment.java

public class ConnectionFragment extends Fragment {

    private static final String TAG = ConnectionFragment.class.getSimpleName();

    private final NetworkCallback networkCallback = new NetworkCallback();
    private ConnectivityManager connectivityManager;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        Log.v(TAG, "onCreate");
        super.onCreate(savedInstanceState);

        connectivityManager = (ConnectivityManager) getActivity().getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
    }

    @Override
    public void onStart() {
        Log.v(TAG, "onStart");
        super.onStart();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            registerNetworkCallback();
        }
    }

    @Override
    public void onStop() {
        Log.v(TAG, "onStop");
        super.onStop();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            unregisterNetworkCallback();
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void registerNetworkCallback() {
        Log.v(TAG, "registerNetworkCallback");

        final NetworkRequest networkRequest = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .build();

        connectivityManager.registerNetworkCallback(networkRequest, networkCallback);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void unregisterNetworkCallback() {
        Log.v(TAG, "unregisterNetworkCallback");
        connectivityManager.unregisterNetworkCallback(networkCallback);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private class NetworkCallback extends ConnectivityManager.NetworkCallback {

        @Override
        public void onAvailable(Network network) {
            Log.v(TAG, "onAvailable");
            bindProcessToNetwork(network);
        }

        @Override
        public void onLost(Network network) {
            Log.v(TAG, "onLost");
            bindProcessToNetwork(null);
        }

        private void bindProcessToNetwork(final Network network) {
            Log.v(TAG, "bindProcessToNetwork: " + network);

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                connectivityManager.bindProcessToNetwork(network);
            } else {
                ConnectivityManager.setProcessDefaultNetwork(network);
            }
        }

    }

}

I hope this will help someone.

But keep in mind: for all other connection relying on another network you need to configure the sockets manually.

Best regards,
winklerrr

like image 154
winklerrr Avatar answered Oct 12 '22 12:10

winklerrr