Android 中的自启行为浅析 # Broadcast(二)

《Android 中的自启行为浅析#Broadcast(一)》中的 1、2 节讨论了广播发送与接收流程,我们接着来说一些系统广播和优先级接收的问题。

3. 一些重要的系统广播

本节我们来提几个重要的系统广播,同时来看广播接收的优先级问题。

3.1 开机自启 ACTION_BOOT_COMPLETE

开机自启广播是很多应用拉活的手段,以至于可能造成耗电甚至有安全隐患。开机自启的广播 Intent 的 action 是:

public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";

那么这个广播是谁在什么时候触发的呢?有多处都会触发,Android M 较之 Android 4.4 改变也挺大的,最后的调用基本没变,均为 ActivityManagerService.finishBooting(),不过一言以蔽之就是在手机刚启动时一些主要进程准备好后或者启动后连带着会通过AMS发送该广播,该广播只触发一次,自然时最早执行到的触发,举个例子默认Launcher启动后在主消息循环时触发,或者切换用户的时候也会触发(对于个人手机这个情况太少见了),在Android M 上 WMS 初始化好后也会触发等等。

final void finishBooting() {
    ......
    synchronized (this) {
        ......
        if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
            ......
            // Tell anyone interested that we are done booting!
            SystemProperties.set("sys.boot_completed", "1");
            SystemProperties.set("dev.bootcomplete", "1");
            for (int i=0; i<mStartedUsers.size(); i++) {
                UserStartedState uss = mStartedUsers.valueAt(i);
                if (uss.mState == UserStartedState.STATE_BOOTING) {
                    uss.mState = UserStartedState.STATE_RUNNING;
                    final int userId = mStartedUsers.keyAt(i);
                    Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
                    intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
*                   broadcastIntentLocked(null, null, intent, null,
                            new IIntentReceiver.Stub() {
                                @Override
                                public void performReceive(Intent intent, int resultCode,
                                        String data, Bundle extras, boolean ordered,
                                        boolean sticky, int sendingUser) {
                                    synchronized (ActivityManagerService.this) {
                                        requestPssAllProcsLocked(SystemClock.uptimeMillis(),
                                                true, false);
                                    }
                                }
                            },
                            0, null, null,
                            android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
                            AppOpsManager.OP_NONE, true, false, MY_PID, Process.SYSTEM_UID,
                            userId);
                }
            }
        }
    }
}

关键代码如上所示,可见开机自启广播需要权限 android.Manifest.permission.RECEIVE_BOOT_COMPLETED 但是这个权限谁都能申请,广播不可中断(abort),广播也一样受标志位 Intent.FLAG_EXCLUDE_STOPPED_PACKAGES 限制,对于普通应用如果没有启动过或者被明确停止过,自启广播是收不到的。

3.2 短信接收广播

4.4与4.3相比变化很大,在4.4上短信接收广播的发送方法是在 InboundSmsHandler 类中:

void dispatchIntent(Intent intent, String permission, int appOp,
        BroadcastReceiver resultReceiver) {
    intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
    mContext.sendOrderedBroadcast(intent, permission, appOp, resultReceiver,
            getHandler(), Activity.RESULT_OK, null, null);
}

你会发现从 4.4 开始,短信接收广播不再支持 abort 了,接收该广播需要声明权限 android.Manifest.permission.RECEIVE_SMS,且需要 AppOps 通过权限 AppOpsManager.OP_RECEIVE_SMS,本文不是在讲短信如何接收,于是点到为止了。

3.3 电话拨打广播

即 Intent.ACTION_NEW_OUTGOING_CALL,广播的发送是在 OutgoingCallBroadcaster.java 中:

private static final String PERMISSION = android.Manifest.permission.PROCESS_OUTGOING_CALLS;

private void processIntent(Intent intent) {
    ......
1.  Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
    if (number != null) {
        broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
    }
    CallGatewayManager.checkAndCopyPhoneProviderExtras(intent, broadcastIntent);
    broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow);
    broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString());
    // Need to raise foreground in-call UI as soon as possible while allowing 3rd party app
    // to intercept the outgoing call.
2.  broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
    if (DBG) Log.v(TAG, " - Broadcasting intent: " + broadcastIntent + ".");

    // Set a timer so that we can prepare for unexpected delay introduced by the broadcast.
    // If it takes too much time, the timer will show "waiting" spinner.
    // This message will be removed when OutgoingCallReceiver#onReceive() is called before the
    // timeout.
    mHandler.sendEmptyMessageDelayed(EVENT_OUTGOING_CALL_TIMEOUT,
            OUTGOING_CALL_TIMEOUT_THRESHOLD);
