Android 4.0上,Android BroadcastReceiver的onReceive()方法被调用两次。

31

我在Android 4.0.3上遇到了一个问题(在4.1.2上它正常工作)。 在我的Activity中,我有一个BroadcastReceiver。当我发送广播时,方法onReceive()总是被调用两次。请给我关于4.0和4.1上BroadcastReceiver差异的任何建议。

private final GcmBroadcastReceiver gcmReceiver = new GcmBroadcastReceiver() {

    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (action != null && action.equals(MyBroadcastReceiver.ACTION)) {
            Log.d("tag", intent.getStringExtra("alert"));
            }
        }
    };

};


@Override
protected void onPause() {
    unregisterReceiver(gcmReceiver);
    super.onPause();
}

@Override
protected void onResume() {
    super.onResume();
    registerGcmReceiver();
}

private void registerGcmReceiver() {
    IntentFilter filter = new IntentFilter(MyBroadcastReceiver.ACTION);
    filter.setPriority(2);
    filter.addCategory("com.android.mypackage");
    registerReceiver(gcmReceiver, filter);
}

我也遇到了同样的问题,但应该是我们这边出了错,我在调用某个方法时调用了两次,导致接收到了两个意图。我建议您调试一下您的代码。 - AZ_
15个回答

45

通常 onReceive 会被调用两次,因为人们在2个位置注册广播很可能你在 onCreate 和 onResume 中都注册了。 (选择一个位置进行注册)。

虽然您可能已经做了几次 - 但始终建议再次查看Activity Life Cycle


1
这是一个非常棒的建议。谢谢@Mercury。 - Amar
1
简单而精彩的回答。+1 - Parth Anjaria
这对我解决了问题,我确实注册了两次:在清单中和通过代码。 - KenSchnetz
1
对我来说,我只在一个地方注册了它,但由于我的onResume()在对话框后第二次被调用,所以它会被注册两次。 - ConcernedHobbit
有时候你在onResume()/onStart()中注册,但在onDestroy()中取消注册。而且很可能会多次调用onResume()/onStart()。因此会出现多次触发的情况。 - Johnny Five

25

我曾有同样的问题。

onReceive()被调用了两次,因为我在活动的onCreate()manifest中都注册了广播接收器。

我将广播接收器的注册从onCreate()移除后,它正常工作了。只需在其中一个位置注册您的广播接收器。

有两种方法可以做到这一点:

  • 在清单文件中静态注册。
  • 在代码中动态注册。

何时使用哪种方法(静态或动态)完全取决于你想做什么。基本上,当你想通过监听系统范围内事件或其他应用程序发送的事件来在屏幕上直接进行一些更改(主屏幕、启动器、状态栏等),并通过在状态栏中显示一些通知或指示器来实现时,使用静态注册的广播接收器是有意义的。而当你想要根据类似的事件在你的应用中直接进行更改,当用户使用它或者将其放在后台时,使用动态注册的接收器是有意义的,这些接收器将持续到注册组件被销毁为止。

了解更多信息: http://codetheory.in/android-broadcast-receivers/


11

简短回答:没有区别。这两个类的BroadcastReceiver都来自于 Android 2.3.2 r1。

我曾经遇到过类似的问题,但是对于我而言,是在安装有 Android 2.3.5 的 HTC Desire HD 上 - 来自 GCM 的通知总是会被接收到两次。我没有找到问题的根源,但是有一个解决方法。您可以为服务器中的每个通知生成唯一的ID,并将其与实际数据一起发送。然后,在接收器中,您可以更新唯一ID与通知数据的映射,并且如果给定ID已经存在数据,则忽略它。

我可能说得不太清楚,所以这里是一个例子:

public void onReceive(Context context, Intent intent) {
    String id = intent.getStringExtra("notificationID");
    if (myMap.get(id) != null)
        return;

    final String action = intent.getAction();
    if (action != null && action.equals(MyBroadcastReceiver.ACTION)) {
        Log.d("tag", intent.getStringExtra("alert"));
        myMap.put(id, *any value you need*);
    }
}

如果您不需要存储额外的数据,可以使用HashSet而不是Map,并检查它是否包含该ID。


顺便说一下,我在HTC上也遇到了这样的问题(HTC ONE X)。 - girlOnSledge
什么是myMap标识符? - user5093161
myMap is any Java Map, e.g. a HashMap - npace
这个答案可能会误导人。我差点就把它当成真的了,直到看到 Mercury 的回答。 - micseydel
我同意。就我个人而言,我记得这是一个设备和操作系统特定的问题,而不是由于注册两次造成的。Mercury的答案有助于解决一个更常见但看起来非常相似的问题。 - npace

8

onStart()中初始化BroadcastReceiver。这样可以解决我的问题。


问题是,为什么它一开始就会这样做...糟糕的安卓。 - Martin Kovachev
@Martin Kovachev - 一个和我志同道合的人。 - Richard Hammond

