如何处理wifi和移动数据之间的网络变化?

Posted

技术标签:

【中文标题】如何处理wifi和移动数据之间的网络变化?【英文标题】:How to handle network change between wifi and mobile data? 【发布时间】:2018-06-13 07:15:21 【问题描述】:

我正在构建一个 VoIP 应用程序。在 VoIP 通话期间,当用户在 WiFi 和移动数据之间切换时,我在处理该场景时遇到了问题。

在我的呼叫屏幕活动中,我已注册接收器,这有助于我获得有关网络变化情况的通知。

这是我用来检测 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 地址更新为 WiFi IP 地址,所以这里没有问题。但是移动数据仍然同时连接,但 getActiveNetworkInfo() 告诉我,即使我早期连接到移动数据,我也已清楚地连接到 WiFi。

所以问题是当用户关闭 WiFi 按钮时,移动数据仍然连接,但我仍然收到 WiFi 关闭的通知。 即使我的手机仍然连接到移动数据,这表明我的网络已断开。

但一秒钟后,我收到一条通知说移动数据已连接。 但是一旦我收到网络断开连接,我就关闭了我的 VoIP 通话。 因此,当我收到 WiFi 已关闭的通知时,如何确定移动数据是否仍处于连接状态。

我尝试了 getActiveNetworkInfo(),但当我收到关闭 WiFi 的通知时,它恰好为空。

我已经关注了这个链接:

Android API call to determine user setting "Data Enabled"How to tell if 'Mobile Network Data' is enabled or disabled (even when connected by WiFi)?

使用上面的链接,我可以检测到当用户连接到 mobiledata 时,移动数据按钮已启用。它给了我真实的信息。 但是当这种特殊情况发生时,问题就会发生。

现在当 wifi 被禁用时,我会收到通知,但它显示移动数据被禁用,即使我的移动数据已启用。我无法处理这种情况,因为我在收到断开连接通知时断开了电话。

【问题讨论】:

wifi断开时可以保持1-2秒的暂停。然后在 1-2 秒后尝试重新检查移动数据是否可用。 @Rahulrr2602 是的,如果我没有任何解决方案,我打算这样做 【参考方案1】:

您可以使用ConnectivityManager 的API:尤其是在您对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's wifi // nevertheless, for simplicity let's assume that's 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。

这意味着,当 WIFI 断开连接时,您无法事先知道设备最终是否会连接到 LTE。因此,您可以采用以下方法:在处理程序上发布一个动作以在一秒钟内发生,并在此动作中取消调用。如果连接很快出现 - 取消安排之前发布的操作。如果您最终进入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 中也不存在替代方案。相反,您可以使用 API 21 中提供的 registerNetworkCallback()

【讨论】:

让我试试 使用 registerDefaultNetworkCallback,你不会事先知道。或者,您可以注册两个单独的回调,一个用于 TRANSPORT_WIFI,另一个用于 TRANSPORT_CELLULAR。这样,您将可以参考这两个网络(如果它们可用)。大多数 OEM 始终在后台保持 Cell 网络处于活动状态。如果他们不这样做,您可以使用 CM#requestNetwork 来请求 Cell network。【参考方案2】:

我使用 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)
        
    

定时器/一次性的使用允许切换连接类型之间的延迟。

【讨论】:

【参考方案3】:

您可以使用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 BroadcastReceiveronDestroy

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

【讨论】:

当接收到 wifi 关闭的网络通知时连接移动数据时会发生这种情况 @Jeeva isConnected 使用在线实用函数在 onCreate 方法中初始化标志,isConnected 标志将判断旧状态和当前状态 onReceive 动作。 @Jeeva 您可以使用 Handler 发布结束通话的内容,并延迟在 Wifi 和移动数据之间切换,正如 @azizbekian answer 提到的那样。

以上是关于如何处理wifi和移动数据之间的网络变化?的主要内容,如果未能解决你的问题,请参考以下文章

我应该如何处理:Wordpress 社交登录(网络)和 Flutter(移动)

AVPlayer : 如何处理网络中断

如何处理 3G 网络上的 purgeIdleCellConnections?

如何处理 iPhone 中的网络切换?

如何处理特定的客户端配置和apk

如何处理网络中断和连接池