Android应用启动后可加载的代码文件有三种,按加载顺序依次如下:
- androidmanifest内uses-library指定的jar
- APK包根目录的dex文件
- APK包lib目录下的so文件
可以在manifest里随便指定想要加载的jar吗?当然不行,这个jar必须是在/etc/permissions/目录下的xml有过配置的,比如:
//截取/etc/permissions/platform.xml文件中的相关内容
<permissions>
...
<library name="android.test.runner"
file="/system/framework/android.test.runner.jar" />
<library name="javax.obex"
file="/system/framework/javax.obex.jar"/>
...
</permissions>
在系统启动时,PMS会读取/etc/permissions/下的所有配置,然后在APK安装时做相关校验,如果APK配置的uses-library文件在系统配置中不存在,安装会出错
在APK安装成功后,我们通过其ApplicationInfo就可以拿到:
ApplicationInfo aInfo;
...
aInfo.sourceDir //执行文件路径
aInfo.publicSourceDir //resource文件路径
aInfo.nativeLibraryDir //so所在路径
aInfo.dataDir //app数据目录
aInfo.sharedLibraryFiles //uses-library配置的文件
sourceDir和publicSourceDir通常来说都是一样的,接着我们来看LoadedApk的构造代码:
public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
CompatibilityInfo compatInfo,
ActivityThread mainThread, ClassLoader baseLoader,
boolean securityViolation, boolean includeCode) {
mActivityThread = activityThread;
mApplicationInfo = aInfo;
mPackageName = aInfo.packageName;
mAppDir = aInfo.sourceDir;
final int myUid = Process.myUid();
mResDir = aInfo.uid == myUid ? aInfo.sourceDir
: aInfo.publicSourceDir;
if (!UserHandle.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) {
aInfo.dataDir = PackageManager.getDataDirForUser(UserHandle.getUserId(myUid),
mPackageName);
}
mSharedLibraries = aInfo.sharedLibraryFiles;
mDataDir = aInfo.dataDir;
mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
mLibDir = aInfo.nativeLibraryDir;
...
}
在LoadedApk构造时,拿到并保存了ApplicationInfo所包含的代码和资源的目录,接着看创建PathClassLoader:
public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader != null) {
return mClassLoader;
}
if (mIncludeCode && !mPackageName.equals("android")) {
String zip = mAppDir;
String libraryPath = mLibDir;
/*
* The following is a bit of a hack to inject
* instrumentation into the system: If the app
* being started matches one of the instrumentation names,
* then we combine both the "instrumentation" and
* "instrumented" app into the path, along with the
* concatenation of both apps' shared library lists.
*/
String instrumentationAppDir =
mActivityThread.mInstrumentationAppDir;
String instrumentationAppLibraryDir =
mActivityThread.mInstrumentationAppLibraryDir;
String instrumentationAppPackage =
mActivityThread.mInstrumentationAppPackage;
String instrumentedAppDir =
mActivityThread.mInstrumentedAppDir;
String instrumentedAppLibraryDir =
mActivityThread.mInstrumentedAppLibraryDir;
String[] instrumentationLibs = null;
if (mAppDir.equals(instrumentationAppDir)
|| mAppDir.equals(instrumentedAppDir)) {
zip = instrumentationAppDir + ":" + instrumentedAppDir;
libraryPath = instrumentationAppLibraryDir + ":" + instrumentedAppLibraryDir;
if (! instrumentedAppDir.equals(instrumentationAppDir)) {
instrumentationLibs =
getLibrariesFor(instrumentationAppPackage);
}
}
if ((mSharedLibraries != null) ||
(instrumentationLibs != null)) {
zip =
combineLibs(mSharedLibraries, instrumentationLibs)
+ ':' + zip;
}
/*
* With all the combination done (if necessary, actually
* create the class loader.
*/
if (ActivityThread.localLOGV)
Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + libraryPath);
// Temporarily disable logging of disk reads on the Looper thread
// as this is early and necessary.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
mClassLoader =
ApplicationLoaders.getDefault().getClassLoader(
zip, libraryPath, mBaseClassLoader);
initializeJavaContextClassLoader();
StrictMode.setThreadPolicy(oldPolicy);
} else {
if (mBaseClassLoader == null) {
mClassLoader = ClassLoader.getSystemClassLoader();
} else {
mClassLoader = mBaseClassLoader;
}
}
return mClassLoader;
}
}
这里重点看getClassLoader函数创建入的zip和libraryPath
String zip = mAppDir;
...
if ((mSharedLibraries != null) ||
(instrumentationLibs != null)) {
zip = combineLibs(mSharedLibraries, instrumentationLibs) + ':' + zip;
}
从这里我们可以看出,创建PathClassLoader时,就同时传入了apk和uses-library所设置的jar作为apk启动要加载的dex path list
到目前位置,dex和uses-library的加载已经明确,那so文件呢?
看System.loadLibrary的代码:
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
VMStack.getCallingClassLoader()获取的就是当前App的PathClassLoader,接着看
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
throw new UnsatisfiedLinkError("Couldn't load " + libraryName +
" from loader " + loader +
": findLibrary returned null");
}
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : mLibPaths) {
String candidate = directory + filename;
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
看到没,最终还是通过
String filename = loader.findLibrary(libraryName);
调用了PathClassLoader的findLibrary来查找so的并返回要加载so的文件路径
总结
所有App启动所需可执行文件的信息,在App安装成功后,都已经被完整的保存到ApplicationInfo里,在App启动时,通过Intent可以从PMS拿到对应的ApplicationInfo,然后基于它来生成对应的LoadedApk和PathClassLoader