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