Android 插件化开发有两方面,一是代码的加载,二是资源的加载。

基于上一篇Android activity的启动方式先对代码的加载说一下,下一篇说一下资源的的加载。

插件化:将一个未安装的apk下载到本地,在未安装的情况下,宿主app可以打开apk 的activity,严格的插件化和组件化需要大家百度科普一下。上一篇<activity的启动>中提到最后是调用ActivityThread的中的performLaunchActivity方法,

Activity activity = null;
try {
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(
            cl, component.getClassName(), r.intent);
    StrictMode.incrementExpectedActivityCount(activity.getClass());
    r.intent.setExtrasClassLoader(cl);
    r.intent.prepareToEnterProcess();
    if (r.state != null) {
        r.state.setClassLoader(cl);
    }
}



方法中的r.packageInfo.getClassLoader(),packaginfo是LoadApk的对象,方法是怎么实现的具体看一下:


public ClassLoader getClassLoader() {
    synchronized (this) {
        if (mClassLoader == null) {
            createOrUpdateClassLoaderLocked(null /*addedPaths*/);
        }
        return mClassLoader;
    }
}

通过getClassLoader获取了一个ClassLoader,

LoadApk,文件的相关信息,诸如Apk文件的代码和资源,甚至代码里面的Activity,Service等四大组件的信息我们都可以通过此对象获取。看一下如何获取:


