在Android中,BroadcastReceiver分动态注册和静态注册. 静态注册的一个优势就是:当你的BroadcastReceiver可以接受系统中

某个broadcast时,系统会自动启动你的程序,从而让BroadcastReceiver完成相关处理; 而动态注册则只有在程序运行时且没有

unregisterReceiver才能接收到broadcast.

此时,假设我们要在系统开机后,要对SIM卡联系人进行读取操作,那么我们应该如何注册自己的BroadcastReceiver呢?

方案一:

通过静态注册receiver来处理ACTION_SIM_STATE_CHANGED的broadcast,当icc state为LOADED时,读取SIM卡联系人.

这是一种比较常规的做法. 但是这个方案有一个比较严重的问题,那就是接收到broadcast的时机太晚了。结果就是,可能开机几

分钟过去了,SIM卡联系人却还没加载出来。

通过查看IccCardProxy中broadcastIccStateChangedIntent()函数的代码,我们发现,它发送的就是一个sticky broadcast

  1. ActivityManagerNative.broadcastStickyIntent(intent, READ_PHONE_STATE, UserHandle.USER_ALL)

按照常理来推断,一个sticky的broadcast不应该需要耗时这么久的。

那问题究竟出在什么地方了呢?

在调查这个问题之前,我们先简单看一下,静态注册的receiver和动态注册的receiver是如何被管理的呢?

静态注册receiver:

简单讲,系统启动时,创建PackageManagerService对象,简称PMS,然后通过scanDirLI函数对各个路径进行扫描,保存receiver等等

main[] // PackageManagerService.java
    PackageManagerService()
        scanDirLI()
            scanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime, UserHandle user)
                parsePackage() // PackageParser.java
                // scanPackageLI(PackageParser...)调用scanPackageDirtyLI来进一步处理parsePackage()生成的PackageParser.Package对象pkg
                // scanPackageDirtyLI将pkg中的receiver添加到PMS的mReceivers中(具体代码:mReceivers.addActivity(a, "receiver")),
                // 并将pkg添加到PMS的mPackages中(具体代码:mPackages.put(pkg.applicationInfo.packageName, pkg))
                scanPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, long currentTime, UserHandle user)  // PackageManagerService.java
                    parseBaseApk(File apkFile, AssetManager assets, int flags) // PackageParser.java
                        parseBaseApk(Resources res, XmlResourceParser parser, int flags, String[] outError)
                            parseBaseApplication() // // 将生成的receiver放到receivers中

  1. } else if (tagName.equals("receiver")) {
  2. Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
  3. if (a == null) {
  4. mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
  5. return false;
  6. }
  7.  
  8. owner.receivers.add(a);
  9.  
  10. } else if (tagName.equals("service")) {

通过上面的简单分析,我们可以发现所有静态注册的receiver是通过PMS进行管理的.

动态注册的receiver:

相比静态注册,动态注册的流程要简单许多.

registerReceiver(BroadcastReceiver receiver, IntentFilter filter) // ContextImpl.java
    registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)
        registerReceiverInternal()
            registerReceiver() // AMS

在AMS的registerReceiver函数中,receiver和broadcast filter相关信息被放到了mRegisteredReceivers和mReceiverResolver中.

了解了静态注册和动态注册receiver在PMS和AMS中的大致流程后,再来看下IccCardProxy发送sticky broadcast时AMS的大概流程.

broadcastStickyIntent(intent, READ_PHONE_STATE, UserHandle.USER_ALL) // ActivityManagerNative.java
    broadcastIntent() // ActivityManagerService.java 简称AMS
        broadcastIntentLocked()

