Android 插件化开发<一;
Posted 清浅岁月
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 插件化开发<一;相关的知识,希望对你有一定的参考价值。
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);
以上是关于Android 插件化开发<一;的主要内容,如果未能解决你的问题,请参考以下文章
Android 插件化多开原理 | 使用插件化技术的恶意应用 | 插件化的其它风险 | 应用开发推荐方案
Android 插件化插件化技术弊端 ( 恶意插件化程序的解决方向 | 常用的插件化虚拟引擎 )
Android 插件化使用插件化引擎对应用进行重打包的恶意软件特征 ( 检测困难 | 成本低 | 恶意插件可更换 | 容易传播 )