连接Wifi时,CONNECTIVITY_ACTION意图接收到两次

52

在我的应用程序中,我有一个通过<receiver>标签启动的作为组件的 BroadcastReceiver,过滤android.net.conn.CONNECTIVITY_CHANGE意图。

我的目标是简单地知道何时建立了Wifi连接,所以我在onReceive()中做的是这样的:

NetworkInfo networkInfo = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI && networkInfo.isConnected()) {
    // Wifi is connected
}

它很好地工作,但每当建立Wifi连接时,我似乎总会在大约一秒钟内收到两个相同的意图。我尝试查看任何我可以从意图、ConnectivityManager和WifiManager获取的信息,但我找不到任何区分这两个意图的东西。

查看日志,至少有另一个BroadcastReceiver也接收到了这两个相同的意图。

它正在运行Android 2.2的HTC Desire上。

不知道为什么会在Wifi连接时收到“重复”的意图,或者这两者之间有什么区别?


https://www.youtube.com/playlist?list=PLrnPJCHvNZuBqr_0AS9BPXgU6gvNeai5S 对于最近实现广播接收器的方法有很好的理解。 - Anubhav Pandey
14个回答

63

注意:如需最新的答案,请参见下面的此处

经过大量的搜索和调试,我相信这是确定Wifi已连接或断开连接的正确方式。

BroadcastReceiver中的onReceive()方法:

public void onReceive(final Context context, final Intent intent) {

if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
    NetworkInfo networkInfo =
        intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
    if(networkInfo.isConnected()) {
        // Wifi is connected
        Log.d("Inetify", "Wifi is connected: " + String.valueOf(networkInfo));
    }
} else if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
    NetworkInfo networkInfo =
        intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
    if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI &&
        ! networkInfo.isConnected()) {
        // Wifi is disconnected
        Log.d("Inetify", "Wifi is disconnected: " + String.valueOf(networkInfo));
    }
}
}

加上以下的 receiver 元素在 AndroidManifest.xml 中

<receiver android:name="ConnectivityActionReceiver"
    android:enabled="true" android:label="ConnectivityActionReceiver">
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
        <action android:name="android.net.wifi.STATE_CHANGE"/>
    </intent-filter>
</receiver>

一些解释:

  • 只考虑ConnectivityManager.CONNECTIVITY_ACTION时,当Wifi连接时,我总是会得到两个含有相同的NetworkInfo实例(都是getType() == TYPE_WIFI且isConnected() == true)的intent - 这就是这个问题所描述的情况。

  • 只使用WifiManager.NETWORK_STATE_CHANGED_ACTION时,当Wifi断开连接时不会广播intent,但是会有两个含有不同的NetworkInfo实例的intent,使得可以确定Wifi连接时的一个事件。

注意:我收到了一个单一的崩溃报告(NPE),其中intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)返回了null。因此,即使它似乎极为罕见,添加一个null检查可能是一个好主意。

干杯, Torsten


3
值得一提的是,至少有时候,在数据连接完全建立(DHCP 配置未完成)之前,WifiManager.NETWORK_STATE_CHANGED_ACTION 意图会在短暂的时刻内被广播,而此时 ConnectivityManager.getActiveNetworkInfo() 可能仍会返回类型为 TYPE_WIFINetworkInfo - Torsten Römer
1
我可以亲自确认Torsten所写的一切内容。感谢您的解决方案。我发现没有更清晰和简单的解决方案令人沮丧。最令人惊讶的是,当WiFi断开连接时,这种行为不会发生。我并不是说这一定是一个错误,但它可能需要更好的文档说明。 - tos
1
从意图中获取NetworkInfo现在已经过时了。然而,不清楚为什么“他们”不希望这保持稳定。有人发现从意图中获取Networkinfo不稳定吗? - Brill Pappin
谢谢,我要求合并账户! - Torsten Römer
1
intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)已被弃用。 - filthy_wizard
显示剩余2条评论

12

如果你监听了 WifiManager.NETWORK_STATE_CHANGED_ACTION,你会收到两次通知,因为在NetworkInfo中有2个方法:

  • isConnectedOrConnecting()
  • isConnected()

第一次调用时,isConnectedOrConnecting() 返回 trueisConnected() 返回 false
第二次调用时,isConnectedOrConnecting()isConnected() 都返回 true

祝好!


10
两种方法对于我来说,这两个事件都返回 true :) - slinden77
这个答案不正确,isConnected() 可以两次返回 true - papezjustin

