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

发布了46 篇原创文章 · 获赞 25 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/zhejiang9/article/details/100100869