ClassNotFoundException cnfe = new ClassNotFoundException("Didn’t find class “” + name + “” on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; } @Override protected URL findResource(String name) { return pathList.findResource(name); } @Override protected Enumeration findResources(String name) { return pathList.findResources(name); } @Override public String findLibrary(String name) { return pathList.findLibrary(name); } } 显然看出DexPathList这个成员对象的重要性,初始化构造方法的时候实例化DexPathList对象,同时,BaseDexClassLoader重写了父类findClass()方法,通过该方法进行类查找的时候,会委托给pathList对象的findClass()方法进行相应的类查找,下面继续查看DexPathList类的findClass方法: final class DexPathList { private Element[] dexElements; DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) { … this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted); … } private static Element[] makeDexElements(List files, File optimizedDirectory, List suppressedExceptions, ClassLoader loader, boolean isTrusted) { Element[] elements = new Element[files.size()]; int elementsPos = 0; for (File file : files) { if (file.isDirectory()) { elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); DexFile dex = null; if (name.endsWith(DEX_SUFFIX)) { // Raw dex file (not inside a zip/jar). try { dex = loadDexFile(file, optimizedDirectory, loader, elements); if (dex != null) { elements[elementsPos++] = new Element(dex, null); } } catch (IOException suppressed) { System.logE("Unable to load dex file: " + file, suppressed); suppressedExceptions.add(suppressed); } } else { try { dex = loadDexFile(file, optimizedDirectory, loader, elements); } catch (IOException suppressed) { suppressedExceptions.add(suppressed); } if (dex == null) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } } else { System.logW("ClassLoader referenced unknown path: " + file); } } return elements; } public Class findClass(String name, List suppressed) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; } }
DexPathList构造方法被调用的时候其实就是通过makeDexElements方法把dexPath进行遍历,依次加载每个dex文件,然后通过数组Element[]存放,而在DexPathList类的findClass调用的时候,通过遍历Element[]的dex文件,在通过DexFile类的loadClassBinaryName()来加载类,如果不为空那么代表加载成功,并且返回class,否则返回null。
下面再来看一下基类的ClassLoader是如何实现的吧
public abstract class ClassLoader { private final ClassLoader parent; protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { c = findClass(name); } } return c; } }
这明显就是一个双亲委派模型,在类加载的时候,首先去查找这个类之前是否已经被加载过,如果加载过直接返回,否则委托父类加载器去查找,如果父类加载器找不到那么就去系统的BootstrapClass去查找,到最后还是找不到的话,那么就自己亲自上阵查找了。这样就避免了重复加载,实现了更加安全。
好了总结一下DexClassLoader的加载过程:loadClass->findClass->BaseDexClassLoader.findClass->DexPathList.findClass->loadDexFile->DexFile.loadClassBinaryName->DexFile.defineClass,大体上就这样么个过程吧。
资源加载
Android系统加载资源都是通过Resource资源对象来进行加载的,因此只需要添加资源(即apk文件)所在路径到AssetManager中,即可实现对插件资源的访问。
/** • Create a new AssetManager containing only the basic system assets. • Applications will not generally use this method, instead retrieving the • appropriate asset manager with {@link Resources#getAssets}. Not for • use by applications. • @hide */ public AssetManager() { final ApkAssets[] assets; synchronized (sSync) { createSystemAssetsInZygoteLocked(); assets = sSystemApkAssets; } mObject = nativeCreate(); if (DEBUG_REFS) { mNumRefs = 0; incRefsLocked(hashCode()); } // Always set the framework resources. setApkAssets(assets, false /invalidateCaches/); }
不难发现AssetManager的构造方法是@hide隐藏的api,所以不能直接使用,这里肯定是需要通过反射啦,不过有人说Android P不是对系统的隐藏Api做出了限制,因此插件化估计要凉凉,但是我想说现在一些主流的插件化技术基本都已经适配了Android9.0了,所以无需担心。下面先简单贴出Android资源的加载流程。关于插件化的资源加载可以参考下滴滴VirtualApk资源的加载思想 (传送门)
class ContextImpl extends Context { //… private ContextImpl(ContextImpl container, ActivityThread mainThread, LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted, Display display, Configuration overrideConfiguration) { //… Resources resources = packageInfo.getResources(mainThread); //… } //… } 这里不去关注packageInfo是如何生成的,直接跟踪到下面去. public final class LoadedApk { private final String mResDir; public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { final int myUid = Process.myUid(); aInfo = adjustNativeLibraryPaths(aInfo); mActivityThread = activityThread; mApplicationInfo = aInfo; mPackageName = aInfo.packageName; mAppDir = aInfo.sourceDir; mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir; // 注意一下这个sourceDir,这个是我们宿主的APK包在手机中的路径,宿主的资源通过此地址加载。 // 该值的生成涉及到PMS,暂时不进行分析。 // Full path to the base APK for this application. //… } //… public Resources getResources(ActivityThread mainThread) { if (mResources == null) { mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this); } return mResources; } //… } 进入到ActivityThread.getTopLevelResources()的逻辑中 public final class ActivityThread { Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { //我们暂时只关注下面这一段代码 AssetManager assets = new AssetManager(); if (assets.addAssetPath(resDir) == 0) { //此处将上面的mResDir,也就是宿主的APK在手机中的路径当做资源包添加到AssetManager里,则Resources对象可以通过AssetManager查找资源,此处见(老罗博客:Android应用程序资源的查找过程分析) return null; } // 创建Resources对象,此处依赖AssetManager类来实现资源查找功能。 r = new Resources(assets, metrics, getConfiguration(), compInfo); } }
从上面的代码中我们知道了我们常用的Resources是如何生成的了,那么理论上插件也就按照如此方式生成一个Resources对象给自己用就可以了。
组件的加载
这个其实不能一概而论,因为Android拥有四大组件,分别为Activity、Service、ContentProvider、BoradCastRecevier,每个组件的属性及生命周期也不一样,所以关于插件中加载的组件就需要分别研究每个组件是如何加载的了。
简单拿Activity组件来说,现在一些主流的方式基本上都是通过“坑位”的思想,这个词最早据说也是来源于360,总的来说,先占坑,因为我们宿主app的Manifest中是不会去申请插件中的Activity的,那我就先占一个坑,欺骗系统,然后替换成插件中的Activity。这里可能需要多个坑位,因为一些资源属性都是可以动态配置的。比如launchMode、process、configChanges、theme等等。
这里还需要了解一下Activity的启动流程,这里我们可以简单看一下。
@Override public void startActivity(Intent intent, @Nullable Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } } 可以看出,我们平时startActivity其实都是通过调用startActivityForResult(),我们接下来继续看 public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) { if (mParent == null) { options = transferSpringboardActivityOptions(options); Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } if (requestCode >= 0) { mStartedActivity = true; } cancelInputsAndStartExitTransition(options); // TODO Consider clearing/flushing other event sources and events for child windows. } else { if (options != null) { mParent.startActivityFromChild(this, intent, requestCode, options); } else { // Note we want to go through this method for compatibility with // existing applications that may have overridden it. mParent.startActivityFromChild(this, intent, requestCode); } } }
我们可以看到是通过系统的Instrumentation这个类execStartActivity()来执行启动Activity的,我们继续可以看到下面的这个方法:
public ActivityResult execStartActivity( 、、、、、 try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); int result = ActivityManager.getService() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()),