一、问题场景
现在有两个应用,一个是应用A,另一个是应用B,B是做为插件的形式存在,服务于A,当应用A需要数据时,则通过ContentProvider去拿数据回来。但是现在有个问题,就是在Android版本5.x上,有一个是否允许自启动权限,这个权限会影响到应用A向应用B获取数据。我们先来看看下面几个场景:
- 场景一:安装应用A,再安装应用B,打开应用B的自启动权限,这个时候激活应用B,这个时候A再去应用B拿数据,则应用A可以顺利拿到数据。这个时候用DDMS去查看B的进程,B肯定是打开的(不是逗你,你都激活了,还能是关闭么。。。)
- 场景二:安装应用A,再安装应用B,关闭应用B的自启动权限,这个时候激活应用B,这个时候A再去应用B拿数据,则应用A也可以顺利拿到数据,这个时候用DDMS去查看B的进程,肯定是打开的(这也不是逗你。。。)
- 场景三:安装应用A,再安装应用B,打开应用B的自启动权限,这个时候不去激活B,查看DDMS,确保B没有被激活,应用A向B拿数据,发现可以取到数据,这个时候再去查看DDMS,可以发现B的进程已经被激活了。
- 场景四:安装应用A,再安装应用B,关闭应用B的自启动权限,这个时候不去激活B,查看DDMS,确保B没有被激活,应用A向B拿数据,发现现在取不到数据了,这个时候在去查看DDMS,发现B的进程没有被激活。此时后台会发现Log:ActivityThread(22430): Failed to find providerinfo for xxx.
由此可以得出,Android中A应用向B应用去通过ContentProvider去获取数据时,是需要先唤醒B应用,在通过B的ContentProvider去获取资源数据,如果此时应用B的自启动权限被取消,则A应用是无法完成读取资源的操作的。
二、问题研究
首先我们先来看一下android源码:
public ProviderInforesolveContentProvider(String name, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
// reader
synchronized (mPackages) {
final PackageParser.Providerprovider = mProvidersByAuthority.get(name);
PackageSetting ps = provider !=null
?mSettings.mPackages.get(provider.owner.packageName)
: null;
return ps != null
&& <span style="color:#ff0000;">mSettings.isEnabledLPr(provider.info,flags, userId)</span>
&& (!mSafeMode ||(provider.info.applicationInfo.flags
&ApplicationInfo.FLAG_SYSTEM)!= 0)
? PackageParser.generateProviderInfo(provider,flags,
ps.readUserState(userId),userId)
: null;
}
}
boolean isEnabledLPr(ComponentInfo componentInfo, int flags, int userId){
if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
return true;
}
final String pkgName = componentInfo.packageName;
final PackageSetting packageSettings = mPackages.get(pkgName);
if (PackageManagerService.DEBUG_SETTINGS) {
Log.v(PackageManagerService.TAG,"isEnabledLock - packageName = "
+ componentInfo.packageName+ " componentName = " + componentInfo.name);
Log.v(PackageManagerService.TAG,"enabledComponents: "
+ compToString(packageSettings.getEnabledComponents(userId)));
Log.v(PackageManagerService.TAG,"disabledComponents: "
+compToString(packageSettings.getDisabledComponents(userId)));
}
if (packageSettings == null) {
return false;
}
PackageUserState ustate = packageSettings.readUserState(userId);
if ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0){
if (ustate.enabled ==COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
return true;
}
}
<span style="color:#ff0000;">if (ustate.enabled ==COMPONENT_ENABLED_STATE_DISABLED
|| ustate.enabled ==COMPONENT_ENABLED_STATE_DISABLED_USER
|| ustate.enabled ==COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
|| (packageSettings.pkg != null&& !packageSettings.pkg.applicationInfo.enabled
&& ustate.enabled== COMPONENT_ENABLED_STATE_DEFAULT)) {
return false;
}</span>
if (ustate.enabledComponents != null
&&ustate.enabledComponents.contains(componentInfo.name)) {
return true;
}
if (ustate.disabledComponents != null
&&ustate.disabledComponents.contains(componentInfo.name)) {
return false;
}
return componentInfo.enabled;
}
也就是说,在ActivityThread.acquried->ActivityManagerService.getprovider->PackageManagerService.resolveContentProvider的方法里会判断是否有对应的provider,如果有的话,还要判断provider的是否可以自动运行。因为自启动权限被关闭了,所以也就无法从应用B读取到数据了 。
三、问题解决
- 打开应用B的自启动权限,这是用户自己的决定,如果有必要的话,如果发现当前读不到B数据,则A主动弹出窗口提示用户把B的应用自启动权限打开。当然,不是所有用户都会这么做。
- 用反射的方法代替ContentProvider去B应用中获取数据资源ID,也就是说,插件包要对外公开出一个资源文件的名字,而这个资源文件的名字保函了你需要读取的资源的ID,我们通过读取这个资源文件,解析出我们要的资源ID,在通过这个包的上下文去取到这个资源即可。
四、总结
由以上的研究可以看出,这个是Android机制上的问题,应用Android 5.0之后才将自启动权限交给用户去管理,因此,至少在应用层上面,我们是没有办法绕过这个权限,去关联启动这个应用的,因此,只能绕过ContentProvider去读取数据。从一个应用读取另一个应用的数据,还要不启动目标应用,这里想到的方法就是反射了。