Android开发的同学都知道,在很多场景下我们需要监听网络变化,从而做一些业务逻辑比如刷新数据。于是我们会找到这样一个广播:ConnectivityManager.CONNECTIVITY_ACTION,注册一个BroadcastReceiver,添加一个ConnectivityManager.CONNECTIVITY_ACTION,就可以监听网络变化了。再看看这个action的注释:

public static final String CONNECTIVITY_ACTIONAdded in API level 1A change in network connectivity has occurred. A default connection has either been established or lost. The NetworkInfo for the affected network is sent as an extra; it should be consulted to see what kind of connectivity event occurred.If this is a connection that was the result of failing over from a disconnected network, then the FAILOVER_CONNECTION boolean extra is set to true.For a loss of connectivity, if the connectivity manager is attempting to connect (or has already connected) to another network, the NetworkInfo for the new network is also passed as an extra. This lets any receivers of the broadcast know that they should not necessarily tell the user that no data traffic will be possible. Instead, the receiver should expect another broadcast soon, indicating either that the failover attempt succeeded (and so there is still overall data connectivity), or that the failover attempt failed, meaning that all connectivity has been lost.For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY is set to true if there are no connected networks at all.Constant Value: "android.net.conn.CONNECTIVITY_CHANGE"


注释很明确的说明了它的作用——在网络变化的时候触发!加上工程里很多地方都在用。于是觉得万无一失,需求轻松搞定。但是,悲剧就此上演!

线上反馈在网络稳定的时候也会触发该广播,排查发现是在注册广播的时候发送的。直觉告诉我这个广播的作用是只会在网络变化的时候才发送,于是怀疑是某个手机厂商修改rom导致的问题,但是试了包括nexus在内的几乎所有手机都会出现这个问题,就茫然了。注释上明明写的是网络变化的时候才会触发该广播,怎么会在注册的时候也会触发呢?

带着这个问题开始研究android广播机制的源码,重点是注册的过程。

广播注册机制本质上是订阅-发布模式。Android里用到这种机制的地方很多,比如按钮click。在Android框架中,Activity和Service都继承了ContextWrapper类,因此,我们可以在Activity或者Service的子类中调用registerReceiver函数来注册广播接收器。这里我们从Activity里发起注册。

public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        IntentFilter filter = new IntentFilter();        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);        registerReceiver(netReceiver, filter);    }......

在MainActivity里注册了一个ConnectivityManager.CONNECTIVITY_ACTION的广播,当网络变化的时候,系统就会给对应的netReceiver发起一个网络变化的广播。

接下来,分析ContextWrapper.registerReceiver函数:

public class ContextWrapper extends Context {    Context mBase;    ...    public ContextWrapper(Context base) {        mBase = base;    }    @Override    public Intent registerReceiver(        BroadcastReceiver receiver, IntentFilter filter) {        return mBase.registerReceiver(receiver, filter);    }    ...}

ContextWrapper. registerReceiver比较简单,调用的是mBase的registerReceiver方法。mBase是一个ContextImpl的实例。

然后分析ContextImpl.registerReceiver:

class ContextImpl extends Context {    @Override    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {        return registerReceiver(receiver, filter, null, null);    }    @Override    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,            String broadcastPermission, Handler scheduler) {        return registerReceiverInternal(receiver, getUserId(),                filter, broadcastPermission, scheduler, getOuterContext());    }   ....}

可以发现最终是调到registerReceiverInternal方法

private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,            IntentFilter filter, String broadcastPermission,            Handler scheduler, Context context) {        IIntentReceiver rd = null;        if (receiver != null) {            if (mPackageInfo != null && context != null) {                if (scheduler == null) {                    scheduler = mMainThread.getHandler();                }                rd = mPackageInfo.getReceiverDispatcher(                    receiver, context, scheduler,                    mMainThread.getInstrumentation(), true);            } else {                if (scheduler == null) {                    scheduler = mMainThread.getHandler();                }                rd = new LoadedApk.ReceiverDispatcher(                        receiver, context, scheduler, null, true).getIIntentReceiver();            }        }        try {            return ActivityManagerNative.getDefault().registerReceiver(                    mMainThread.getApplicationThread(), mBasePackageName,                    rd, filter, broadcastPermission, userId);        } catch (RemoteException e) {            return null;        }    }


