文章目录

  • 广播
  • 基本概念
  • 发送广播
    • plantuml
    • sendStickyBroadcast
      • 声明
      • 流程图
      • 流程中关键代码
        • userid uid appid 多用户
        • 数据结构
  • stickyBroadcast使用
    • 源代码
    • 运行结果
    • 解惑
  • Frida动态获取sticky广播
  • 写在最后
  • 公众号

广播

作为Android的四大组件之一,广播的用途还是非常广泛的。广播是一种同时通知多个对象的事件通知机制,顾名思义也能大概知道是这个意思,类似日常生活中的大喇叭广播,多个人可以收听,人们大都只关心和自己有关的事情,而对和自己无关的事情进行屏蔽,Android中的广播和这个差不多。

基本概念

普通广播:(略)
有序广播:(略)
粘性广播:只要不移除,就一直在内存当中。本文主要说这种广播,同时介绍一个Google的一个"bug",不知google是有意的还是无意而引起的这个bug。粘性广播是Depracated,Google已经不建议使用,存在安全隐患。
静态注册:(略)
动态注册:(略)

发送广播

下图是是广播发送时序图(比较粗略,不过通过这个图能足够梳理发送广播的流程)。本文基于的是Android11。关于怎么画时序图,每个人都有每个人的喜好工具,笔者比较喜欢开源工具plantuml.

plantuml

官方文档: https://plantuml.com/zh/sequence-diagram。
在Android启动流程中的图就是使用plantuml来绘制的。下图是createVirtualDisplay的流程图,plantuml的代码如下,基本上常用的功能下面的代码都有涉及。

@startuml
WFDSession -> DisplayManager: createVirtualDisplay
note left :Surface from native method
DisplayManager ->DisplayManagerGlobal:createVirtualDisplay
activate DisplayManagerGlobal
DisplayManagerGlobal -> DisplayManagerService: createVirtualDisplay
DisplayManagerService -> DisplayManagerService: createVirtualDisplayInternalactivate DisplayManagerServiceDisplayManagerService ->  VirtualDisplayAdapter: createVirtualDisplayLockedVirtualDisplayAdapter --> DisplayManagerService: DisplayDevice deviceDisplayManagerService ->DisplayManagerService:   handleDisplayDeviceAddedLockedactivate DisplayManagerService #DarkSalmonDisplayManagerService ->DisplayManagerService: addLogicalDisplayLocked(device);activate DisplayManagerService #00fa9aDisplayManagerService -> DisplayManagerService:assignDisplayIdLockednote left: displayIdactivate DisplayManagerService #AB82FFdeactivateDisplayManagerService -> DisplayManagerService:assignLayerStackLocked(displayId)note left:layerStackactivate DisplayManagerService #AB82FFdeactivateDisplayManagerService -> LogicalDisplay: new LogicalDisplayLogicalDisplay --> DisplayManagerService:LogicalDisplay displayDisplayManagerService ->DisplayManagerService: configureColorModeLockedactivate DisplayManagerService #AB82FFdeactivateDisplayManagerService ->DisplayManagerService: sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);activate DisplayManagerService #AB82FFdeactivatedeactivateDisplayManagerService -> DisplayManagerService:updateDisplayStateLockedactivate DisplayManagerService #AB82FFdeactivateDisplayManagerService -> DisplayManagerService:scheduleTraversalLocked(false)activate DisplayManagerService #AB82FFdeactivatedeactivateDisplayManagerService -> DisplayManagerService:findLogicalDisplayForDeviceLocked(device)activate DisplayManagerService #AB82FFdeactivateDisplayManagerService --> DisplayManagerGlobal:display.getDisplayIdLocked()deactivate
DisplayManagerGlobal -> DisplayManagerGlobal:getRealDisplay(displayId)activate DisplayManagerGlobal #AB82FFdeactivate
DisplayManagerGlobal --> DisplayManager: new VirtualDisplay
deactivate
DisplayManager --> WFDSession: VirtualDisplay mVirtualDisplay
@enduml

效果:

sendStickyBroadcast

声明