3.  sendOrderedBroadcastAsUser(broadcastIntent, UserHandle.OWNER,
            PERMISSION, new OutgoingCallReceiver(),
            null,  // scheduler
            Activity.RESULT_OK,  // initialCode
            number,  // initialData: initial value for the result data
            null);  // initialExtras
}

可以看出,接收电话拨打的广播需要权限 android.Manifest.permission.PROCESS_OUTGOING_CALLS,但没有 AppOps 的限制,该广播是在 mFgBroadcastQueue 队列中的,有更少的超时时间和间隔等待时间。

4 广播接收优先级

4.1 优先级计算结果不同的情况

从《Android 中的自启行为浅析#Broadcast(一)》1.1节步骤4中,第八步是在无序广播发送时,动态广播接收器优先插入广播队列进行处理,第十一步开始,对所有解析出的接收器(对于有序广播是所有接收器,对于无序广播只有静态接收器)进行排序的操作后再插入队列进行处理,回顾一下代码重点来看优先级部分:

private final int broadcastIntentLocked(ProcessRecord callerApp,
        String callerPackage, Intent intent, String resolvedType,
        IIntentReceiver resultTo, int resultCode, String resultData,
        Bundle map, String requiredPermission, int appOp,
        boolean ordered, boolean sticky, int callingPid, int callingUid,
        int userId) {
    ......
1.  List receivers = null; // 静态广播接收器
    List<BroadcastFilter> registeredReceivers = null; // 动态广播接收器
    // Need to resolve the intent to interested receivers...
    if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
             == 0) {
        receivers = collectReceiverComponents(intent, resolvedType, users);
    }
    if (intent.getComponent() == null) {
        registeredReceivers = mReceiverResolver.queryIntent(intent,
                resolvedType, false, userId);
    }
    ......
    
    int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
    if (!ordered && NR > 0) {
        ...
        final BroadcastQueue queue = broadcastQueueForIntent(intent);
        BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                callerPackage, callingPid, callingUid, resolvedType, requiredPermission,
                appOp, registeredReceivers, resultTo, resultCode, resultData, map,
                ordered, sticky, false, userId);
        ...
        final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
        if (!replaced) {
2.          queue.enqueueParallelBroadcastLocked(r);
            queue.scheduleBroadcastsLocked();
        }
        registeredReceivers = null;
        NR = 0;
    }

    // Merge into one list.
    int ir = 0;
    if (receivers != null) {
        ......

        int NT = receivers != null ? receivers.size() : 0;
        int it = 0;
        ResolveInfo curt = null;
        BroadcastFilter curr = null;
3.      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;
            }
        }
    }
4.  while (ir < NR) {
        if (receivers == null) {
            receivers = new ArrayList();
        }
        receivers.add(registeredReceivers.get(ir));
        ir++;
    }

    if ((receivers != null && receivers.size() > 0)
            || resultTo != null) {
        BroadcastQueue queue = broadcastQueueForIntent(intent);
        BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                callerPackage, callingPid, callingUid, resolvedType,
                requiredPermission, appOp, receivers, resultTo, resultCode,
                resultData, map, ordered, sticky, false, userId);
        ...
        boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); 
        if (!replaced) {
5.          queue.enqueueOrderedBroadcastLocked(r);
            queue.scheduleBroadcastsLocked();
        }
    }

    return ActivityManager.BROADCAST_SUCCESS;
}

步骤1:搜集匹配的静态和动态广播接收器,分别是靠 PMS 中的 "ActivityIntentResolver mReceivers;" 和 AMS 中的 " IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver;",都是 IntentResolver,再回顾一下《Android 中的自启行为浅析#Broadcast(一)》第二节提到的 queryIntent 函数:

public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
        int userId) {
    ...... //一大波逻辑

    FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
    if (firstTypeCut != null) {
        buildResolveList(intent, categories, debug, defaultOnly,
                resolvedType, scheme, firstTypeCut, finalList, userId);
    }
    if (secondTypeCut != null) {
        buildResolveList(intent, categories, debug, defaultOnly,
                resolvedType, scheme, secondTypeCut, finalList, userId);
    }
    if (thirdTypeCut != null) {
        buildResolveList(intent, categories, debug, defaultOnly,
                resolvedType, scheme, thirdTypeCut, finalList, userId);
    }
    if (schemeCut != null) {
        buildResolveList(intent, categories, debug, defaultOnly,
                resolvedType, scheme, schemeCut, finalList, userId);
    }
*   sortResults(finalList);

    ......
    return finalList;
}

注意带*这行代码,是对最终解析结果进行排序:

