1. 背景
由于在工程中运用了 SPI 机制,经过 ServiceLoader 的配合来完结模块间的通信。可是忽然收到线上客户反馈运用了 SDK 后无法进行模块加载,导致部分功用异常。
2. 剖析排查
借助客户供给的测验包进行 debug 调试,发现在调试到 ServiceLoader.load() 办法时的确无法加载到对应的模块装备。检查 ServiceLoader 的状态信息如下:
其间的 loader 是 LoadApk$WarningContextClassLoader 目标,而正常情况下是 DexPathClassLoader。
2.1 检查 ServiceLoader.loader 界说
ServiceLoader API 文档:developer.android.com/reference/j…
依据接口界说 load 办法会依据指定的 serviceType 创立新的 ServiceLoader 目标回来,ServiceLoader 内部依据当时线程对应的 ContextClassLoader 目标去加载装备,所以到这儿能够剖析到 load 办法的加载结果会受 ContextClassLoader 的影响,进一步推理可能收到插件化、热修复等结构影响,承认后并没有使插件化、热修复等结构。
2.2 WarningContextClassLoader 为何物?
查找 Android famework 源码,找到 WarningContextClassLoader 是界说在 LoaderApk 文件中的内部类(部分版本是 ActivityThread 类中的内部类)。
private void initializeJavaContextClassLoader() {
IPackageManager pm = ActivityThread.getPackageManager();
android.content.pm.PackageInfo pi =
PackageManager.getPackageInfoAsUserCached(
mPackageName,
PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
UserHandle.myUserId());
if (pi == null) {
throw new IllegalStateException("Unable to get package info for "
+ mPackageName + "; is package not installed?");
}
/*
* Two possible indications that this package could be
* sharing its virtual machine with other packages:
*
* 1.) the sharedUserId attribute is set in the manifest,
* indicating a request to share a VM with other
* packages with the same sharedUserId.
*
* 2.) the application element of the manifest has an
* attribute specifying a non-default process name,
* indicating the desire to run in another packages VM.
*/
boolean sharedUserIdSet = (pi.sharedUserId != null);
boolean processNameNotDefault =
(pi.applicationInfo != null &&
!mPackageName.equals(pi.applicationInfo.processName));
boolean sharable = (sharedUserIdSet || processNameNotDefault);
ClassLoader contextClassLoader =
(sharable)
? new WarningContextClassLoader()
: mClassLoader;
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
private static class WarningContextClassLoader extends ClassLoader {
private static boolean warned = false;
private void warn(String methodName) {
if (warned) {
return;
}
warned = true;
Thread.currentThread().setContextClassLoader(getParent());
Slog.w(ActivityThread.TAG, "ClassLoader." + methodName + ": " +
"The class loader returned by " +
"Thread.getContextClassLoader() may fail for processes " +
"that host multiple applications. You should explicitly " +
"specify a context class loader. For example: " +
"Thread.setContextClassLoader(getClass().getClassLoader());");
}
...
}
在运用创立时会调用 ActivityThread 类中的 attach 办法中,attach 办法从而调用 LoadedApk 类中的 makeApplicationInner() 用于创立对应的 Application 目标。在 makeApplicationInner() 办法的内部调用 initializeJavaContextClassLoader 办法创立对应的 ContentClassLoader 目标,在 initializeJavaContextClassLoader 办法的内部能够看到,假如当时 App 在 manifest 中设置 sharedUserId 特点,则当时运用运用的是 WarningContextClassLoader。下面咱们便是检查 App 中的装备。
终究验证了咱们的猜想,运用 demo 设置 sharedUserId 特点问题可正常复现。
2.3 sharedUserId 特点
检查官方文档该特点装备 API 级别 29 中已弃用此常量。
同享用户 ID 会在软件包管理器中导致具有不确定性的行为。因而,强烈建议您不要运用它,并且咱们在未来的 Android 版本中会将其移除。
2.总结
排查问题还是比较费神,在没有显着错误的时候,只能针对每个可疑的信息去剖析,期望发现蛛丝马迹。