7

这是在API 21及以上版本上注册连接更改的正确方法。以下代码可以放置在基础活动中,这样您可以期望您应用程序中的每个屏幕(从此活动继承)都会收到这些回调。

首先,创建一个网络回调来监视连接更改。

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private val networkCallback: ConnectivityManager.NetworkCallback = object : ConnectivityManager.NetworkCallback() {

    // Implement the callback methods that are relevant to the actions you want to take.
    // I have implemented onAvailable for connecting and onLost for disconnecting.

    override fun onAvailable(network: Network?) {
        super.onAvailable(network)
    }

    override fun onLost(network: Network?) {
        super.onLost(network)
    }
}

然后,在相关位置注册和注销此回调函数。
override fun onResume() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
        cm?.registerNetworkCallback(NetworkRequest.Builder().build(), networkCallback)
    }
}

在合适的时候注销。

override fun onPause() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
        cm?.unregisterNetworkCallback(networkCallback)
    }
}

请注意,这里有一个关于Build.VERSION_CODES.LOLLIPOP的检查。该功能仅适用于Lollipop及以上版本。如果您的应用程序支持低于API 21,请确保有处理Pre-Lollipop设备中的网络状态更改的计划。


4

更新了Torsten的代码,使得在WIFI断开连接时,只有适当的单个广播被执行。

使用NetworkInfo.getDetailedState() == DetailedState.DISCONNECTED进行检查。

public void onReceive(final Context context, final Intent intent) {
    if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
        NetworkInfo networkInfo = intent
            .getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
        if (networkInfo.isConnected()) {
            // Wifi is connected
            Log.d("Inetify","Wifi is connected: " + String.valueOf(networkInfo));
        }
    } else if (intent.getAction().equals(
        ConnectivityManager.CONNECTIVITY_ACTION)) {
        NetworkInfo networkInfo = intent
            .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
        if (networkInfo.getDetailedState() == DetailedState.DISCONNECTED) {
            // Wifi is disconnected
            Log.d("Inetify","Wifi is disconnected: "+String.valueOf(networkInfo));
        }
    }
}

对于那些想知道变化的人来说,它是将if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI && ! networkInfo.isConnected())改为if (networkInfo.getDetailedState() == DetailedState.DISCONNECTED) - Mr_and_Mrs_D
WiFi和蜂窝数据呢?我相信两者都会导致BroadcastReceiver触发,从而导致更多的调用。 - lazypig

3
如果您将活动注册为意图监听器,则会收到两次相同的消息。具体来说,您需要选择是否要在包级别(XML)或编程级别上进行侦听。
如果您为广播接收器设置了一个类,并将其连接到侦听器,并在活动中附加了意图过滤器,则消息将被复制两次。
希望这可以解决您的问题。

这是正确的,你可以在类中创建Broadcast监听器,也可以在清单文件中创建,但不能同时存在。 - Eric Novins
3
最后我可以对这个发表评论了...你的答案肯定是正确的,但是我只在清单文件中注册了BroadcastReceiver。 - Torsten Römer

2
我使用 "in" 解决了这个问题。
onCreate()
       intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
       intentFilter.addAction("android.net.wifi.WIFI_STATE_CHANGED");
       intentFilter.addAction("android.net.wifi.STATE_CHANGE");
       ctx.registerReceiver(outgoingReceiver, intentFilter);

