广播接收器在一个事件中接收到多个相同的消息

34

我注册了一个接收器来监听网络事件:

<receiver 
    android:label="NetworkConnection"
    android:name=".ConnectionChangeReceiver" >
    <intent-filter >
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>

接收器也很简单:

public class ConnectionChangeReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
        if (activeNetInfo != null) {
                Log.v("@@@","Receiver : " + activeNetInfo);
        } else {
            Log.v("@@@","Receiver : " + "No network");
        }
    }
}

问题是,当Wifi连接时,我会连续收到3条相同的消息,如下所示:

Receiver : NetworkInfo: type: WIFI[], state: CONNECTED/CONNECTED, reason: (unspecified), extra: (none), roaming: false, failover: false, isAvailable: true
Receiver : NetworkInfo: type: WIFI[], state: CONNECTED/CONNECTED, reason: (unspecified), extra: (none), roaming: false, failover: false, isAvailable: true
Receiver : NetworkInfo: type: WIFI[], state: CONNECTED/CONNECTED, reason: (unspecified), extra: (none), roaming: false, failover: false, isAvailable: true

它们全部都是“CONNECTED/CONNECTED”(难道不应该是类似CONNECTING/OBTAINING_IPADDR等的状态吗?),所以问题是如何判断实际连接成功了。我有一些程序需要在wifi真正连接上之后才能运行,我不希望它们连续被调用三次。

PS:3G只发送一条消息,所以这里没有问题。

更新:

看起来这是设备特定的问题。

为了测试,我使用了2个Desire HD和4个随机的Android手机(不同的Aquos型号和一些无名中国品牌)。在DHD和一个随机手机连接wifi时,我收到了3条消息,在其余手机上我只收到了一条消息。真奇怪。


日志之间有多少时间间隔?可以看到事件发生时的currentTimeMillis()吗?我在想它们是否如此紧密地发生,以至于getActiveNetworkInfo()实际上返回同一个对象(静态对象),并被记录三次。 - brianestey
17:51:50.023 17:51:50.414 17:51:50.617 - Sver
嗯,我明白了。我会尝试创建一个只包含这个接收器的干净项目,看看会发生什么。 - Sver
哇,是的,我遇到了设备问题。我更新了我的问题。 - Sver
我只是猜测,但这些手机有什么共同点吗?也许ROM与此有关。 - SERPRO
显示剩余2条评论
6个回答

58

接收多个广播是一个设备特定的问题。一些手机只发送一个广播,而其他手机则发送2或3个。但有一个解决方法:

假设您在wifi断开时会收到断开消息,我会猜测第一个是正确的消息,另外两个可能只是因为某种原因的回声。

为了知道消息已经被调用,您可以使用一个静态布尔变量,在连接和断开之间进行切换,并且只有在接收到连接并且布尔值为true时才调用子程序。类似于:

public class ConnectionChangeReceiver extends BroadcastReceiver {
    private static boolean firstConnect = true;

    @Override
    public void onReceive(Context context, Intent intent) {
        final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        final NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
        if (activeNetInfo != null) {
            if(firstConnect) { 
                // do subroutines here
                firstConnect = false;
            }
        }
        else {
            firstConnect= true;
        }
    }
}

4
谢谢!这是一个真正简单而好的想法。你需要注意两件事情。首先 - 把“firstConnect”存储在某个地方,例如共享偏好设置中;其次,在从3G切换到WiFi时,并没有实际断开连接,所以最好分别处理3G和WiFi事件。 - Sver
你如何从这个类中调用Activity的方法?它不是由Activity初始化的,所以我无法将上下文发送到这个类。 - KasparTr
使用这个技巧开发了一个应用,但是现在很混乱。 但是,“设备特定问题”只针对网络广播,还是任意广播都会被复制? - Blaisorblade
谢谢@Sver。那非常有帮助。 - Rushi M Thakker

9
你还可以在静态字段中缓存上次处理的连接类型,并针对传入的广播进行检查。这样,您将只收到每种连接类型的一个广播。
当连接类型更改时,它显然会起作用。当设备退出连接时,activeNetworkInfo 将为 null,currentType 将为 NO_CONNECTION_TYPE,即默认情况。
public class ConnectivityReceiver extends BroadcastReceiver {

    /** The absence of a connection type. */
    private static final int NO_CONNECTION_TYPE = -1;

    /** The last processed network type. */
    private static int sLastType = NO_CONNECTION_TYPE;

    @Override
    public void onReceive(Context context, Intent intent) {
        ConnectivityManager connectivityManager = (ConnectivityManager)
                context.getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        final int currentType = activeNetworkInfo != null
                ? activeNetworkInfo.getType() : NO_CONNECTION_TYPE;

        // Avoid handling multiple broadcasts for the same connection type
        if (sLastType != currentType) {
            if (activeNetworkInfo != null) {
                boolean isConnectedOrConnecting = activeNetworkInfo.isConnectedOrConnecting();
                boolean isWiFi = ConnectivityManager.TYPE_WIFI == currentType;
                boolean isMobile = ConnectivityManager.TYPE_MOBILE == currentType;

                // TODO Connected. Do your stuff!
            } else {
                // TODO Disconnected. Do your stuff!
            }

            sLastType = currentType;
        }
}

3
在我的情况下,我在onResume中注册了我的BroadcastReceivers,并且只在onDestroy中注销它们。这导致每个broadcastreceiver被注册3或4次,具体取决于活动恢复的次数。将广播接收器设置在正确的位置,以符合活动生命周期,将允许您停止收到多个令人困惑的调用。

是的,这也是我的情况! - mehulmpt

1

onCreate() 中注册您的 LocalBroadcastreceiver,而不是在 onResume() 中。在 onDestroy 中取消注册。


0

我对Aleksander提出的方法有所担忧,因为它没有考虑到同一类型的网络更改,例如从一个WiFi网络到另一个WiFi网络。

我建议比较活动网络的extraInfo,其中包含网络名称,例如WiFi SSID或移动网络名称,如VZW。

 String currentNetworkName = "";

 ConnectivityManager connectivityManager =
            ((ConnectivityManager) context.getSystemService(
                    Context.CONNECTIVITY_SERVICE));

    NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
    boolean connected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
    if (connected) {
        // prevent duplicate connect broadcasts
        String extraInfo = activeNetwork.getExtraInfo();
        if(! currentNetworkName.equals(extraInfo)) {
            // to do: handle network changes
            currentNetworkName = extraInfo;
        }
    } else {
        Log.d(TAG, "is not connected");
        isConnected = false;
        currentNetworkName = "";
    }

0

我有一个应用程序,当用户重新联网时上传数据。由于我的广播接收器可以多次接收意图,这可能导致数据被上传多次。为了处理这个问题,我使用一个服务,如果已经在运行,则不会执行任何操作。

广播接收器:

public class ConnectionChangeReceiver extends BroadcastReceiver {
    private static boolean firstConnect = true;

    @Override
    public void onReceive(Context context, Intent intent) {
        final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        final NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
        if (activeNetInfo != null) {
            startService();
        }   
    }
}

服务:

public class MyService extends Service {
    private boolean mRunning;

    @Override
    public void onCreate() {
        super.onCreate();
        mRunning = false;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!mRunning) {
            mRunning = true;
            uploadTheData();
        }
        return super.onStartCommand(intent, flags, startId);
    }
}

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