通过下面的声明可知要使用sendStickyBroadcast,需要android.Manifest.permission.BROADCAST_STICKY权限. 关于Android的权限后续文章会专门介绍.

 @Deprecated@RequiresPermission(android.Manifest.permission.BROADCAST_STICKY)public abstract void sendStickyBroadcast(@RequiresPermission Intent intent);
流程图

sendStickyBroadcast的主要流程如下所示,后面的部分和发送正常的广播流程一致.
流程涉及到aidl, android中实现进程通信的一种最重要的机制。后面会开专题来讲解aidl.

流程中关键代码

判断caller是否是System.

final boolean isCallerSystem;
switch (UserHandle.getAppId(callingUid)) {case ROOT_UID:case SYSTEM_UID:case PHONE_UID:case BLUETOOTH_UID:case NFC_UID:case SE_UID:case NETWORK_STACK_UID:isCallerSystem = true;break;default:isCallerSystem = (callerApp != null) && callerApp.isPersistent();break;
}

Intent->filterEquals的实现:

public boolean filterEquals(Intent other) {if (other == null) {return false;}if (!Objects.equals(this.mAction, other.mAction)) return false;if (!Objects.equals(this.mData, other.mData)) return false;if (!Objects.equals(this.mType, other.mType)) return false;if (!Objects.equals(this.mIdentifier, other.mIdentifier)) return false;if (!(this.hasPackageEquivalentComponent() && other.hasPackageEquivalentComponent())&& !Objects.equals(this.mPackage, other.mPackage)) {return false;}if (!Objects.equals(this.mComponent, other.mComponent)) return false;if (!Objects.equals(this.mCategories, other.mCategories)) return false;return true;
}
userid uid appid 多用户

userid:就是有多少个实际的用户罗,例如老爸很穷,要跟儿子共用一台手机,那可以跟手机将两个用户,user 0和 user 1。两个用户的应用和数据是独立的。
uid:跟应用进程相关。除了 sharduid的应用,每个用户的每个应用的 uid不一样的。用户 0的应用的uid从一万开始算。

appid:跟 app相关,包名相同的 appid都一样。即使是不同用户。例如你和儿子都在这台手机装了微信,但这两个微信的appid是一样的。uid与 userId存在一种计算关系( uid = userId * 1000000 + appId)

多用户其实是系统为应用的 data目录和 storage目录分配了一份不同且独立的存储空间,不同用户下的存储空间互不影响且没有权限访问。同时,系统中的AMS、 PMS、 WMS等各大服务都会针对userId/UserHandle进行多用户适配,并在用户启动、切换、停止、删除等生命周期时做出相应策略的改变。通过以上两点,Android创造出来一个虚拟的多用户运行环境.

数据结构

SparseArray: Android 在 Android SdK 为我们提供的一个基础的数据结构,其功能类似于 HashMap。与 HashMap 不同的是它的 Key 只能是 int 值,不能是其他的类型。

    /*** State of all active sticky broadcasts per user.  Keys are the action of the* sticky Intent, values are an ArrayList of all broadcasted intents with* that action (which should usually be one).  The SparseArray is keyed* by the user ID the sticky is for, and can include UserHandle.USER_ALL* for stickies that are sent to all users.*/final SparseArray<ArrayMap<String, ArrayList<Intent>>> mStickyBroadcasts =new SparseArray<ArrayMap<String, ArrayList<Intent>>>();

ArrayMap: ArrayMap是一种通用的key-value映射的数据结构,旨在提高内存效率,它与传统的HashMap有很大的不同。它将其映射保留在数组数据结构中:两个数组(其中一个存放每个item的hash值的整数数组,以及key/value对的Object数组)。这避免了它为放入映射的每个item创建额外的对象,并且它还积极地控制这些数组的增长。数组的增长只需要复制数组中的item,而不是重建hash映射。