下面贴一下broadcastIntentLocked中比较重要的处理

  1. private final int broadcastIntentLocked(ProcessRecord callerApp,
  2. String callerPackage, Intent intent, String resolvedType,
  3. IIntentReceiver resultTo, int resultCode, String resultData,
  4. Bundle map, String requiredPermission, int appOp,
  5. boolean ordered, boolean sticky, int callingPid, int callingUid,
  6. int userId) {
  7. intent = new Intent(intent);
  8.  
  9. // By default broadcasts do not go to stopped apps.
  10. intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
  11.  
  12. ......
  13.  
  14. userId = handleIncomingUser(callingPid, callingUid, userId,
  15. true, ALLOW_NON_FULL, "broadcast", callerPackage);
  16. ......
  17. /*
  18. * Prevent non-system code (defined here to be non-persistent
  19. * processes) from sending protected broadcasts.
  20. */
  21. int callingAppId = UserHandle.getAppId(callingUid);
  22.  
  23. ......
  24.  
  25. // Figure out who all will receive this broadcast.
  26. List receivers = null; // PMS中的结果
  27. List<BroadcastFilter> registeredReceivers = null; // AMS中的结果
  28. // Need to resolve the intent to interested receivers...
  29. if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
  30. == 0) {
  31. // 向PMS查询符合条件的receiver
  32. receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
  33. }
  34. if (intent.getComponent() == null) {
  35. if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) {
  36. ......
  37. } else {
                // 向AMS查询符合条件的receiver
  38. registeredReceivers = mReceiverResolver.queryIntent(intent,
  39. resolvedType, false, userId);
  40. }
  41. }
  42.  
  43. final boolean replacePending =
  44. (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
  45.  
  46. ......
  47.  
  48. // 注意,这里用的是从AMS中查询出来的符合条件的receiver
  49. int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
  50. if (!ordered && NR > 0) {
  51. // If we are not serializing this broadcast, then send the
  52. // registered receivers separately so they don't wait for the
  53. // components to be launched.
  54. final BroadcastQueue queue = broadcastQueueForIntent(intent);
  55. BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
  56. callerPackage, callingPid, callingUid, resolvedType, requiredPermission,
  57. appOp, registeredReceivers, resultTo, resultCode, resultData, map,
  58. ordered, sticky, false, userId);
  59. if (DEBUG_BROADCAST) Slog.v(
  60. TAG, "Enqueueing parallel broadcast " + r);
  61. final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
  62. if (!replaced) {
  63. queue.enqueueParallelBroadcastLocked(r);
  64. queue.scheduleBroadcastsLocked();
  65. }
  66. registeredReceivers = null;
  67. NR = 0;
  68. }
  69.  
  70. ......
  71.  
  72. if ((receivers != null && receivers.size() > 0)
  73. || resultTo != null) {
  74. BroadcastQueue queue = broadcastQueueForIntent(intent);
  75. BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
  76. callerPackage, callingPid, callingUid, resolvedType,
  77. requiredPermission, appOp, receivers, resultTo, resultCode,
  78. resultData, map, ordered, sticky, false, userId);
  79. if (DEBUG_BROADCAST) Slog.v(
  80. TAG, "Enqueueing ordered broadcast " + r
  81. + ": prev had " + queue.mOrderedBroadcasts.size());
  82. if (DEBUG_BROADCAST) {
  83. int seq = r.intent.getIntExtra("seq", -1);
  84. Slog.i(TAG, "Enqueueing broadcast " + r.intent.getAction() + " seq=" + seq);
  85. }
  86. boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);
  87.  
  88. if (!replaced) {
  89. // 注意,被放到ordered broadcast中了!
  90. queue.enqueueOrderedBroadcastLocked(r);
  91. queue.scheduleBroadcastsLocked();
  92. }
  93. }
  94.  
  95. return ActivityManager.BROADCAST_SUCCESS;
  96. }

collectReceiverComponents()和mReceiverResolver.queryIntent()是broadcastIntentLocked()中两个重要的函数调用.分别初始化了broadcastIntentLocked()中的receivers和registeredReceivers变量.

collectReceiverComponents()基本是通过调用AppGlobals.getPackageManager().queryIntentReceivers()来查询符合条件的receiver,

也就是PMS的queryIntentReceivers()函数进行查询,进而通过PMS中mReceivers变量的queryIntent来执行查询操作.

可见,collectReceiverComponents()最终查询到的是静态注册的符合条件的receiver.

再来简单看下mReceiverResolver.queryIntent()方法的查询结果. 我们在执行AMS的registerReceiver()时,将broadcast以及broadcast filter相关的

信息放到了mReceiverResolver中,因此,这里查询到的是所有符合条件的动态注册的receiver.

最终,符合条件的静态注册的receiver被保存在broadcastIntentLocked函数中receivers变量中;符合条件的动态注册的receiver被保存在registeredReceivers变量中。

