Android應用可執行文件的加載介紹(LoadedApk)

Android應用啓動後可加載的代碼文件有三種,按加載順序依次如下:

  1. androidmanifest內uses-library指定的jar
  2. APK包根目錄的dex文件
  3. 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章