Как обрабатывать изменение сети между Wi-Fi и мобильными данными?

Я создаю приложение VoIP. Во время вызова VoIP, когда пользователь переключается между Wi-Fi и мобильными данными, у меня есть проблема с обработкой сценария.

В своей активности на экране вызова я зарегистрировался для получателя, который помогает мне получать уведомления о сценариях изменения сети.

Это код, который я использую для обнаружения изменений в сетях в методе onRecieve. conn_name - это закрытая переменная уровня класса, содержащая предыдущее имя соединения.

ConnectivityManager connectivity_mgr = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
    NetworkInfo net_info = connectivity_mgr.getActiveNetworkInfo();

    if (net_info != null && net_info.isConnectedOrConnecting() && !conn_name.equalsIgnoreCase("")) {
        new_con = net_info.getExtraInfo();        
        if (new_con != null && !new_con.equalsIgnoreCase(conn_name))
            network_changed = true;
        conn_name = (new_con == null) ? "" : new_con;
        connectionStatus ="connected";        
    } else {
        if (net_info != null && conn_name.equalsIgnoreCase("")){
            conn_name = net_info.getExtraInfo();
            connectionStatus ="connected";
            network_changed = true;
        }else if(!new_con.equals(conn_name)) {    
            conn_name = "";
            connectionStatus ="disconnected";
            network_changed = true;          
        }        
    }

Таким образом, используя вышеуказанный метод, я смог обнаружить изменения в сети. Но одна странная вещь происходит, когда я подключен к WiFi. Когда мое приложение запускается изначально, оно связано с мобильными данными. Когда пользователь входит в свою известную зону WiFi, он подключается к своей известной сети WiFi. Поскольку WiFi всегда выбирается в качестве маршрута по умолчанию, Android переключается на WiFi, и я получаю сетевое уведомление о том, что WiFi включен.

Поэтому я обновляю IP-адрес своих приложений до IP-адреса WiFi, поэтому никаких проблем здесь нет. Но все же мобильные данные все еще подключены в то же время, но getActiveNetworkInfo() говорит мне, что я четко подключен к WiFi, даже если раньше был подключен к мобильным данным.

Таким образом, проблема в том, что когда пользователь отключает кнопку WiFi, и мобильные данные все еще подключены, но я все равно получаю уведомление об отключении WiFi. Это указывает на то, что сеть отключена, даже если мой телефон все еще подключен к мобильным данным.

Но через секунду я получаю уведомление, что мобильные данные подключены. Но как только я получил отключенную сеть, я прекратил свой вызов VoIP. Поэтому, когда я получаю уведомление о выключенном WiFi, как я могу убедиться, что мобильные данные все еще подключены.

Я попытался getActiveNetworkInfo(), но это происходит, когда я получаю уведомление для WiFi отключен.

Я перешел по этой ссылке:

Вызов Android API для определения настроек пользователя "Данные включены"
Как определить, включена ли функция "Данные мобильной сети" (даже если она подключена через WiFi)?

Используя вышеупомянутую ссылку, я могу обнаружить, что кнопка мобильных данных была включена, когда пользователь подключился к mobiledata.it дает мне истину. Но проблема возникает, когда происходит именно этот случай.

Теперь, когда Wi-Fi отключен, я получаю уведомление, но оно показывает, что мобильные данные отключены, даже если мои мобильные данные включены. Я не могу справиться с этой ситуацией, так как я отключаю свои звонки, когда я получаю свое отключенное уведомление.

Ответ 1

Вы можете использовать API-интерфейсы ConnectivityManager: особенно в вашем случае вы заинтересованы в registerDefaultNetworkCallback():


    public class TestActivity extends AppCompatActivity {

        private ConnectivityManager manager;
        private final ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                super.onAvailable(network);
                // this ternary operation is not quite true, because non-metered doesn't yet mean, that it wifi
                // nevertheless, for simplicity let assume that true
                Log.i("vvv", "connected to " + (manager.isActiveNetworkMetered() ? "LTE" : "WIFI"));
            }

            @Override
            public void onLost(Network network) {
                super.onLost(network);
                Log.i("vvv", "losing active connection");
            }
        };

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            manager.registerDefaultNetworkCallback(networkCallback);
        }

        @Override
        protected void onDestroy() {
            super.onDestroy();
            manager.unregisterNetworkCallback(networkCallback);
        }
    }

Мое устройство подключается к LTE примерно за полсекунды.

enter image description here