2
您也可以通过设置布尔标志来实现它,当您进入onReceive()函数时。

2

针对Xamarin开发人员:

我曾遇到类似问题,原因是我在代码中的某处注册了广播接收器两次:

Android.App.Application.Context.RegisterReceiver(myReceiver, new IntentFilter("MY_INTENT_ACTION"));

另一种方式是通过属性:

[BroadcastReceiver(Enabled = true, Exported = true)]
[IntentFilter(new[] { "MY_INTENT_ACTION" })]
public class MyReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
    }
}

我删除了属性,问题得到解决!


1
我相信这是确定Wifi是否已连接或断开的正确方法。
在BroadcastReceiver中的onReceive()方法:只需放置一个简单的检查(这是由SharedPreferences实现的逻辑):
package com.example.broadcasttest;

import java.util.List;
import java.util.Random;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import android.widget.Toast;

public class CustomReceiver extends BroadcastReceiver {

boolean isInternetPresent = false;
@Override
public void onReceive(Context context, Intent intent) {
    // TODO Auto-generated method stub
    ActivityManager am = (ActivityManager) context
            .getSystemService(Activity.ACTIVITY_SERVICE);
    String packageName = am.getRunningTasks(1).get(0).topActivity
            .getPackageName();
    @SuppressWarnings("deprecation")
    List<RunningTaskInfo> taskInfo = am.getRunningTasks(1);
    ComponentName componentInfo = taskInfo.get(0).topActivity;


    if(packageName.equals("com.example.broadcasttest") && taskInfo.get(0).topActivity.getClassName().toString().equals("com.example.broadcasttest.MainActivity"))
    {

        //          SharedPreferences sharedPreferences=context.getSharedPreferences("FLAG", Context.MODE_PRIVATE);
        //          String check=sharedPreferences.getString("check","");

        ConnectionDetector  cd = new ConnectionDetector(context);
        // get Internet status
        isInternetPresent = cd.isConnectingToInternet();

        // check for Internet status
        if (isInternetPresent) {
            SharedPreferences sharedPreferences=context.getSharedPreferences("FLAG", Context.MODE_PRIVATE);
            String check=sharedPreferences.getString("check","");
            if(check.equals("chkd1")){
                Log.d("activity name", "CURRENT Activity ::" + taskInfo.get(0).topActivity.getClassName()+"   Package Name :  "+componentInfo.getPackageName());


                String abc = context.getClass().toString();
                Toast.makeText(context, "hiiii "+abc, Toast.LENGTH_SHORT).show();
                Log.e("ghorar kochu", "checking:");
                MainActivity m = new MainActivity();

                m.abc(context);
            }
            else if(check.equals("chkd"))
            {
                Log.e("Thanks For the checking", "checking:"+check);
            }
        }
        else if (!isInternetPresent) {
                 SharedPreferences sharedPreferences1=context.getSharedPreferences("FLAG", Context.MODE_PRIVATE);
            SharedPreferences.Editor editor1=sharedPreferences1.edit();
            editor1.putString("check","chkd1");
            editor1.commit();
        }

    }




}



}

这是实际的接收器类。现在进入主活动。

package com.example.broadcasttest;

import java.net.URLEncoder;
import java.util.ArrayList;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends Activity {

    Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SharedPreferences sharedPreferences1=getSharedPreferences("FLAG", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor1=sharedPreferences1.edit();
        editor1.putString("check","chkd1");
        editor1.commit();
        btn = (Button)findViewById(R.id.btn);

        btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent in =new Intent(MainActivity.this,Second.class);
                startActivity(in);

            }
        });
    }
    public void abc(Context context){
        try{
        SharedPreferences sharedPreferences1=context.getSharedPreferences("FLAG", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor1=sharedPreferences1.edit();
        editor1.putString("check","chkd");
        editor1.commit();
        }
        catch(NullPointerException e)
        {
            e.printStackTrace();
        }
        new JsonForTimeLineList().execute();
        }

    class JsonForTimeLineList extends AsyncTask<Void, Void, Void> {

        private String msg = null;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //  pDialog.show();


        }


        @Override
        protected Void doInBackground(Void... params) {
            return null;
        }

        @Override
        protected void onPostExecute(Void  resultaaa) {


            Log.e("hi", "helo");


        }

    }

    }

这段代码仅在您的应用程序处于“onresume”状态并且在MainActivity中时才能正常工作。
以下是我的ConnectionDetector类。通过它,我检查wifi连接。
package com.example.broadcasttest;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;

public class ConnectionDetector {

     private Context _context;

        public ConnectionDetector(Context context){
            this._context = context;
        }

        public boolean isConnectingToInternet(){
            ConnectivityManager connectivity = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
              if (connectivity != null) 
              {
                  NetworkInfo[] info = connectivity.getAllNetworkInfo();
                  if (info != null) 
                      for (int i = 0; i < info.length; i++) 
                          if (info[i].getState() == NetworkInfo.State.CONNECTED)
                          {
                              return true;
                          }

              }
              return false;
        }


}

