前言
Android Lolipoop以上版本屏蔽了隐式Intent启动Service,以前我们可以这么做:
Intent intent = new Intent("action.of.service");
context.startService(intent);
现在这样行不通了,会报错!根据Google指示,我们最少应该给Intent里面加上Service的包名:
Intent intent = new Intent("action.of.service");
intent.setPackage("packageName.of.service");
context.startService(intent);
好的,这样修改就妥妥的了。
偷懒
我们动动歪脑筋,在手握源码的情况下,为何不直接屏蔽隐式Intent验证机制呢?回到KitKat年代,一片和谐,多么美好。
过程
说干就干。start/stop/bind service时的Intent验证机制位于ContextImpl.java中。
可以看到也就是抛个异常,别的啥也没干嘛。于是我们动手:
private void validateServiceIntent(Intent service) {
if (service.getComponent() == null && service.getPackage() == null) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
IllegalArgumentException ex = new IllegalArgumentException(
"Service Intent must be explicit: " + service);
throw ex; <--- 我们轻松将其注释
} else {
Log.w(TAG, "Implicit intents with startService are not safe: " + service
+ " " + Debug.getCallers(2, 3));
}
}
}
之后新编framework.jar替换原来的,重启。
问题
一切并未按预期发展,更改后的系统开机时不断重启,检查log,发现出错的调用堆栈与DevicePolicyManagerService.java和KeyChain.java两个文件有关。
DevicePolicyManagerService
代码接收开机广播,启动一个AsyncTask。
BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
......
if (Intent.ACTION_BOOT_COMPLETED.equals(action)
|| KeyChain.ACTION_STORAGE_CHANGED.equals(action)) {
new MonitoringCertNotificationTask().execute(intent);
}
}
}
AsyncTask
中做了一些工作,其中会调用KeyChain
进行绑定Service操作。
private void manageNotification(UserHandle userHandle) {
......
// Call out to KeyChain to check for user-added CAs
boolean hasCert = false;
try {
KeyChainConnection kcs = KeyChain.bindAsUser(mContext, userHandle);
try {
if (!kcs.getService().getUserCaAliases().getList().isEmpty()) {
hasCert = true;
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
} finally {
kcs.close();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (RuntimeException e) {
Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
}
......
}
KeyChain
public static KeyChainConnection bindAsUser(Context context, UserHandle user)
throws InterruptedException {
if (context == null) {
throw new NullPointerException("context == null");
}
......
Intent intent = new Intent(IKeyChainService.class.getName());
ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
intent.setComponent(comp); <---- 出错时,这个ComponentName为空
boolean isBound = context.bindServiceAsUser(intent,
keyChainServiceConnection,
Context.BIND_AUTO_CREATE,
user);
if (!isBound) {
throw new AssertionError("could not bind to KeyChainService");
}
return new KeyChainConnection(context, keyChainServiceConnection, q.take());
}
可以看到KeyChain
中视图绑定一个Service,且在出错的调用链上Component
为空,实际上是隐式Intent
绑定Service。
这是Google自己也有问题啊。。。。。。
分析
- 隐式
Intent
验证机制生效,系统正常。 - 隐式
Intent
验证机制被屏蔽,系统system_server
进程报错,定位错误位于以上启动Service代码。 - 绑定Service出错时,
KeyChain
抛出AssertionError
异常。 AssertionError
异常直接继承与Error
类。方法调用者DevicePolicyManagerService
并未捕获此异常。- 因此,导致
DevicePolicyManagerService
执行出错,又该Service是系统核心服务运行于system_server
进程,则该进程挂掉,重启。 - 如果打开隐式
Intent
验证机制,则抛出IllegalArgumentException
,继承于RuntimeException
,被DevicePolicyManagerService
捕获,不会导致挂掉。系统继续往下运行。
解决
加一行catch即可,搞定。
private void manageNotification(UserHandle userHandle) {
......
// Call out to KeyChain to check for user-added CAs
boolean hasCert = false;
try {
KeyChainConnection kcs = KeyChain.bindAsUser(mContext, userHandle);
try {
if (!kcs.getService().getUserCaAliases().getList().isEmpty()) {
hasCert = true;
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
} finally {
kcs.close();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (RuntimeException e) {
Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
} catch (AssertionError e) { <--- 万恶的异常。
Log.e(LOG_TAG, "Could not connect to KeyChain service", e);
}
......
}
存疑
个人认为,根本原因应该是IKeyChainService
这个Service没有找到,导致了后续问题。待查到为什么找不到这玩意后再来更新。
补充
接上文,已经发现了为何IKeyChainService
没有找到:
IKeyChainService
是com.android.keychain
提供的服务,属于KeyChain
这个MODEL。经查,发现是我们的ROM中没有加入此模块。与AOSP源码对比,更改build/target/product/core.mk,加入KeyChain
即可。