在接下来enqueueParallelBroadcastLocked时,却只使用了registeredReceivers,即动态注册的receiver. 而静态注册的receiver却被enqueueOrderedBroadcastLocked

放到了ordered broadcast队列中!

下面再来简单看下BroadcastQueue对broadcast的处理流程:

scheduleBroadcastsLocked()
    handleMessage() // BroadcastQueue$BroadcastHandler
        processNextBroadcast()
            deliverToRegisteredReceiverLocked() // non-ordered
                performReceiveLocked()
                    scheduleRegisteredReceiver() // ActivityThread$ApplicationThread
                        performReceive() // LoadedApk$ReceiverDispatcher
                        mActivityThread.post(args)
                            run() // LoadedApk$Args
                                receiver.onReceive(mContext, intent) // 执行BroadcastReceiver的onReceive方法
            processCurBroadcastLocked() // ordered
                scheduleReceiver() // ActivityThread$ApplicationThread
                    handleMessage() // ActivityThread$H
                        handleReceiver() // ActivityThread
                            receiver.onReceive(context.getReceiverRestrictedContext(), data.intent) // 执行BroadcastReceiver的onReceive方法

由于ordered broadcast是一条一条来处理,也就不难发现为什么我们静态注册的接收ACTION_SIM_STATE_CHANGED的broadcast receiver很晚才能被启动了.

既然静态注册的receiver只能接受ordered broadcast后,如果想提升接收broadcast的速度,那我们只能使用动态注册receiver了。也就引出了我们的第二个方案.

方案二:

通过自定义Application并在自定义Application中动态注册receiver来处理ACTION_SIM_STATE_CHANGED的broadcast,在AndroidManifest.xml中声明时为自定义

的application添加android:persistent="true"属性. 这样就可以大幅提升接收ACTION_SIM_STATE_CHANGED的速度,但这是为什么呢?

在AMS的systemReady()中将通过PMS的getPersistentApplications()获得所有persistent属性为true的程序,并将他们启动起来. 即在进入launcher之前就被启动了.

而我们在自定义的Application的onCreate()中注册receiver, 来接收ACTION_SIM_STATE_CHANGED的broadcast,我们的receiver将被AMS管理,进而我们就可以更

快的接收到broadcast了(参考上文对AMS中broadcastIntentLocked()的分析). 但是这个方案也不是完美的,因为它声明了persistent=“true”,会占用内存以及增加开机时间.

自定义Application的代码:

  1. public class MyApp extends Application {
  2.  
  3. private BroadcastReceiver mReceiver = new BroadcastReceiver() {
  4.  
  5. @Override
  6. public void onReceive(Context context, Intent intent) {
  7. // TODO
  8. }
  9. };
  10.  
  11. @Override
  12. public void onCreate() {
  13. super.onCreate();
  14. IntentFilter intentFilter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
  15. intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
  16. registerReceiver(mReceiver, intentFilter);
  17. }
  18.  
  19. }

在AndroidManifest中的声明:

  1. <application
  2. android:name="MyApp"
  3. android:label="@string/app_name"
  4. android:persistent="true">
  5. ...
  6. </application>

不止ACTION_SIM_STATE_CHANGED,其它的broadcast receiver也可以采用方案二的做法来更快速的接收broadcast,就看自己有没有这个需要了.

写的比较仓促,有纰漏之处,敬请批评指正!