26/**
27 * ArrayMap is a generic key->value mapping data structure that is
28 * designed to be more memory efficient than a traditional {@link java.util.HashMap}.
29 * It keeps its mappings in an array data structure -- an integer array of hash
30 * codes for each item, and an Object array of the key/value pairs.  This allows it to
31 * avoid having to create an extra object for every entry put in to the map, and it
32 * also tries to control the growth of the size of these arrays more aggressively
33 * (since growing them only requires copying the entries in the array, not rebuilding
34 * a hash map).
35 *
36 * <p>Note that this implementation is not intended to be appropriate for data structures
37 * that may contain large numbers of items.  It is generally slower than a traditional
38 * HashMap, since lookups require a binary search and adds and removes require inserting
39 * and deleting entries in the array.  For containers holding up to hundreds of items,
40 * the performance difference is not significant, less than 50%.</p>
41 *
42 * <p>Because this container is intended to better balance memory use, unlike most other
43 * standard Java containers it will shrink its array as items are removed from it.  Currently
44 * you have no control over this shrinking -- if you set a capacity and then remove an
45 * item, it may reduce the capacity to better match the current size.  In the future an
46 * explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
47 */
48public final class ArrayMap<K, V> implements Map<K, V> {

stickyBroadcast使用

源代码

AndroidManifest.xml 需要添加如下权限:

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

MainActivity.java:
通过分析前面的intentFilter方法可知,当包不一致的时候,尽管action是一致的,但是intent是不同的。

    @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);password_et = (EditText) this.findViewById(R.id.password);username_et = (EditText) this.findViewById(R.id.username);message_tv = ((TextView) findViewById(R.id.textView));registerReceiver(new MyReceiver(), new IntentFilter("send"));this.findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent();intent.setAction("send");sendStickyBroadcast(intent);//注意这里,设置了package,这样两个intent是不一样的intent.setPackage("com.android.systemui");sendStickyBroadcast(intent);}});}

MyReceiver.java

public class MyReceiver extends BroadcastReceiver {private static final String TAG = MyReceiver.class.getSimpleName();@Overridepublic void onReceive(Context context, Intent intent) {Log.d(TAG,"burning "+ intent.getPackage() + ":"+intent.getAction());//移除广播context.removeStickyBroadcast(intent);}
}

运行结果

第一次运行application,点击登陆:

12-28 21:33:01.153 14806 14806 D MyReceiver: burning null:send

关闭程序,再次运行application:
(1)在没有点击登陆的时候,可以看到下面的信息,居然接收到包名是com.android.systemui的intent。

这个一方面证明了sticky broadcast在内存中一直存在并且可以在不同的包之间使用,这个很明显是存在安全隐患的。同时证明了removeStickyBroadcast移除的是没有设置package的intent. 显然如果removeStickyBroadcast使用不当,会造成令人迷惑的结果。

12-28 21:34:55.649 15394 15394 D MyReceiver: burning com.android.systemui:send

(2)点击登陆,可以看到下面的输出:

12-28 21:35:00.801 15394 15394 D MyReceiver: burning null:send

解惑

removeStickyBroadcast最终会调用AMS中的unbroadcastIntent:

    public final void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {// Refuse possible leaked file descriptorsif (intent != null && intent.hasFileDescriptors() == true) {throw new IllegalArgumentException("File descriptors passed in Intent");}userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);synchronized(this) {if (checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)!= PackageManager.PERMISSION_GRANTED) {String msg = "Permission Denial: unbroadcastIntent() from pid="+ Binder.getCallingPid()+ ", uid=" + Binder.getCallingUid()+ " requires " + android.Manifest.permission.BROADCAST_STICKY;Slog.w(TAG, msg);throw new SecurityException(msg);}ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);if (stickies != null) {ArrayList<Intent> list = stickies.get(intent.getAction());if (list != null) {int N = list.size();int i;for (i=0; i<N; i++) {if (intent.filterEquals(list.get(i))) {list.remove(i);break;}}if (list.size() <= 0) {stickies.remove(intent.getAction());}}if (stickies.size() <= 0) {mStickyBroadcasts.remove(userId);}}}}

registerReceiver实现:

    public Intent registerReceiver(IApplicationThread caller, String callerPackage,IIntentReceiver receiver, IntentFilter filter, String permission, int userId,int flags) {. . . 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);// Don't provided intents that aren't available to instant apps.if (instantApp &&(intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {continue;}// 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;}. . . return sticky;}}

返回值是:

Intent sticky = allSticky != null ? allSticky.get(0) : null;

显然,第二次启动application之后,在onCreate方法中调用了registerReceiver方法,方相应action对应的intent是设置package的(因为只有一个),所以会出现下面的日志

12-28 21:34:55.649 15394 15394 D MyReceiver: burning com.android.systemui:send

但是,当点击登陆的时候,为什么不是设置package的intent,而是没有设置package的intent。

12-28 21:35:00.801 15394 15394 D MyReceiver: burning null:send

AMS中部分输出日志:

11-04 13:08:28.976  1672 15703 W ActivityManager: burning broadcastIntentLocked sticy true
11-04 13:08:28.977  1672 15703 W ActivityManager: burning broadcastIntentLocked sticy enter
11-04 13:08:28.977  1672 15703 W ActivityManager: burning broadcastIntentLocked stickies  UserHandle.USER_ALLtrue
11-04 13:08:28.977  1672 15703 W ActivityManager: burning broadcastIntentLocked stickies  userId0 true
11-04 13:08:28.977  1672 15703 W ActivityManager: burning broadcastIntentLocked stickies  stickiesCount1
11-04 13:08:28.977  1672 15703 W ActivityManager: burning broadcastIntentLocked stickies  stickiesCount filterIntent { act=send flg=0x10 pkg=com.android.systemui }
11-04 13:08:28.978  1672 15703 W ActivityManager: burning broadcastIntentLocked sticy true
11-04 13:08:28.978  1672 15703 W ActivityManager: burning broadcastIntentLocked sticy enter
11-04 13:08:28.978  1672 15703 W ActivityManager: burning broadcastIntentLocked stickies  UserHandle.USER_ALLtrue
11-04 13:08:28.979  1672 15703 W ActivityManager: burning broadcastIntentLocked stickies  userId0 true
11-04 13:08:28.979  1672 15703 W ActivityManager: burning broadcastIntentLocked stickies  stickiesCount2
11-04 13:08:28.979  1672 15703 W ActivityManager: burning broadcastIntentLocked stickies  stickiesCount filterIntent { act=send flg=0x10 pkg=com.android.systemui }
11-04 13:08:28.984  6016  6016 D com.example.stickytest.MainActivity: burning null:send
11-04 13:08:28.985  1672 15703 W ActivityManager: burning unbroadcastIntent userId = 0intent Intent { act=send flg=0x10 }
11-04 13:08:28.985  1672 15703 W ActivityManager: burning unbroadcastIntent stickies N=2
11-04 13:08:28.985  1672 15703 W ActivityManager: burning unbroadcastIntent stickies Intent { act=send flg=0x10 pkg=com.android.systemui }
11-04 13:08:28.985  1672 15703 W ActivityManager: burning unbroadcastIntent stickies Intent { act=send flg=0x10 }
11-04 13:08:28.985  1672 15703 W ActivityManager: burning unbroadcastIntent stickies filterEqualsIntent { act=send flg=0x10 }
11-04 13:08:28.985  1672 15703 W ActivityManager: burning unbroadcastIntent userId = 0

通过上面的日志可以看出当前应用只接收了不带Package的intent,并且移除了不带package的intent的.因此可以推测带package的intent被相应的包接收了.

但是这样又有一个问题,第二次启动的时候又为何可以接收到带有package的intent?

 BroadcastRecord r = new BroadcastRecord(queue, intent, null,null, null, -1, -1, false, null, null, OP_NONE, null, receivers,null, 0, null, null, false, true, true, -1, false,false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */);
public void enqueueParallelBroadcastLocked(BroadcastRecord r) {mParallelBroadcasts.add(r);enqueueBroadcastHelper(r);
}
deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);
817                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
818                        new Intent(r.intent), r.resultCode, r.resultData,
819                        r.resultExtras, r.ordered, r.initialSticky, r.userId);
590                    app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
591                            data, extras, ordered, sticky, sendingUser, app.getReportedProcState());
1161        public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
1162                int resultCode, String dataStr, Bundle extras, boolean ordered,
1163                boolean sticky, int sendingUser, int processState) throws RemoteException {1164            updateProcessState(processState, false);
1165            receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
1166                    sticky, sendingUser);
1167        }

Frida动态获取sticky广播

Hook system_server进程。

写在最后

粘性广播尽量不要使用,一方面是安全问题,另一方面,容易产生令人迷惑的结果。

公众号