在registerReceiverInternal中,经过前面的一步步准备,最终会调到箭头处的registerReceiver方法。这里需要注意一下rd这个对象,这是IIntentReceiver的实例,是一个Binder对象。因为Android广播是可以跨应用的,就涉及到进程间的通信,这里略去不讲。经过前面的一系列准备,会把MainActivity中的BroadcastReceiver和IntentFilter等数据传递给ActivityManagerProxy的registerReceiver方法。从数据流的角度上讲,我们在MainActivity里创建的BroadcastReceiver和IntentFilter最终传递传给了ActivityManagerProxy.registerReceiver。

因此,重点看看 ActivityManagerProxy.registerReceiver这个方法:

class ActivityManagerProxy implements IActivityManager{    public ActivityManagerProxy(IBinder remote){        mRemote = remote;    }    public Intent registerReceiver(IApplicationThread caller, String packageName,            IIntentReceiver receiver,            IntentFilter filter, String perm, int userId) throws RemoteException{        Parcel data = Parcel.obtain();        Parcel reply = Parcel.obtain();        data.writeInterfaceToken(IActivityManager.descriptor);        data.writeStrongBinder(caller != null ? caller.asBinder() : null);        data.writeString(packageName);        data.writeStrongBinder(receiver != null ? receiver.asBinder() : null);        filter.writeToParcel(data, 0);        data.writeString(perm);        data.writeInt(userId);        mRemote.transact(REGISTER_RECEIVER_TRANSACTION, data, reply, 0);        reply.readException();        Intent intent = null;        int haveIntent = reply.readInt();        if (haveIntent != 0) {            intent = Intent.CREATOR.createFromParcel(reply);        }        reply.recycle();        data.recycle();        return intent;    }     ...}

这个方法比较清晰,封装数据,调用mRemote.transact方法,最终进入到ActivityManagerService中的registerReceiver函数中去了,mRemote是一个IBinder对象。可以看到,数据经过种种传递,最终流向了终极地方——ActivityManagerService的registerReceiver方法!

再来看看ActivityManagerService.registerReceiver这个方法

