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的。


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