更多Android源码内容,欢迎关注我的微信公众号:无情剑客。

Android源码篇-深入理解粘性广播(1)相关推荐

  1. Android源码分析-全面理解Context

    前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...

  2. Android源码分析之理解Window和WindowManager

    Window和WindowManager概述 Window是一个抽象类,它的具体实现是PhoneWindow,创建一个Window通过WindowManager 就可以完成.WindowManager ...

  3. android 浏览器源码分析,从源码出发深入理解 Android Service

    原标题:从源码出发深入理解 Android Service 原文链接: 建议在浏览器上打开,删除了大量代码细节,:) 本文是 Android 系统学习系列文章中的第三章节的内容,介绍了 Android ...

  4. 《深入理解Android内核设计思想(第2版)(上下册)》之Android源码下载及编译

    本文摘自人民邮电出版社异步社区<深入理解Android内核设计思想(第2版)(上下册)> 购书地址:http://item.jd.com/12212640.html 试读地址:http:/ ...

  5. 《深入理解Android内核设计思想(第2版)(上下册)》之Android源码下载及编译...

    本文摘自人民邮电出版社异步社区<深入理解Android内核设计思想(第2版)(上下册)> 购书地址:item.jd.com/12212640.ht- 试读地址:www.epubit.com ...

  6. Android源码分析 - Framework层的Binder(客户端篇)

    开篇 本篇以aosp分支android-11.0.0_r25作为基础解析 我们在之前的文章中,从驱动层面分析了Binder是怎样工作的,但Binder驱动只涉及传输部分,待传输对象是怎么产生的呢,这就 ...

  7. Android Jetpack架构组件之 Room(使用、源码篇)

    2019独角兽企业重金招聘Python工程师标准>>> 1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发 ...

  8. Android源码解析(一)动画篇-- Animator属性动画系统

    Android源码解析-动画篇 Android源码解析(一)动画篇-- Animator属性动画系统 Android源码解析(二)动画篇-- ObjectAnimator Android在3.0版本中 ...

  9. android 使用4大组件的源码,Android Jetpack架构组件之 Paging(使用、源码篇)

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

最新文章

  1. 关于常见的底层驱动源码资料
  2. python自学流程-Python系统学习流程图,教你一步步学习python
  3. Spring boot日志关系
  4. apache目录 vscode_[PHP] php, apache, VS Code安装与配置
  5. .NET MYSQL数据库操作基类( C#源码)
  6. 基于 HTML5 WebGL 的 3D 服务器与客户端的通信
  7. oracle11g设置开机自启动,oracle11g在linux系统下开机自启动设置
  8. sql azure 语法_在Azure Data Studio中学习用于SQL Notebook的Markdown语言
  9. 语音识别技术突飞猛进
  10. bzoj 1237: [SCOI2008]配对(DP)
  11. 安装Labview2012 “labview 2012 未定义必须的 NIPathsDir属性 maxAFWDIR”
  12. 列表套字典三者匹配对应关系
  13. Vista 自动激活工具(最新 最权威 所有版本 可升级)
  14. ubuntu2004上使用python以及postgresql处理数据 - 针对comp3311
  15. 沿海当地平均海面与85面高程关系
  16. sql导出的身份证后几位是000
  17. 使用 Swift 在 iOS 10 中集成 Siri —— SiriKit 教程(Part 1) 1
  18. 利用Photoshop进行快速切图
  19. percona toolkit系列(gh-ost)
  20. 中文颜色名称与RGB颜色对照表

热门文章

  1. C# particle class
  2. 学计算机用什么cpu,做设计用什么cpu
  3. 中关村商城广告切换纯净代码
  4. 地理国情监测arcpy将文件夹多个gdb下的要素类featureClass,使用FeatureClassToShapefile_conversion转换成shapefile文件。
  5. PHP最新我约微博整站系统源码+功能非常多
  6. php写个发红包_使用PHP编写发红包程序
  7. 如何获取微信用户的Openid详解(微信网页授权)
  8. 大学生最应该考的十大最有价值证书
  9. 君子签用区块链打造电子合同证据链闭环,提升电子合同证据效力
  10. 摔跤吧爸爸-影评感悟(匍匐泥泞,不忘星空-目标)