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

public static final String CONNECTIVITY_ACTION
Added in API level 1
A 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 forthe 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 datatraffic 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 {@Overrideprotected 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;}@Overridepublic Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {return mBase.registerReceiver(receiver, filter);}...
}

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

然后分析ContextImpl.registerReceiver:

class ContextImpl extends Context {@Overridepublic Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {return registerReceiver(receiver, filter, null, null);}@Overridepublic 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 ActivityManagerNativeimplements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {public Intent registerReceiver(IApplicationThread caller, String callerPackage,IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {enforceNotIsolatedCaller("registerReceiver");ArrayList<Intent> 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<String> actions = filter.actionsIterator();if (actions == null) {ArrayList<String> noAction = new ArrayList<String>(1);noAction.add(null);actions = noAction.iterator();}// Collect stickies of usersint[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };while (actions.hasNext()) {String action = actions.next();for (int id : userIds) {ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);if (stickies != null) {ArrayList<Intent> intents = stickies.get(action);if (intents != null) {if (stickyIntents == null) {stickyIntents = new ArrayList<Intent>();}stickyIntents.addAll(intents);}}}}}ArrayList<Intent> 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<Intent>();}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 diedreturn 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<Intent> 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<Intent>();}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()方法可以知道是否是一个已经被缓存过的粘性广播。


深入分析Android监听网络变化的坑相关推荐

  1. Android 监听网络变化弹出提示窗口

    项目有这个需求,监听如果网络断开后3秒内如果没有恢复则弹出网络异常的页面.于是在度娘找了些资料自己写了一个.现在分享一下出现的问题以及解决方法. 1.查到要监听网络需要使用广播接收者.于是摘了一段网上 ...

  2. Android 第十九课 大喇叭--广播机制----动态注册监听网络变化与静态注册实现开机启动

    为了便于进行 系统级别的消息通知,Android引入了一套广播消息机制. 1.广播机制简介: 因为Android中的每个应用程序都可以对自己感兴趣的广播尽心注册,这样程序只会接收自己所关心的广播内容, ...

  3. android 监听网络状态

    今天,讲讲怎么监听手机网络状态的改变. 一.加入网络权限 获取网络信息需要在AndroidManifest.xml文件中加入相应的权限. <uses-permission android:nam ...

  4. android 监听界面变化,Android之页面有变化用onWindowFocusChanged来监听权限是否开启...

    1 问题 我们需要在Activity里面监听网络变化.热点是否开启和关闭.GPS服务是否开启.位置权限是否开启等一些列行为. 2 思路 方法一: 如果是需要启动activity进行权限申请,我们可以用 ...

  5. Android检测网络状态监听网络变化

    网络状态改变.用户信息改变(帐户的钱),第一时间就要想到用观察者模式来实现 广播接收者,简单地把网络的变化通过接口抛出去 package com.qkt.face.et.receiver;import ...

  6. android 监听图库变化,Android ContentObserver 监听图库变化

    电脑环境是XP,软件是gVim7.3,安装在C盘的 Program Files 下.如何设置gVim的字体和背景颜色:C---Program Files---Vim---_vimrc文件,用文本编辑器 ...

  7. Android 监听网络连接状态,判断网络连接方式,9.0网络连接,获取已连接WiFi名称SSID和MAC

    获取已连接的WiFi名称 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> ...

  8. android 7.0 短信监控,Android 7.0 监听网络变化的示例代码

    Android7.0前,Android系统前网络切换时,会发广播,业务只要监听广播即可. public class NetChangeReceiver extends BroadcastReceive ...

  9. Android 7.0 隐式广播-监听网络变化

    Android7.0前,Android系统前网络切换时,会发广播,业务只要监听广播即可. public class NetChangeReceiver extends BroadcastReceive ...

最新文章

  1. Caffe中计算图像均值的实现(cifar10)
  2. 一个不错的报表工具 open flash chart 2
  3. 【知识星球】每日干货看图猜技术,你都会吗?
  4. mfc中怎么集成文件_怎么把几个pdf合并并打印在一份文件中?
  5. spdk/dpdk 编译相关问题汇总
  6. 1499元!魅族Watch“天青”配色正式首销:与手机完全互联互通
  7. 人工智能导论 王万良教授_FCES2019 panel4:人工智能的第一堂课究竟讲什么?
  8. SAP License:值得一看的ERP问题
  9. python 同步event对象
  10. 励志c语言编码的开始,基础打开VS操作指南
  11. python转义字符\r的使用
  12. 高德api只显示省级地图
  13. 关于时间复杂度的详解
  14. iis7 下php 环境配置文件,Win7下在IIS7中配置PHP的环境
  15. goahead内嵌web——用户登录
  16. semantic navigation 目标驱动的视觉语义导航(一)
  17. 多多自走棋改动_多多自走棋:20日更新,刺客、光羽修改,装备小幅调整
  18. Unity for Windows: II – Publishing Unity games to Windows Store
  19. MATLAB数据保存
  20. Python爬虫实战:爬取股票信息

热门文章

  1. js 设置html标签样式表,js怎么设置css样式?
  2. web前端开发在线课程,前端校招面试题及解析大全
  3. 【3D建模制作技巧分享】ZBrush如何使用Z球
  4. 蜡烛图红色和绿色有什么区别?
  5. transformer--变形金刚
  6. 多特征融合的高分辨率遥感图像海陆分离——刘思彤
  7. 艾永亮:特仑苏有机奶的进阶之路,不断助力实现成为更好的自己
  8. Ubuntu EOS2.0.6币服务安装
  9. android软键盘显现,Android软键盘的显示和隐藏
  10. 漫谈IA32的系统管理模式(SMM)以及IA32的四种CPU模式