1. 背景

由于在工程中运用了 SPI 机制,经过 ServiceLoader 的配合来完结模块间的通信。可是忽然收到线上客户反馈运用了 SDK 后无法进行模块加载,导致部分功用异常。

2. 剖析排查

借助客户供给的测验包进行 debug 调试,发现在调试到 ServiceLoader.load() 办法时的确无法加载到对应的模块装备。检查 ServiceLoader 的状态信息如下:

记录 android:sharedUserId 暴露的一个知识盲点

其间的 loader 是 LoadApk$WarningContextClassLoader 目标,而正常情况下是 DexPathClassLoader。

2.1 检查 ServiceLoader.loader 界说

ServiceLoader API 文档:developer.android.com/reference/j…

记录 android:sharedUserId 暴露的一个知识盲点

依据接口界说 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 中的装备。

记录 android:sharedUserId 暴露的一个知识盲点

终究验证了咱们的猜想,运用 demo 设置 sharedUserId 特点问题可正常复现。

2.3 sharedUserId 特点

检查官方文档该特点装备 API 级别 29 中已弃用此常量。
同享用户 ID 会在软件包管理器中导致具有不确定性的行为。因而,强烈建议您不要运用它,并且咱们在未来的 Android 版本中会将其移除。

记录 android:sharedUserId 暴露的一个知识盲点

2.总结

排查问题还是比较费神,在没有显着错误的时候,只能针对每个可疑的信息去剖析,期望发现蛛丝马迹。