Это означает, что вы не можете заранее знать, будет ли устройство в конечном итоге подключаться к LTE или нет в то время, когда WIFI будет отключен. Таким образом, вы можете использовать следующий подход: опубликовать действие в обработчике, которое произойдет за секунду, и в рамках этого действия отменить вызов. Если соединение появится в ближайшее время - отмените ранее опубликованное действие. Если вы оказались в Runnable коде, то соединение не было установлено быстро, что означает, что вы должны завершить вызов.


    public class TestActivity extends AppCompatActivity {

        private ConnectivityManager manager;

        private final Handler handler = new Handler();
        private final ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                super.onAvailable(network);
                Log.i("vvv", "connected to " + (manager.isActiveNetworkMetered() ? "LTE" : "WIFI"));

                // we've got a connection, remove callbacks (if we have posted any)
                handler.removeCallbacks(endCall);
            }

            @Override
            public void onLost(Network network) {
                super.onLost(network);
                Log.i("vvv", "losing active connection");

                // Schedule an event to take place in a second
                handler.postDelayed(endCall, 1000);
            }
        };

        private final Runnable endCall = new Runnable() {
            @Override
            public void run() {
                // if execution has reached here - feel free to cancel the call
                // because no connection was established in a second
            }
        };

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            manager.registerDefaultNetworkCallback(networkCallback);
        }

        @Override
        protected void onDestroy() {
            super.onDestroy();
            manager.unregisterNetworkCallback(networkCallback);
            handler.removeCallbacks(endCall);
        }
    }

Недостатком этого подхода является то, что registerDefaultNetworkCallback() доступен начиная с API 24. В ConnectivityManagerCompat также не существует альтернативы. Вместо этого вы можете использовать registerNetworkCallback() который доступен из API 21.

Ответ 2

Вы можете использовать BroadcastReceiver и зарегистрировать NETWORK_STATE_CHANGED_ACTION и WIFI_STATE_CHANGED_ACTION.

private boolean isConnected;

final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null || intent.getAction() == null)
            return;
        switch (intent.getAction()){
            case WifiManager.NETWORK_STATE_CHANGED_ACTION :
            case WifiManager.WIFI_STATE_CHANGED_ACTION :
                if (!isConnected && isOnline(BaseActivity.this)) {
                    isConnected = true;
                    // do stuff when connected
                    Log.i("Network status: ","Connected");
                }else{
                    isConnected = isOnline(BaseActivity.this);
                    Log.i("Network status: ","Disconnected");
                }
                break;
        }
    }
};



@Override
protected void onCreate(Bundle savedInstanceState) {
    isConnected = isOnline(this);
    final IntentFilter filters = new IntentFilter();
    filters.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    filters.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    registerReceiver(broadcastReceiver, filters);
}


public static boolean isOnline(Context ctx) {
    ConnectivityManager cm = (ConnectivityManager) ctx
            .getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo netInfo = cm != null
            ? cm.getActiveNetworkInfo()
            : null;
    return netInfo != null && netInfo.isConnectedOrConnecting();
}

Обновить Не забудьте отменить регистрациюReceiver BroadcastReceiver onDestroy

@Override
protected void onDestroy() {
    unregisterReceiver(broadcastReceiver);
    super.onDestroy();
} 

Ответ 4

Моя реализация с RxJava

class ConnectivityMonitor : ConnectivityManager.NetworkCallback() {
    var networkTimeout: Disposable? = null

    override fun onAvailable(network: Network?) {
        super.onAvailable(network)
        Timber.d("Network available")
        networkTimeout?.dispose()
    }

    override fun onLosing(network: Network?, maxMsToLive: Int) {
        super.onLosing(network, maxMsToLive)
        Timber.d("onLosing")
    }

    override fun onLost(network: Network?) {
        super.onLost(network)
        Timber.d("onLost")

        networkTimeout = Single.timer(5, TimeUnit.SECONDS)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { _ -> Timber.d("Network lost") }
    }

    override fun onUnavailable() {
        super.onUnavailable()
        Timber.d("Network unavailable")
    }
}

Настройка слушателя:

    private fun setupListeners() {
        // connection listener
        val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            connectivityManager.registerDefaultNetworkCallback(connectivityMonitor)
        } else {
            val builder = NetworkRequest.Builder()
            connectivityManager.registerNetworkCallback(builder.build(), connectivityMonitor)
        }
    }

Использование таймера/одноразового использования позволяет задерживать переключение между типами подключения.