如何提升ACTION_SIM_STATE_CHANGED的接收速度?的更多相关文章

  1. 【Android优化篇】提升Activity加载速度的方法

    文章转自:http://www.jianshu.com/p/2007ca0290d3 作者: CoderFan 前言 这个也是我面试遇到的问题,当时只回答了一种情况,异步加载数据,没想到别的方式,回来 ...

  2. webpack 提升90%的构建速度 HardSourceWebpackPlugin

    HardSourceWebpackPlugin 插件 不能提升第一次构建的速度,但对于第二次构建能提升99%的构建速度 第一次构建: 第二次: 提升了..,算不出来,反正就是很多啦~~~ npm in ...

  3. Nginx开启Gzip压缩提升页面加载速度

    1.在 nginx 的conf 目录下新建 gzip.conf 文件 #开启gzip压缩 gzip on; #设置允许压缩的页面最小字节数 gzip_min_length 1k; #申请4个单位为16 ...

  4. 优化JavaScripe 提升首页加载速度的几种方案解析

    优化目的: 1. 减少load量. 2. 优化js,加快页面加载速度. 网站中最影响网站打开速度的是什么?我会告诉是网站中的javascript,简称JS.模板中引用的JS文件越多,打开速度越慢,细读 ...

  5. Android提升进入界面的速度

    应用除了有内存占用.内存泄露.内存抖动等看不见的性能问题外,还有很多看得见的性能问题,比如进入界面慢.点击反应慢.页面卡顿等等,这些看得见的体验问题会严重影响用户使用APP心情,但用户的情绪又无法通过 ...

  6. SSE再学习:灵活运用SIMD指令6倍提升Sobel边缘检测的速度(4000*3000的24位图像时间由180ms降低到30ms)。

    这半年多时间,基本都在折腾一些基本的优化,有很多都是十几年前的技术了,从随大流的角度来考虑,研究这些东西在很多人看来是浪费时间了,即不能赚钱,也对工作能力提升无啥帮助.可我觉得人类所谓的幸福,可以分为 ...

  7. SSE图像算法优化系列九:灵活运用SIMD指令16倍提升Sobel边缘检测的速度(4000*3000的24位图像时间由480ms降低到30ms)。

    这半年多时间,基本都在折腾一些基本的优化,有很多都是十几年前的技术了,从随大流的角度来考虑,研究这些东西在很多人看来是浪费时间了,即不能赚钱,也对工作能力提升无啥帮助.可我觉得人类所谓的幸福,可以分为 ...

  8. 使用Qt 3D Studio 2.4显着提升性能(渲染速度提高了565%)

    发布于2019年6月18日星期二11评论Qt 3D Studio 2.4显着改善性能 发表于Biz Circuit&Dev Loop,设计,图形,性能,Qt 3D Studio 除了有效使用系 ...

  9. Blazor 发布WebAssembly使用Brotli 压缩提升初次加载速度

    使用Brotli提高网站访问速度 在优化网站打开速度上,我们有很多的方法,而其中一个就是减少诸如Javascript和CSS等资源文件的大小,而减少文件大小的方法除了在代码上下功夫外,最常用的方法就是 ...

随机推荐

  1. Source not found :Edit Source Lookup Path 解决方案

    作者原创,转载请注明转载地址 在eclipse中用debug调试的时候,出现了以下问题,很是尴尬,经常碰到,所以有必要进行总结一下: 对该问题有两种解决方案, 一种比较文明:解决方法可参考如下网址: ...

  2. 08_Flume_Selector实践

    实践一:replicating selector 1.目标场景 selector将event复制,分发给所有下游节点 2.Flume Agent配置 Agent配置 # Name the compon ...

  3. AtCoder square869120 Contest #3 F sushi

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

  4. Cocos2d-x学习笔记(九)场景切换

    这里只介绍两个简单的两个场景相互切换的方法. void pushScene(Scene *sn)和void popScene(Scene *sn)是一对.前者将当前场景放入到场景堆栈中,并切换到下一个 ...

  5. Python中通过csv的writerow输出的内容有多余的空行

    第一种方法 如下生成的csv文件会有多个空行 import csv #python2可以用file替代open with open("test.csv","w" ...

  6. go变量赋初值

    package main import "fmt" func variableinitial(){ , var s string="abc" fmt.Print ...

  7. python 删除列表中的字典元素

    data={"} ,{"}]} print("before:\n\n",data,"\n\n") temp=- k= for i in da ...

  8. Mutex, semaphore, spinlock

    Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个.一般的用法是用于串行化对critical section代码的访问,保证这段代码不会被并行的运行. Semaphor ...

  9. Thunder团队Final版爱阅app发布视频

    视频链接:https://www.bilibili.com/video/av17008792/ 视频简介:首先出现的是我们团队的logo,接着是Final版爱阅app的功能展示,紧接着是我们团队的开发 ...

  10. Android AlarmManager 的使用

    AlarmManager简介及使用场景: AlarmManager的使用机制有的称呼为全局定时器,有的称呼为闹钟.通过对它的使用,它的作用和Timer有点相似. 都有两种相似的用法: (1).在指定时 ...