protected void sortResults(List<R> results) {
    Collections.sort(results, mResolvePrioritySorter);
}

mResolvePrioritySorter 是排序规则了:

private static final Comparator mResolvePrioritySorter = new Comparator() {
    public int compare(Object o1, Object o2) {
        final int q1 = ((IntentFilter) o1).getPriority();
        final int q2 = ((IntentFilter) o2).getPriority();
        return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0);
    }
};

没错,就是比较优先级,优先级高的在前,低的在后。
步骤2:如果是无序广播,动态广播接收器直接插入队列处理,队列里自然是优先级高的在前面了;
步骤3:注意NR这个变量,如果第2步是无序广播处理,NR会置为0,这一步就直接不再做合并排序了,即这一步只有有序广播的时候才会执行,是将动态广播接收器按优先级合并到静态广播接收器队列中去;
步骤4:变量 it 走到最后了动态广播接收器还有剩下的合并,就一并将剩下的并到静态广播接收器队列末尾;
步骤5:所有静态广播接收器入队列处理;

4.2 优先级计算结果相同的情况

对于优先级相同的情况,其实就是看 4.1 节说的对 IntentResolver 的最终解析结果排序的时候,谁在前谁在后了(compare 方法中谁是 o1 谁是 o2),那也就是前面那四次 buildResolveList 方法。

先来简单提一下 IntentFilter 匹配的规则,在《Android 中的自启行为浅析#Broadcast(一)》1.2.0节,注册广播接收器流程第五步

mReceiverResolver.addFilter(bf);

没错就是通过这一步来添加解析的“词库”的,来看 IntentResolver.addFilter():

public void addFilter(F f) {
    ...
1.  mFilters.add(f);
2.  int numS = register_intent_filter(f, f.schemesIterator(),
            mSchemeToFilter, "      Scheme: ");
3.  int numT = register_mime_types(f, "      Type: ");
    if (numS == 0 && numT == 0) {
4.      register_intent_filter(f, f.actionsIterator(),
                mActionToFilter, "      Action: ");
    }
    if (numT != 0) {
5.      register_intent_filter(f, f.actionsIterator(),
                mTypedActionToFilter, "      TypedAction: ");
    }
}

步骤1:注册的 IntentFilter 存在 mFilters 列表中;
步骤2:注册的 IntentFilter mDataSchemes 域不为空的话,将其加入 mSchemeToFilter 列表(ArrayMap<String, F[]>);
步骤3:注册的 IntentFilter mDataTypes 域不为空的话,将其加入 mTypeToFilter 列表(ArrayMap<String, F[]>);
步骤4:如果前面两步都是空的话,判断 IntentFilter 的 mActions 域,这个一般不为空了,加入 mActionToFilter 列表(ArrayMap<String, F[]>);
步骤5:如果步骤3不为空(同时有 mDataTypes 和 mActions 域)的话,就将其加入 mTypedActionToFilter 列表(ArrayMap<String, F[]>);

对于动态广播来讲, mActionToFilter 的 Key 就是一个 Action 字符串了,Value 就是一个 BroadcastFilter 列表了,后调用 addFilter(即后注册的动态广播接收器)的自然会加到对应 BroadcastFilter 列表的末尾。在 IntentResolver.buildResolveList() 函数查找的时候先注册的自然是排在了前面。

那么就可以得出结论了,同样优先级的情况下,对于动态广播接收器来说,先注册的会排在前面。
对于静态广播接收器,注册的时机有两种情况,一种是 PMS 启动时的目录扫描,一种是手机运行中安装新软件。

先来看 PMS 的构造函数:

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    ......
    synchronized (mPackages) {
        mHandlerThread.start();
        mHandler = new PackageHandler(mHandlerThread.getLooper());
        Watchdog.getInstance().addThread(mHandler, mHandlerThread.getName(),
                WATCHDOG_TIMEOUT);

        File dataDir = Environment.getDataDirectory(); // 文件路径"/data"
        mAppDataDir = new File(dataDir, "data"); // 文件路径"/data/data"
        mAppInstallDir = new File(dataDir, "app"); // 文件路径"/data/app"
        mAppLibInstallDir = new File(dataDir, "app-lib"); // 文件路径"/data/app-lib"
        mAsecInternalPath = new File(dataDir, "app-asec").getPath(); // 文件路径"/data/app-asec"
        mUserAppDataDir = new File(dataDir, "user"); // 文件路径"/data/user"
        mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); // 文件路径"/data/app-private"
        ......
        File frameworkDir = new File(Environment.getRootDirectory(), "framework"); // 文件路径"/framework"
        ......
        // Find base frameworks (resource packages without code).
        mFrameworkInstallObserver = new AppDirObserver(
            frameworkDir.getPath(), OBSERVER_EVENTS, true, false);
        mFrameworkInstallObserver.startWatching();
