ps:问题描述,在进行系统裁剪以及引导加速后导致设备rtc功能异常–timeservice服务无法开机时被广播带起,导致rtc set time无法在网络更新时间后执行。
文章续自:【qcom msm8953 android712】rtc 调试分析
上图是本平台网络更新并带RTC功能的大概框图,其宏观大概工作如下:
- 网络时间更新,触发Android层的systime set;
- Android层time set后触发TIME_SET广播的产生和发送;
- TimeService静态接收TIME_SET广播并执行相应操作;
- TimeService和time_daemon进行交互将时间设置并实现rtc set time至kernel rtc driver。
这里主要分析下从NetTime到TimeServcie的一个工作流程,涉及代码如下所示:
frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/services/core/java/com/android/server/am/UserController.java
frameworks/base/services/core/java/com/android/server/am/UserState.java
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
frameworks/base/services/core/java/com/android/server/IntentResolver.java
frameworks/base/core/java/android/os/UserManagerInternal.java
frameworks/base/core/java/android/content/IntentFilter.java
frameworks/base/core/java/android/content/pm/PackageParser.java
基于解决本问题的原因是TimeService没有正常启动(开机阶段),那么带着这个问题去分析。
Q:TimeService没有正常启动的原因?
A:没收到广播。
Q:为什么没收到广播?
A:广播没发或者发了没收到(被过滤或者丢弃或者拦截)。
那么事关广播发送接收相关机制,那么先从这里开始。
1.先确认广播是否发出,如何确认,很简单,找到发送广播的地方,根据前面???的分析,知道广播发送者是AlarmManagerService 的 AlarmThread线程,至于它是怎么工作的,这里不做分析。
如下:
private class AlarmThread extends Thread{
//....
public void run(){
//...
Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
//...
}
//....
}
这里可以很直接的看到AlarmManagerService.AlarmThread.run() 通过 sendBroadcastAsUser 发送了携带ACTION_TIME_CHANGED信息的intent,即action = android.intent.action.TIME_SET。
结论:广播确认已经“发出”。并且携带了FLAG_RECEIVER_REPLACE_PENDING 和 FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 标志信息,UserHandle.ALL类型的接收者。关于标志信息和UserHandle.ALL的相关可自行查阅资料。
2.跟踪至广播发送处理代码–broadcastIntentLocked
broadcastIntentLocked方法位于AMS中。通过broadcastIntent调用(不做阐述),如下:
注意,该方法代码可以所非常之多,这里只将与本文的部分留下
final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {
// ...
receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
// ...
}
这里我只留下了collectReceiverComponents方法调用的代码,将broadcastIntentLocked方法的工作内容列如下,可参考源码阅读:
- 取得intent对象,并添加FLAG_EXCLUDE_STOPPED_PACKAGES标志;
- 验证广播的合法性和合理性;
- 处理action;
- 处理sticky类型广播;
- 判断调用者想让广播送给所有用户还是特定用户;
- 找出广播的接收者,包括动态注册和静态注册;
- 分别将合法合理的动态广播和静态广播入队–BroadcastQueue;
- 执行广播发送调度。
ps:这里说明下,该方法里面用了大量的篇幅去验证广播的合法性合理性,权限等问题。另外,该方法实际上只是对广播做了分类处理和验证处理以及入队处理,真正发送广播的操作实际还在后面,也就是上面提到广播入队后的发送调度。
回到上面提到的collectReceiverComponents方法,该方法是针对静态广播接收者的一个整理收集。
它的返回类型是一个ResolveInfo类型的集合。
如下:
private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
int callingUid, int[] users) {
// ...
List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
.queryIntentReceivers(intent, resolvedType, pmFlags, user).getList();
// ...
}
可从上面看出,实际上返回的结果是从queryIntentReceivers方法中得到的,而该方法又是通过getPackageManager()得到,即实现在PMS中, 跟踪如下:
@Override
public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
String resolvedType, int flags, int userId) {
return new ParceledListSlice<>(
queryIntentReceiversInternal(intent, resolvedType, flags, userId));
}
从上可以得出以下两点:
- 该方法是重写的;
- 该方法的结果实际是从queryIntentReceiversInternal得到;
PMS继承了IPackageManager.Stub继而实现了queryIntentReceivers方法,因此在AMS中可以通过getPackageManager()调用该方法。
queryIntentReceiversInternal方法实现如下所示:
private @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
String resolvedType, int flags, int userId) {
// ...
flags = updateFlagsForResolve(flags, userId, intent);
// ...
// reader
synchronized (mPackages) {
String pkgName = intent.getPackage();
if (pkgName == null) {
return mReceivers.queryIntent(intent, resolvedType, flags, userId);
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,
userId);
}
return Collections.emptyList();
}
// ...
}
可以从上分析得出如下结论:
- 调用updateFlagsForResolve方法得到新的标志信息;
- 查询intent相应的信息又根据是否存在包名来走不同的处理路线,没有包名则调用queryIntent方法,否则通过PackageParser去解析,解析成功则通过queryIntentForPackage得到相关信息,否则返回空列表。
这里先看第一点:updateFlagsForResolve方法如何实现:
updateFlagsForResolve是通过层层调用至PMS.updateFlags方法,该方法的作用是根据当前用户的加密状态更新给定标志。也就是说,这里的更新标志信息和用户的加密状态扯上关系了,这里留个心眼,后面有用。
updateFlags实现如下:
/**
* Update given flags based on encryption status of current user.
*/
private int updateFlags(int flags, int userId) {
if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE)) != 0) {
// Caller expressed an explicit opinion about what encryption
// aware/unaware components they want to see, so fall through and
// give them what they want
} else {
// Caller expressed no opinion, so match based on user state
if (getUserManagerInternal().isUserUnlockingOrUnlocked(userId)) {
flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
} else {
flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE;
}
}
return flags;
}
private UserManagerInternal getUserManagerInternal() {
if (mUserManagerInternal == null) {
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
}
return mUserManagerInternal;
}
可以看到最终的返回结果是通过getUserManagerInternal方法得到的对象携带的isUserUnlockingOrUnlocked方法得到的,getUserManagerInternal得到的对象是UserManagerInternal对象,可以看到是通过LocalServices注册的,跟到UserManagerInternal对象引用的isUserUnlockingOrUnlocked方法看看发现该方法是个抽象方法,由子类实现,那么实现了该方法是UserManagerService(该服务是为UserManager服务)的内部类LocalService,LocalService继承了UserManagerInternal
@Override
public boolean isUserUnlockingOrUnlocked(int userId) {
synchronized (mUserStates) {
int state = mUserStates.get(userId, -1);
return (state == UserState.STATE_RUNNING_UNLOCKING)
|| (state == UserState.STATE_RUNNING_UNLOCKED);
}
}
这里又发现最终是通过mUserStates取userId对应的键值得到的,真是绕的深啊。也就是说如果只要当前用户不是正在解锁状态和已经解锁状态的其中一个,那么isUserUnlockingOrUnlocked的返回值就是true。
最终PMS.updateFlagsForResolve的标志就会是如下:
这个标志最终会传入上面提到的PMS.queryIntentReceiversInternal.mReceivers.queryIntent 或者 queryIntentForPackage方法中,那么问题来了mReceivers是什么?
// All available receivers, for your resolving pleasure.
final ActivityIntentResolver mReceivers =
new ActivityIntentResolver();
可以看到mReceivers 是 ActivityIntentResolver类型,实际上它是存放所有广播类型信息的地方。
ActivityIntentResolver是PMS的内部类,继承了IntentResolver,实现了它的抽象方法,也就是上面提到的queryIntent、queryIntentForPackage等方法:
final class ActivityIntentResolver
extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
// ...
return super.queryIntent(intent, resolvedType, defaultOnly, userId);
// ...
return super.queryIntent(intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId);
// ...
return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
}
可以看到,它们都调用了父类的相应方法,也就是实现是在IntentResolver类中:
public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
int userId) {
// ...
ArrayList<R> finalList = new ArrayList<R>();
// ...
F[] firstTypeCut = null;
F[] secondTypeCut = null;
F[] thirdTypeCut = null;
F[] schemeCut = null;
// ...
buildResolveList(...);
// ...
}
这里实际上是加快了对广播的查找(分类查找,而不是全部遍历查找),最终都是通过buildResolveList方法得到数据存在了finalList数组中返回上去。
buildResolveList方法实现如下:
private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
boolean debug, boolean defaultOnly,
String resolvedType, String scheme, F[] src, List<R> dest, int userId) {
// ...
match = filter.match(action, resolvedType, scheme, data, categories, TAG);
if (match >= 0) {
if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
// ...
final R oneResult = newResult(filter, match, userId);
if (oneResult != null) {
dest.add(oneResult);
// ...
}
} else {
hasNonDefaults = true;
}
}
// ...
}
该方法重点就是filter.match() (IntentFilter类型),它找到是否有匹配的广播接收者。如果大于0并且newResult方法得到的新结果不为空,那么就加入dest,这个dest就是从queryIntent传进来的finalList数组。最终反馈到AMS.collectReceiverComponents的返回值中,得到静态广播对应的接收者集合。
ps:
updateFlagsForResolve -> updateFlagsForComponent -> updateFlags
那么问题来了,匹配匹配总要有二者吧,匹配的源从哪里来?这里谈的是静态广播的接收者查找匹配,那么就得看看静态广播是如何被加入到filter中去的。
静态广播的注册流程:
PMS处理静态广播的注册事宜,即PMS处理包括对AndroidManifest.xml的解析工作,也就是静态广播在xml中注册的原因,因为安装包或者服务等时候我们都在xml中声明相关信息。
PMS在构造的时候,会对指定的目录进行扫描安装,如下:
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// ...
for (File f : RegionalizationDirs) {
File RegionalizationSystemDir = new File(f, "system");
// Collect packages in <Package>/system/priv-app
scanDirLI(new File(RegionalizationSystemDir, "priv-app"),
PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
// Collect packages in <Package>/system/app
scanDirLI(new File(RegionalizationSystemDir, "app"),
PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags, 0);
// Collect overlay in <Package>/system/vendor
scanDirLI(new File(RegionalizationSystemDir, "vendor/overlay"),
PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags | SCAN_TRUSTED_OVERLAY, 0);
}
// ...
}
这里可以看到分别对四个路径的目录进行了扫描处理,那是如何处理的?跟进去看看:
private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
// ...
Runnable scanTask = new Runnable() {
public void run() {
// ...
scanPackageTracedLI(ref_file, ref_parseFlags | PackageParser.PARSE_MUST_BE_APK,
ref_scanFlags, ref_currentTime, null);
// ...
}
// ...
}
};
// ...
}
可以看到在scanDirLI中起了一个线程(扫描这么多目录确实需要线程),而真正的执行者是scanPackageTracedLI。
这里注意了:scanPackageTracedLI有两个方法,注意它们是不一样的。
/**
* Traces a package scan.
* @see #scanPackageLI(File, int, int, long, UserHandle)
*/
private PackageParser.Package scanPackageTracedLI(File scanFile, final int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
// ...
return scanPackageLI(scanFile, parseFlags, scanFlags, currentTime, user);
// ...
}
private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
final int policyFlags, int scanFlags, long currentTime, UserHandle user)
throws PackageManagerException {
// ...
// Scan the parent
scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags, currentTime, user);
// Scan the children
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPkg = pkg.childPackages.get(i);
scanPackageLI(childPkg, policyFlags,
scanFlags, currentTime, user);
}
// ...
}
这里经过分析,没有实际验证,只验证了安装的情况,应该是构造的时候去扫描走的前者,后面安装的时候扫描解析走的后者,这样导致二者虽然都是调用了scanPackageLI方法,却实际上走到线路确是不同的。
笔者分析rtc的原因是走了后者,这里直接跟踪后者:
后者最终是到了PackageParser.Package scanPackageLI
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
boolean success = false;
// ...
final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags,
currentTime, user);
success = true;
return res;
// ...
}
}
这里也是通过另一个方法scanPackageDirtyLI来得到结果,这个方法也是相当的长,实际上它就是对xml文件解析的实际者:
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
final int policyFlags, final int scanFlags, long currentTime, UserHandle user)
throws PackageManagerException {
// ...
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 ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {
if (r == null) {
r = new StringBuilder(256);
} else {
r.append(' ');
}
r.append(a.info.name);
}
}
// ...
}
它将xml文件中的receiver标签的信息剥离出出来,这个receiver信息就是静态注册时候的广播action。
将receiver信息添加进fliter中是通过mReceivers.addActivity方法,这个mReceivers是不是在哪见过?没错,就是前面提到过的,这也就进一步证明了xml文件中的reciver信息都存在这个类型对象里面。
看看addActivity方法做了什么?
public final void addActivity(PackageParser.Activity a, String type) {
// ...
addFilter(intent);
// ...
}
它就直接调用了addFilter方法,该方法在抽象类IntentResolver中实现。就是将指定intent加入IntentResolver中。这样一加入,就保证,我们在AMS.collectReceiverComponents方法中能够得到相对应的匹配了,这样就能让广播发送者将广播发送至对应的接收者了。
通过上面一跟踪,发现设备TimeService没启动的原因是PMS.updateFlagsForResolve中更新的标志异常了(这个异常是因为mUserStates.get方法得到的用户状态是0,也就是正在BOOTING状态),导致后面传入给mReceivers.queryIntent方法的flags异常,最终导致AMS中针对TIME_SET的广播接收者的收集里没有TimeService,这样静态广播就无法带动TimeService的进程启动了。这样就收不到TIME_SET的广播了。
原因为什么这样,后面抓log,查资料发现Android7.0以后引入了一种启动模式–Direct Boot Mode,讲的是针对多用户的安全而产生的,开机后用户会有几种状态变化,包括前面提到的解锁中和已解锁的状态等等。
而在有些应用是不能在某些状态前启动的(在查询接收者匹配的过程中会被无视掉),也就是说TimeService必须保证能在解锁中或者已解锁之前能够启动。这样就需要在xml中申明如下:
至此,问题解决。
ps:问题源于系统做过一次裁剪,启动优化加速,导致TIME_SET广播过早的发出,而用户还未解锁或者解锁中,这样丢失TimeService丢失启动机会。
参考:
android安全问题(六) 抢先接收广播 - 内因篇之广播接收器注册流程
Android开发之旅: Intents和Intent Filters(理论部分)
Intent匹配规则以及解析框架深入分析
安卓广播的底层实现原理
Android广播发送接收机制
Direct Boot Mode
【Android话题-5.4应用相关】说说静态广播的注册和收发原理
Android多用户之UserManagerService源码分析