这是我的Manifest.xml文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />

     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".Second"
            android:label="@string/app_name" >

        </activity>
        <receiver android:name="com.example.broadcasttest.CustomReceiver">
            <intent-filter >
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

当我启用WiFi时,以下是我的日志记录。

11-19 20:13:11.474: D/activity name(25417): CURRENT Activity ::com.example.broadcasttest.MainActivity   Package Name :  com.example.broadcasttest
11-19 20:13:11.481: E/ghorar kochu(25417): checking:
11-19 20:13:11.542: E/hi(25417): helo
11-19 20:13:11.573: V/RenderScript(25417): Application requested CPU execution
11-19 20:13:11.580: V/RenderScript(25417): 0xb90a7850 Launching thread(s), CPUs 4
11-19 20:13:17.158: E/Thanks For the checking(25417): checking:chkd

如果您有任何疑问,请告诉我。

1
我已经找到了这个问题的原因。
基本上,当我们为BLUETOOTH_STATE_CHANGE注册接收器时,如果我们打开蓝牙,则其onReceive()方法将针对以下两个蓝牙状态调用两次:
状态1:STATE_TURNING_ON 表示本地蓝牙适配器正在启动。
状态2:STATE_ON 表示本地蓝牙适配器已经开启并准备好使用。
同样,在关闭蓝牙时,其onReceive()方法将再次针对以下两个蓝牙状态调用两次:
状态1:STATE_TURNING_OFF 表示本地蓝牙适配器正在关闭。
状态2:STATE_OFF 表示本地蓝牙适配器已关闭。
因此,我们可以按照以下方式处理这种情况:
@Override
public void onReceive(Context context, Intent intent) {
    final String action = intent.getAction();
    if (action != null && action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
        if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_ON) {

            // When Bluetooth adapter is on, and ready for use
            //DO YOUR CODE HERE FOR "BLUETOOTH ON" CASE


        }
        else if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_OFF) {

            //When local Bluetooth adapter is off
            //DO YOUR CODE HERE FOR "BLUETOOTH OFF" CASE

        }

    }
} 

看了你的代码(@girlOnSledge)之后,似乎如果按照上述步骤操作,你的代码在那种情况下也能正常工作。


1

在我的服务类中,我多次调用了sendBroadcast(intent)

移除其中一个解决了问题。


0

我通过使用一个静态值来解决了这个问题。

请查看下面的代码。

我的活动

private static Snackbar mSnackbar;
private NetworkChangeReceiver mNetworkChangeReceiver;

@Override
protected void onResume() {
    super.onResume();
    registerReceivers();
}
@Override
protected void onPause() {
    super.onPause();
    unRegisterReceivers();
}
private void registerReceivers() {
    mNetworkChangeReceiver = new NetworkChangeReceiver() {
        @Override
        protected void onNetworkChange(boolean status) {
            if (!status) {
                if (mSnackbar == null) {
                    mSnackbar = Snackbar
                            .make(drawer, R.string.no_internet, Snackbar.LENGTH_INDEFINITE)
                            .setActionTextColor(Color.YELLOW)
                            .setAction(R.string.ok, new View.OnClickListener() {
                                @Override
                                public void onClick(View view) {
                                }
                            });
                    mSnackbar.show();
                }
            } else {
                if (mSnackbar != null) {
                    mSnackbar.dismiss();
                    mSnackbar = null;
                }
            }
        }
    };
    IntentFilter nwStateChangeFilter = new IntentFilter();
    nwStateChangeFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
    nwStateChangeFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    registerReceiver(mNetworkChangeReceiver, nwStateChangeFilter);
}
private void unRegisterReceivers() {
    unregisterReceiver(mNetworkChangeReceiver);
}

NetworkChangeReceiver.Class

public abstract class NetworkChangeReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(final Context context, final Intent intent) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        if (activeNetwork != null) { // connected to the internet
                if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI){
                    // connected to wifi
                    onNetworkChange(true);
                } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
                    // connected to the mobile provider's data plan
                    onNetworkChange(true);
                } else {
                    // not connected to the internet
                    onNetworkChange(false);
                }
        } else {
            // not connected to the internet
            onNetworkChange(false);
        }
    }
    protected abstract void onNetworkChange(boolean status);
}

使用静态字段很可能只是一种解决方法,而不是一个解决方案。此外,这种方法没有被单元测试覆盖。 - girlOnSledge
是的,这只是解决此问题的一种权宜之计,而不是真正的解决方案。 - Rasool Mohamed
解决问题的方法可能是一时的,可能是临时的,可能是修复错误的措施,但只要它始终有效(而我的意思是在经过巨大的 Google / Android / Microsoft / Uncle Tom Cobbleigh 等升级后),并且被后来者理解,那么它就是合法的。 - Richard Hammond

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