1.      scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR
                | PackageParser.PARSE_IS_PRIVILEGED,
                scanMode | SCAN_NO_DEX, 0);

        // Collected privileged system packages.
        File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");// 文件路径"/system/priv-app"
        mPrivilegedInstallObserver = new AppDirObserver(
                privilegedAppDir.getPath(), OBSERVER_EVENTS, true, true);
        mPrivilegedInstallObserver.startWatching();
2.          scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED, scanMode, 0);

        // Collect ordinary system packages.
        File systemAppDir = new File(Environment.getRootDirectory(), "app");// 文件路径"/system/app"
        mSystemInstallObserver = new AppDirObserver(
            systemAppDir.getPath(), OBSERVER_EVENTS, true, false);
        mSystemInstallObserver.startWatching();
3.      scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);

        // Collect all vendor packages.
        File vendorAppDir = new File("/vendor/app");
        mVendorInstallObserver = new AppDirObserver(
            vendorAppDir.getPath(), OBSERVER_EVENTS, true, false);
        mVendorInstallObserver.startWatching();
4.      scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);

        ......
        if (!mOnlyCore) {
            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                    SystemClock.uptimeMillis());
            mAppInstallObserver = new AppDirObserver(
                mAppInstallDir.getPath(), OBSERVER_EVENTS, false, false);
            mAppInstallObserver.startWatching();
5.          scanDirLI(mAppInstallDir, 0, scanMode, 0);

            mDrmAppInstallObserver = new AppDirObserver(
                mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false, false);
            mDrmAppInstallObserver.startWatching();
6.          scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
                    scanMode, 0);
            ......
        }
        ......
    }
    ......
}

步骤1:扫描 /framework 下的包;
步骤2:扫描 /system/priv-app 下的包;
步骤3:扫描 /system/app 下的包;
步骤4:扫描 /vendor/app 下的包;
步骤5:扫描 /data/app下的包;
步骤6:扫描 /data/app-private 下的包;

扫描文件时是用的 File.list() 列出所有文件,顺序就是 Linux 上文件在磁盘上的偏移顺序,一般来讲就是后创建的在后面,那么对应 Android 上来说就是后安装的在后面。

本文不再展开扫描的细节,就简单贴一个执行到最后注册 IntentFilter 的地方,在 PackageManagerService.scanPackageLI,安装新应用时同样也会执行这个函数去扫描解析出的应用包(PackageParser.Package pkg):

// All available receivers, for your resolving pleasure.
final ActivityIntentResolver mReceivers = new ActivityIntentResolver();

private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
        int parseFlags, int scanMode, long currentTime, UserHandle user) {
    ......
    // writer
    synchronized (mPackages) {
        ......
        for (i=0; i<N; i++) {
            PackageParser.Activity a = pkg.receivers.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName, pkg.applicationInfo.uid);
*           mReceivers.addActivity(a, "receiver");
            if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
                if (r == null) {
                    r = new StringBuilder(256);
                } else {
                    r.append(' ');
                }
                r.append(a.info.name);
            }
        }
        ......
    }
    ......
}

带*的这行代码调用 ActivityIntentResolver.addActivity():

public final void addActivity(PackageParser.Activity a, String type) {
    final boolean systemApp = isSystemApp(a.info.applicationInfo);
    mActivities.put(a.getComponentName(), a);
    ...
    final int NI = a.intents.size();
    for (int j=0; j<NI; j++) {
        PackageParser.ActivityIntentInfo intent = a.intents.get(j);
        ......
*       addFilter(intent);
    }
}

执行到了 IntentResolver.addFilter(),剩下的上面已经说过了。
那么对于 PMS 刚启动时,也基本可以说是刚开机时,注册的静态广播接收器的顺序就是:

  1. /framework 下的包
  2. /system/priv-app 下的包
  3. /system/app 下的包
  4. /vendor/app 下的包
  5. /data/app下的包
  6. /data/app-private 下的包

同一个目录下先安装的排在前面。运行过程中安装的应用还得排在启动扫描的之后,仍然是先安装的先注册。

5 小结

本文分析了广播的发送与接收的流程,广播接收器的注册,以及一些重要的系统广播、优先级的问题。我们发现,自从有了 FLAG_EXCLUDE_STOPPED_PACKAGES 和 AppOps 的限制,普通应用通过广播形式自启、拉活,也没那么容易了。

标签: none

添加新评论