public final class ActivityManagerService extends ActivityManagerNative        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {    public Intent registerReceiver(IApplicationThread caller, String callerPackage,            IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {        enforceNotIsolatedCaller("registerReceiver");        ArrayList stickyIntents = null;        ProcessRecord callerApp = null;        int callingUid;        int callingPid;        synchronized(this) {            if (caller != null) {                callerApp = getRecordForAppLocked(caller);                if (callerApp == null) {                    throw new SecurityException(                            "Unable to find app for caller " + caller                            + " (pid=" + Binder.getCallingPid()                            + ") when registering receiver " + receiver);                }                if (callerApp.info.uid != Process.SYSTEM_UID &&                        !callerApp.pkgList.containsKey(callerPackage) &&                        !"android".equals(callerPackage)) {                    throw new SecurityException("Given caller package " + callerPackage                            + " is not running in process " + callerApp);                }                callingUid = callerApp.info.uid;                callingPid = callerApp.pid;            } else {                callerPackage = null;                callingUid = Binder.getCallingUid();                callingPid = Binder.getCallingPid();            }            userId = handleIncomingUser(callingPid, callingUid, userId,                    true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);            Iterator actions = filter.actionsIterator();            if (actions == null) {                ArrayList noAction = new ArrayList(1);                noAction.add(null);                actions = noAction.iterator();            }            // Collect stickies of users            int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };            while (actions.hasNext()) {                String action = actions.next();                for (int id : userIds) {                    ArrayMap> stickies = mStickyBroadcasts.get(id);                    if (stickies != null) {                        ArrayList intents = stickies.get(action);                        if (intents != null) {                            if (stickyIntents == null) {                                stickyIntents = new ArrayList();                            }                            stickyIntents.addAll(intents);                        }                    }                }            }        }        ArrayList allSticky = null;        if (stickyIntents != null) {            final ContentResolver resolver = mContext.getContentResolver();            // Look for any matching sticky broadcasts...            for (int i = 0, N = stickyIntents.size(); i < N; i++) {                Intent intent = stickyIntents.get(i);                // If intent has scheme "content", it will need to acccess                // provider that needs to lock mProviderMap in ActivityThread                // and also it may need to wait application response, so we                // cannot lock ActivityManagerService here.                if (filter.match(resolver, intent, true, TAG) >= 0) {                    if (allSticky == null) {                        allSticky = new ArrayList();                    }                    allSticky.add(intent);                }            }        }        // The first sticky in the list is returned directly back to the client.        Intent sticky = allSticky != null ? allSticky.get(0) : null;        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);        if (receiver == null) {            return sticky;        }        synchronized (this) {            if (callerApp != null && (callerApp.thread == null                    || callerApp.thread.asBinder() != caller.asBinder())) {                // Original caller already died                return null;            }            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());            if (rl == null) {                rl = new ReceiverList(this, callerApp, callingPid, callingUid,                        userId, receiver);                if (rl.app != null) {                    rl.app.receivers.add(rl);                } else {                    try {                        receiver.asBinder().linkToDeath(rl, 0);                    } catch (RemoteException e) {                        return sticky;                    }                    rl.linkedToDeath = true;                }                mRegisteredReceivers.put(receiver.asBinder(), rl);            } else if (rl.uid != callingUid) {                throw new IllegalArgumentException(                        "Receiver requested to register for uid " + callingUid                        + " was previously registered for uid " + rl.uid);            } else if (rl.pid != callingPid) {                throw new IllegalArgumentException(                        "Receiver requested to register for pid " + callingPid                        + " was previously registered for pid " + rl.pid);            } else if (rl.userId != userId) {                throw new IllegalArgumentException(                        "Receiver requested to register for user " + userId                        + " was previously registered for user " + rl.userId);            }            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,                    permission, callingUid, userId);            rl.add(bf);            if (!bf.debugCheck()) {                Slog.w(TAG, "==> For Dynamic broadcast");            }            mReceiverResolver.addFilter(bf);            // Enqueue broadcasts for all existing stickies that match            // this filter.            if (allSticky != null) {                ArrayList receivers = new ArrayList();                receivers.add(bf);                final int stickyCount = allSticky.size();                for (int i = 0; i < stickyCount; i++) {                    Intent intent = allSticky.get(i);                    BroadcastQueue queue = broadcastQueueForIntent(intent);                    BroadcastRecord r = new BroadcastRecord(queue, intent, null,                            null, -1, -1, null, null, AppOpsManager.OP_NONE, null, receivers,                            null, 0, null, null, false, true, true, -1);                    queue.enqueueParallelBroadcastLocked(r);                    queue.scheduleBroadcastsLocked();                }            }            return sticky;        }    }...}

这个方法相对较长,从16208L-16355L,就摘出几个重点的地方看。

            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());            if (rl == null) {                rl = new ReceiverList(this, callerApp, callingPid, callingUid,                        userId, receiver);                if (rl.app != null) {                    rl.app.receivers.add(rl);                } else {                    try {                        receiver.asBinder().linkToDeath(rl, 0);                    } catch (RemoteException e) {                        return sticky;                    }                    rl.linkedToDeath = true;                }                mRegisteredReceivers.put(receiver.asBinder(), rl);



咱们在MainActivity创建的receiver首先会被add到一个列表里,叫ReceiverList。因为同一个广播可能在多个地方注册,因此需要一个列表保存。然后这个列表被放在以receiver为key的ActivityManagerService的成员变量mRegisteredReceivers中。mRegisteredReceivers实际上是一个HashMap。

至此,这个订阅-发布模式的“订阅”就差不多了,但是这里只是将receiver保存起来,还没有和对应的filter(例如ConnectivityManager.CONNECTIVITY_ACTION)关联。紧接着往后看,我们会发现如下代码:


            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,                    permission, callingUid, userId);            rl.add(bf);            if (!bf.debugCheck()) {                Slog.w(TAG, "==> For Dynamic broadcast");            }            mReceiverResolver.addFilter(bf);


这里便是关联filter和receiver的地方。当我们在sendBroadcast的时候发送了一个广播,系统便会从广播的filter中找到对应的receiver,然后将消息传递过去。

最后,回到最初的问题,为什么会在注册的时候收到网络变化的广播。答案也在ActivityManagerService. registerReceiver方法中。我们知道广播的类型分为普通广播、有序广播和粘性广播,问题就出在粘性广播上!

回到代码中:

        ArrayList allSticky = null;        if (stickyIntents != null) {            final ContentResolver resolver = mContext.getContentResolver();            // Look for any matching sticky broadcasts...            for (int i = 0, N = stickyIntents.size(); i < N; i++) {                Intent intent = stickyIntents.get(i);                // If intent has scheme "content", it will need to acccess                // provider that needs to lock mProviderMap in ActivityThread                // and also it may need to wait application response, so we                // cannot lock ActivityManagerService here.                if (filter.match(resolver, intent, true, TAG) >= 0) {                    if (allSticky == null) {                        allSticky = new ArrayList();                    }                    allSticky.add(intent);                }            }        }        // The first sticky in the list is returned directly back to the client.        Intent sticky = allSticky != null ? allSticky.get(0) : null;        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);        if (receiver == null) {            return sticky;        }

这段代码会去找所有filter对应的Sticky Intent。Sticky Intent对应的就是粘性广播,这个广播会在第一次发送之后被系统保存到内存里(手机关机会清零),等以后每次调用registerReceiver来注册相同filter的广播接收器时,就会得到这个广播。在本例中由于只注册了一次,因此allSticky.size()为1。

继续往后看:

            // Enqueue broadcasts for all existing stickies that match            // this filter.            if (allSticky != null) {                ArrayList receivers = new ArrayList();                receivers.add(bf);                final int stickyCount = allSticky.size();                for (int i = 0; i < stickyCount; i++) {                    Intent intent = allSticky.get(i);                    BroadcastQueue queue = broadcastQueueForIntent(intent);                    BroadcastRecord r = new BroadcastRecord(queue, intent, null,                            null, -1, -1, null, null, AppOpsManager.OP_NONE, null, receivers,                            null, 0, null, null, false, true, true, -1);                    queue.enqueueParallelBroadcastLocked(r);                    queue.scheduleBroadcastsLocked();                }            }

在registerReceiver的最后,如果allSticky.size()不为0,就会把allSticky里面的所有Sticky Intent放到广播队列里——BroadcastQueue。BroadcastQueue是一个异步消息队列,一旦里面有广播消息,就会发送广播!至此,也就明白了为什么会在注册ConnectivityManager.CONNECTIVITY_ACTION的时候会发送广播了。原因就在于ConnectivityManager.CONNECTIVITY_ACTION是一个粘性广播,并且在之前某个时候被发送过,导致内存里保存有,那么以后一旦注册就会发送一个网络变化的广播。

最后,粘性广播在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated。通过BroadcastReceiver.isInitialStickyBroadcast()方法可以知道是否是一个已经被缓存过的粘性广播。





更多相关文章

  1. Android(安卓)获取未读未接来电和未读短信数量
  2. Android利用系统广播---监听应用程序安装和卸载[转]
  3. 说说Android的广播(6) - 广播消息的接收和派发
  4. Android监测手机网络状态变化的广播
  5. android中dumpsys函数介绍与使用
  6. 文章推荐:Android(安卓)BroadcastReceiver应用详解
  7. Android(安卓)48个小知识(第一篇1-24)很强大!
  8. Android代码之路:BroadcastReceiver初体验
  9. 日拱一卒(七)

随机推荐

  1. Android系列教程之Android项目的目录结构
  2. Android日历功能有GestureDetector手势识
  3. Android通过OpenGl Es渲染NV21格式视频
  4. Android使用TextView实现无下划线超链接
  5. Android数据存储(二) Files
  6. Android(安卓)热补丁实践之路
  7. android开机动画制作与播放原理简介
  8. Android(安卓)Studio (IntelliJ/IDEA)在And
  9. Android之log
  10. spring for android