怎样绕开QQ通讯录和360的广播中断,在它们之前拿到短信?反编译其代码,发现了其中的奥秘。这要从广播特别是有序广播的分发机制说起,底层的代码还有待我们的研究。广播分为两种,无序广播和有序广播。可以理解为散列和队列广播。
首先无序广播,不能中断,分发机制有点类似散列发送。这种广播的发送为context.sendBroadcast。这种广播是不能中断的(不能阻止其向其他receiver发送),请看API说明。
其次为有序广播,可以中断。它是一个按一种优先级顺序发送,即按某种messagequere队列发送,其中队列中的任何广播接收者都可以中断该广播,导致它之后的成员不能接收到广播。
分析:
如果我们的优先级设置为最大int形整数是能够排在最前面的。优先级设置最高的会排在前面,如果优先级相同则按默认的排序不动,那么现在默认的排序是怎么样的呢?我们发现大部分广播发送者都是只传送一个action,所以我们关注:
if (resolvedType == null && scheme == null && intent.getAction() != null) {
firstTypeCut = mActionToFilter.get(intent.getAction());
if (debug) log.v(TAG, "Action list: " + firstTypeCut);
}
mActionToFilter内部list的排序决定了同样优先级情况下的排序问题,首先我们跟踪比较容易跟踪的。
1、动态注册,AMS中的registerReceiver方法中有对变量mReceiverResolver的操作如下:
BroadcastFilter bf = new BroadcastFilter(filter, rl, permission);
rl.add(bf);
if (!bf.debugCheck()) {
log.w(TAG, "==> For Dynamic broadast");
}
mReceiverResolver.addFilter(bf);
这里验证了同样优先级的情况下先动态注册的排在后来动态注册的广播接收者的前面。
2、静态注册。我们首先跟踪到PackageManagerService中的queryIntentReceivers方法ComponentNamecomp = intent.getComponent();一般为空。因为广播一般不会发送给某个指定的包,所以最终一般会调用:
if (pkgName == null) {
return (List<ResolveInfo>)mReceivers.queryIntent(intent, resolvedType, flags);
}
这里会调用到IntentResolver的public List<R> queryIntent(Intent intent, String resolvedType,boolean defaultOnly) 方法。这里同样是找mActionToFilter中的值,说明这个对象里面可以动态赋值给它(动态注册AMS中的registerReceiver方法中有对变量mReceiverResolver)也可以静态赋值,那么我们现在关注的就是mf.xml中的赋值顺序。但是到这里跟踪不下去了,感觉是安装的时候packageManageservice做了某种特殊的设置,adb install和adb push也做了不同的操作。
3、继续回来看AMS的broadcastIntentLocked方法
if (!ordered && NR > 0) {
如果不是队列广播这里会先立即给动态注册的广播接收者发送消息。并且这里还说明了一个问题:就是非ordered的广播基本都是动态注册优先接收到信息,无论动态注册的优先级多低(有的情况也不是这样的,具体见源代码)。
if (receivers != null) {
这里只要有静态注册的一般不为空。
int NT = receivers != null ? receivers.size() : 0;
int it = 0;
ResolveInfo curt = null;
BroadcastFilter curr = null;
while (it < NT && ir < NR) {
if (curt == null) {
curt = (ResolveInfo) receivers.get(it);
}
if (curr == null) {
curr = registeredReceivers.get(ir);
}
if (curr.getPriority() >= curt.priority) {
// Insert this broadcast record into the final list.
receivers.add(it, curr);
ir++;
curr = null;
it++;
NT++;
} else {
// Skip to the next ResolveInfo in the final list.
it++;
curt = null;
}
}
这一段仅仅是把动态注册的优先级一样的广播加在了静态注册的前面,这里完全验证了动态注册优先静态注册。代码后面的就是发送广播了。
实现:我们首先看这个队列是怎么形成的。假设我们广播优先级别都设成2147483647(注:最大int形整数)。首先动态注册优先级别最高,其次静态注册。在动态注册中,最早动态注册优先级别最高。在静态注册中,最早安装的程序,静态注册优先级别最高(注:安装APK会解析mf.xml,把其加入队列)。注意这里安装是指adb install xxx.apk或者手机上安装应用。
反编译源码会发现,其首先静态注册的广播接收器里面设置的优先数量级为2147483647,然后在广播中启动一个service,在service中注册了一个优先数量级为2147483647的同样的广播接收器。就这么简单就实现了没有同种实现的应用的情况下拦截短信(动态注册不会一直运行,程序退出了就收不到广播了,静态注册可以一直接收)。
首先写一个应用,注册一个开机完成广播接收者。<receiver
android:name=".MyBrocast"
android:permission="android.permission.BROADCAST_SMS" >
<intent-filter android:priority="2147483647" >
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
<intent-filter android:priority="2147483647" >
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
然后在这个广播接收者接收到信息之后马上启动一个service
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)){
Intent service=new Intent(context, MyService.class);
context.startService(service);
}
}
然后在service重新注册一个接收短信的广播接收者,优先级2147483647
IntentFilter localIntentFilter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
localIntentFilter.setPriority(2147483647);
MyBrocast localMessageReceiver = new MyBrocast();
Intent localIntent = registerReceiver(localMessageReceiver, localIntentFilter);
代价是应用需要一直有这么一个service后台运行。一旦被杀,优先权又回被抢占,只有等到下次重启。除非应用在其他软件之前安装到手机上。
总结一下:
具体的顺序:代码动态注册的Intent-Filter高于manifest静态注册的Intent-Filter。动态注册中的Intent-Filter在相同优先级下(如整型的最大值),接受顺序是按照动态注册的时间顺序。静态注册中Intent-Filter在相同优先级下,接受顺序是apk的安装顺序。
参考帖子:http://www.eoeandroid.com/forum.php?mod=viewthread&tid=148381