BroadcastReceiver
 public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                NetworkInfo networkInfo =
                        intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
                if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI &&
                        networkInfo.isConnected()) {
                    // Wifi is connected
                    Log.d("Inetify", "Wifi is connected: " + String.valueOf(networkInfo));

                    Log.e("intent action", intent.getAction());
                    if (isNetworkConnected(context)){
                        Log.e("WiFi", "is Connected. Saving...");
                        try {
                            saveFilesToServer("/" + ctx.getString(R.string.app_name).replaceAll(" ", "_") + "/Temp.txt");
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }}


 boolean isNetworkConnected(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo ni = cm.getActiveNetworkInfo();
        if (ni != null) {
            Log.e("NetworkInfo", "!=null");

            try{
                //For 3G check
                boolean is3g = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
                        .isConnectedOrConnecting();
                //For WiFi Check
                boolean isWifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
                        .isConnected();

                Log.e("isWifi", "isWifi="+isWifi);
                Log.e("is3g", "is3g="+is3g);
                if (!isWifi)
                {

                    return false;
                }
                else
                {
                    return true;
                }

            }catch (Exception er){
                return false;
            }

        } else{
            Log.e("NetworkInfo", "==null");
            return false;
        }
    }

2

我使用SharedPref和时间解决了两次调用的问题。

private static final Long SYNCTIME = 800L;
private static final String LASTTIMESYNC = "DATE";
SharedPreferences sharedPreferences;
private static final String TAG = "Connection";

@Override
public void onReceive(Context context, Intent intent) {
     Log.d(TAG, "Network connectivity change");
     sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

     final ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
     final NetworkInfo ni = connectivityManager.getActiveNetworkInfo();

        if (ni != null && ni.isConnected()) {   

            if(System.currentTimeMillis()-sharedPreferences.getLong(LASTTIMESYNC, 0)>=SYNCTIME)
            {
                sharedPreferences.edit().putLong(LASTTIMESYNC, System.currentTimeMillis()).commit();
                // Your code Here.
            }
     }
     else if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, Boolean.FALSE)) {
            Log.d(TAG, "There's no network connectivity");

    }
}

由于1.call和2.call之间存在小的延迟(约200毫秒),因此在IF语句中,时间第二个调用将停止,只有第一个调用会继续。


尽管这个解决方案听起来很糟糕,但它对我有效。我已经尝试过信号量和同步块,但都没有很好地为我工作。 - lazypig

1
如果您只想接收一次,可以通过变量简单控制它。
 if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
                NetworkInfo activeNetwork = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
                if (activeNetwork != null) { // connected to the internet
                    if (activeNetwork.isConnected() && !isUpdated) {
                        if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
                            // connected to wifi
                        } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
                            // connected to the mobile provider's data plan
                        }

                        isUpdated = true;
                    } else {
                        isUpdated = false;
                    }
                }
            }

1

当打开WIFI时,

  1. 在MOBILE数据开启的情况下,会发送两个广播: 广播 #1:MOBILE数据已断开连接,并且 广播 #2:WIFI已连接
  2. 在MOBILE数据关闭的情况下,只发送一个广播: 广播 #1:WIFI已连接

在上述两种情况下关闭WIFI时,可以观察到类似的行为。

为了区分这两种情况,请按照以下步骤#2和#3:

        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "*** Action: " + intent.getParcelableExtra("networkInfo"));

            NetworkInfo netInfo = intent.getParcelableExtra("networkInfo");

            if(intent.getAction().equalsIgnoreCase("android.net.conn.CONNECTIVITY_CHANGE")) {
                ConnectivityManager connectivityManager
                        = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
                NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
                if (activeNetInfo != null) {
                    if (netInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                        if (netInfo.getState().name().contains("DISCONNECTED")
                                && activeNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                            Log.d(TAG, "WIFI disconnect created this broadcast. MOBILE data ON."); // #1
                        } else if (netInfo.getState().name().contains("CONNECTED")
                                && activeNetInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                            Log.d(TAG, "WIFI connect created this broadcast."); // #2
                        }
                    } else if (netInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                        if (netInfo.getState().name().contains("DISCONNECTED")
                                && activeNetInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                            Log.d(TAG, "MOBILE data disconnect created this broadcast. WIFI ON."); // #3
                        } else if (netInfo.getState().name().contains("CONNECTED")
                                && activeNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                            Log.d(TAG, "MOBILE data connect created this broadcast."); // #4
                        }
                    }
                } else {
                    Log.d(TAG, "No network available");
                }
            }
        }

1

我通过使用 NetworkInfo 的意图扩展解决了这个问题。 在下面的示例中,如果 WiFi 或移动网络已连接,则仅触发一次 onReceive 事件。

if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) {

        NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);

        boolean screenIsOn = false;
        // Prüfen ob Screen on ist
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            screenIsOn = pm.isInteractive();
        } else {
            screenIsOn = pm.isScreenOn();
        }

        if (Helper.isNetworkConnected(context)) {
            if (networkInfo.isConnected() && networkInfo.isAvailable()) {
                Log.v(logTAG + "onReceive", "connected");

                if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                    Log.v(logTAG + "onReceive", "mobile connected");

                } else if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                    Log.v(logTAG + "onReceive", "wifi connected");
                }
            }
        }

和我的帮手:

    public static boolean isNetworkConnected(Context ctx) {
    ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo ni = cm.getActiveNetworkInfo();

    return ni != null;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接