if (mPackageName.equals("android")) {
        // Note: This branch is taken for system server and we don't need to setup
        // jit profiling support.
        if (mClassLoader != null) {
            // nothing to update
            return;
        }

        if (mBaseClassLoader != null) {
            mClassLoader = mBaseClassLoader;
        } else {
            mClassLoader = ClassLoader.getSystemClassLoader();
        }

        return;
    }

    // Avoid the binder call when the package is the current application package.
    // The activity manager will perform ensure that dexopt is performed before
    // spinning up the process.
    if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) {
        VMRuntime.getRuntime().vmInstructionSet();
        try {
            ActivityThread.getPackageManager().notifyPackageUse(mPackageName,
                    PackageManager.NOTIFY_PACKAGE_USE_CROSS_PACKAGE);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

    if (mRegisterPackage) {
        try {
            ActivityManagerNative.getDefault().addPackageDependency(mPackageName);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    // Lists for the elements of zip/code and native libraries.
    //
    // Both lists are usually not empty. We expect on average one APK for the zip component,
    // but shared libraries and splits are not uncommon. We expect at least three elements
    // for native libraries (app-based, system, vendor). As such, give both some breathing
    // space and initialize to a small value (instead of incurring growth code).
    final List<String> zipPaths = new ArrayList<>(10);
    final List<String> libPaths = new ArrayList<>(10);
    makePaths(mActivityThread, mApplicationInfo, zipPaths, libPaths);

    final boolean isBundledApp = mApplicationInfo.isSystemApp()
            && !mApplicationInfo.isUpdatedSystemApp();

    String libraryPermittedPath = mDataDir;
    if (isBundledApp) {
        // This is necessary to grant bundled apps access to
        // libraries located in subdirectories of /system/lib
        libraryPermittedPath += File.pathSeparator +
                                System.getProperty("java.library.path");
    }

    final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);

    // If we're not asked to include code, we construct a classloader that has
    // no code path included. We still need to set up the library search paths
    // and permitted path because NativeActivity relies on it (it attempts to
    // call System.loadLibrary() on a classloader from a LoadedApk with
    // mIncludeCode == false).
    if (!mIncludeCode) {
        if (mClassLoader == null) {
            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
            mClassLoader = ApplicationLoaders.getDefault().getClassLoader(
                "" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
                librarySearchPath, libraryPermittedPath, mBaseClassLoader);
            StrictMode.setThreadPolicy(oldPolicy);
        }

        return;
    }

    /*
     * With all the combination done (if necessary, actually create the java class
     * loader and set up JIT profiling support if necessary.
     *
     * In many cases this is a single APK, so try to avoid the StringBuilder in TextUtils.
     */
    final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
            TextUtils.join(File.pathSeparator, zipPaths);

    if (ActivityThread.localLOGV)
        Slog.v(ActivityThread.TAG, "Class path: " + zip +
                ", JNI path: " + librarySearchPath);

    boolean needToSetupJitProfiles = false;
    if (mClassLoader == null) {
        // 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,
                mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                libraryPermittedPath, mBaseClassLoader);

        StrictMode.setThreadPolicy(oldPolicy);
        // Setup the class loader paths for profiling.
        needToSetupJitProfiles = true;
    }

    if (addedPaths != null && addedPaths.size() > 0) {
        final String add = TextUtils.join(File.pathSeparator, addedPaths);
        ApplicationLoaders.getDefault().addPath(mClassLoader, add);
        // Setup the new code paths for profiling.
        needToSetupJitProfiles = true;
    }

    // Setup jit profile support.
    //
    // It is ok to call this multiple times if the application gets updated with new splits.
    // The runtime only keeps track of unique code paths and can handle re-registration of
    // the same code path. There's no need to pass `addedPaths` since any new code paths
    // are already in `mApplicationInfo`.
    //
    // It is NOT ok to call this function from the system_server (for any of the packages it
    // loads code from) so we explicitly disallow it there.
    if (needToSetupJitProfiles && !ActivityThread.isSystem()) {
        setupJitProfileSupport();
    }
}

拿到classLoader后,将activity加载进来。大家应该还记得这个,发送消息给ActivityThread的中的H,Handler对象,重写HandlerMessage的方法里面的,其中handlerLauncherActivity里面调用的是performLaunchActivity方法。重点看一下getPackaeInfoNoCheck()方法,看一下

LoadApk是如何产生的。


public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

            r.packageInfo = getPackageInfoNoCheck(
                    r.activityInfo.applicationInfo, r.compatInfo);
            handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
        CompatibilityInfo compatInfo) {
    return getPackageInfo(ai, compatInfo, null, false, true, false);
}
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
        ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
        boolean registerPackage) {
    final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
    synchronized (mResourcesManager) {
        WeakReference<LoadedApk> ref;
        if (differentUser) {
            // Caching not supported across users
            ref = null;
        } else if (includeCode) {
            ref = mPackages.get(aInfo.packageName);
        } else {
            ref = mResourcePackages.get(aInfo.packageName);
        }

        LoadedApk packageInfo = ref != null ? ref.get() : null;
        if (packageInfo == null || (packageInfo.mResources != null
                && !packageInfo.mResources.getAssets().isUpToDate())) {
            if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
                    : "Loading resource-only package ") + aInfo.packageName
                    + " (in " + (mBoundApplication != null
                            ? mBoundApplication.processName : null)
                    + ")");
            packageInfo =
                new LoadedApk(this, aInfo, compatInfo, baseLoader,
                        securityViolation, includeCode &&
                        (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

            if (mSystemThread && "android".equals(aInfo.packageName)) {
                packageInfo.installSystemApplicationInfo(aInfo,
                        getSystemContext().mPackageInfo.getClassLoader());
            }

            if (differentUser) {
                // Caching not supported across users
            } else if (includeCode) {
                mPackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            } else {
                mResourcePackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            }
        }
        return packageInfo;

大家注意一下,第三个参数,在调用的时候是穿的参数是null即baseLoader,所以走的是这个:

packageInfo =
                new LoadedApk(this, aInfo, compatInfo, baseLoader,
                        securityViolation, includeCode &&
                        (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

跟进:

public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
        CompatibilityInfo compatInfo, ClassLoader baseLoader,
        boolean securityViolation, boolean includeCode, boolean registerPackage) {

    mActivityThread = activityThread;
    setApplicationInfo(aInfo);
    mPackageName = aInfo.packageName;
    mBaseClassLoader = baseLoader;
    mSecurityViolation = securityViolation;
    mIncludeCode = includeCode;
    mRegisterPackage = registerPackage;
    mDisplayAdjustments.setCompatibilityInfo(compatInfo);
}

文章前面的getClassLoader方法里面调用的是:



private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
    if (mPackageName.equals("android")) {
        // Note: This branch is taken for system server and we don't need to setup
        // jit profiling support.
        if (mClassLoader != null) {
            // nothing to update
            return;
        }

        if (mBaseClassLoader != null) {
            mClassLoader = mBaseClassLoader;
        } else {
            mClassLoader = ClassLoader.getSystemClassLoader();
        }

        return;
    }



因为mBaseClassLoader是null直接调ClassLoader.getSystemClassLoader()

@CallerSensitive
public static ClassLoader getSystemClassLoader() {
    return SystemClassLoader.loader;
}
static private class SystemClassLoader {
    public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private static ClassLoader createSystemClassLoader() {
    String classPath = System.getProperty("java.class.path", ".");
    String librarySearchPath = System.getProperty("java.library.path", "");

    // String[] paths = classPath.split(":");
    // URL[] urls = new URL[paths.length];
    // for (int i = 0; i < paths.length; i++) {
    // try {
    // urls[i] = new URL("file://" + paths[i]);
    // }
    // catch (Exception ex) {
    // ex.printStackTrace();
    // }
    // }
    //
    // return new java.net.URLClassLoader(urls, null);

    // TODO Make this a java.net.URLClassLoader once we have those?
    return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}

最后new 了一个PathClassLoader,也就是LoaderAPK最后返回的是是一个PathClassLoader的对象去加载

activity = mInstrumentation.newActivity(
        cl, component.getClassName(), r.intent);
public Activity newActivity(ClassLoader cl, String className,
        Intent intent)
        throws InstantiationException, IllegalAccessException,
        ClassNotFoundException {
    return (Activity)cl.loadClass(className).newInstance();
}

里面的cl即使PathClassLoader对象,进一步看一下PathClassLoader:

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}
public class BaseDexClassLoader extends ClassLoader {
    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        throw new RuntimeException("Stub!");
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new RuntimeException("Stub!");
    }

    protected URL findResource(String name) {
        throw new RuntimeException("Stub!");
    }

    protected Enumeration<URL> findResources(String name) {
        throw new RuntimeException("Stub!");
    }

    public String findLibrary(String name) {
        throw new RuntimeException("Stub!");
    }

    protected synchronized Package getPackage(String name) {
        throw new RuntimeException("Stub!");
    }

    public String toString() {
        throw new RuntimeException("Stub!");
    }
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package dalvik.system;

import java.io.File;

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}



PathClassLoader和DexClassLoader,
它们都继承自BaseDexClassLoader,这两个类有什么区别呢?其实看一下它们的源码注释就一目了然了。

Android系统通过PathClassLoader来加载系统类和主dex中的类。
而DexClassLoader则用于加载其他dex文件中的类。
他们都是继承自BaseDexClassLoader,具体的加载方法是findClass。



@Override
    protected Class<?> loadClass(String className, boolean resolve)
           throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
            clazz = findClass(className);
        }

        return clazz;
    }

在PathClassLoader和BaseDexClassLoader都米有找到loadClass,最后在ClassLoader中找见,调的是findClass,父类里面没有实现,子类PathClassLoader也没有实现,BaseDexClassLoader也没有实现,我是不是看到假的源码了,我看的API是25的。

ClassLoader的方法,findClass方法,PathClassLoader和BaseDexClassLoader都木有重写是什么鬼?求解释。


protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}