Small框架类加载实现
来源:互联网 发布:淘宝网页编辑 编辑:程序博客网 时间:2024/06/10 00:28
Small框架类加载实现
一、总体流程图:
插件类的加载是Small插件化框架中重要的部分,我们首先列出Small类加载程序的总体流程图:
程序开始,通过逐层调用,首先开启一个LoadBundleThread线程,在LoadBundleThread线程中读取bundle.json字符串配置信息并解析。接下来,获取最新版本插件的so文件路径,生成其对应的BundleParser,并对存在更新的so文件签名进行CRC校验。然后,将使用loadDex方法生成插件对应DexFile对象的过程放入到Runnable线程中,将Runnable线程放入到List sIOActions中。接下来,将List sIOActions中Runnable线程放入到线程池中执行,这样就生成了插件所对应的DexFile对象。最后重要的,利用插件的DexFile对象生成插件Element对象,将插件程序产生的Element数组与宿主程序Element数组融合,这样就最终完成了LoadBundleThread线程。最后响应LoadBundleHandler的MSG_COMPLETE消息,执行Small.OnCompleteListener的onComplete方法,实现跳转到MainActivity。
二、详细讲解
① 开始:
准备工作
Small框架类加载程序的入口在LaunchActivity.java的onStart()方法中。在分析程序的入口之前,我们需要看一下Small的一些准备工作,这些准备工作是在Small框架的Application.java的onCreate()方法中开始的,onCreate()方法调用了Small.java中的preSetUp方法:
Small.preSetUp(this);
调用了small.java的preSetUp方法中的流程如下图:
首先,注册ActivityLauncher、ApkBundleLauncher、WebBundleLauncher三种BundleLauncher,实质上就是生成三种BundleLauncher的对象,并将其添加入List sBundleLauncher中。
获取宿主程序PackageInfo对象,再通过PackageInfo对象获得versionCode,如果宿主程序是第一次安装或者更新,那么这个versionCode与SharedPreferences中保存的versionCode将会不一致,如果这样,sIsNewHostApp设置为true,并将新的versionCode保存入SharedPreferences里。
注:Android系统为我们提供了很多服务管理的类,包括ActivityManager、PowerManager(电源管理)、AudioManager(音频管理)等。除此之外,还提供了一个PackageManger管理类,它的主要职责是管理应用程序包。 通过它,我们就可以获取应用程序信息,比如获取PackageInfo、ApplicationInfo、ActivityInfo等AnroidManifest.xml文件节点信息,它们与AnroidManifest.xml的对应关系示意图如下图:
接下来,获取宿主程序签名信息,存放于byte[][]sHostCertificates数组中。
判断task栈顶的Activity与宿主程序的默认启动Activity是否一致,如果不一致则调用setUp(context, null)方法。这个操作的主要作用是程序处于后台运行,很可能被强杀,当我们回到前台时,由于Activity task栈没有被清空,重新开启之前的Activity,此时调用setUp(context, null)方法,就是重新对程序进行初始化,保证Activity能够正常启动。
至此准备工作就完成,下面分析类加载程序的入口。
类加载程序入口
进入LaunchActivity,首先执行它的onCreate方法,如果sIsNewHostApp为true,即第一次打开应用,LaunchActivity显示“Preparing for first launching…”。
LaunchActivity类的onStart方法调用了Small.java中的setUp方法,并声明net.wequick.small.Small.OnCompleteListener监听器,代码如下:
@Overrideprotected void onStart() { super.onStart(); ...... //调用setUp Small.setUp(this, new net.wequick.small.Small.OnCompleteListener() { @Override public void onComplete() { ...... //打开main插件 Small.openUri("main", LaunchActivity.this); finish(); } });}
- Small.java中的setUp方法流程图如下:
首先,判断sHasSetUp是否为true,如果sHasSetUp==true,调用net.wequick.small.Small.OnCompleteListener的onComplete方法;如果首次调用setUp方法,sHasSetUp==false,则调用Bundle.java的loadLaunchableBundles()方法,并设置sHasSetUp = true。
接下来我们查看一下Bundle.java的loadLaunchableBundles()方法,loadLaunchableBundles()方法的示意图如下:
② 开启LoadBundleThread
在Bundle.java的loadLaunchableBundles()方法中,因为synchronous为false,sLoading仍然为false;开启LoadBundleThread线程,并声明LoadBundleHandler,因为此时sLoading==false,所以程序不执行while循环。
LoadBundleThread线程的run方法如下:
public void run() { // Instantiate bundle loadBundles(mContext); sLoading = false; sHandler.obtainMessage(MSG_COMPLETE).sendToTarget();}
可以看到,在run方法中首先调用了Bundle.java中的loadBundles(Context context)方法,之后就发现:sLoading设置为false,然后响应LoadBundleHandler类MSG_COMPLETE消息,实现net.wequick.small.Small.OnCompleteListener类的onComplete()方法的调用。于是我们的关键点又落在了Bundle.java中的loadBundles(Context context)方法之上。
③ 读取bundle.json字符串并解析
Bundle.java的loadBundles(Context context)方法的目的就是获得插件配置的json字符串,其流程如下:
声明patch目录下的bundle.json文件patchManifestFile,patch目录为/data/data/包名/files/,如果有添加新的插件,应该将新的bundle.json文件放置到此目录。
获得SharedPreferences中缓存的json字符串manifestJson,这个字符串是调用updateManifest方法后生成的。
如果缓存的manifestJson字符串不为空,则将manifestJson字符串写入patchManifestFile文件,并清空SharedPreferences中的存储;如果manifestJson字符串为空,并且patchManifestFile存在,读取patchManifestFile中的json字符串,将其复制给manifestJson;如果manifestJson字符串为空,并且patchManifestFile不存在,那就读取assets文件下的bundle.json文件的内容,将其赋值给manifestJson。
利用json字符串生成JSONObject对象manifestData:
JSONObject manifestData = new JSONObject(manifestJson);
- 最后调用Bundle.java中的loadBundles(manifest.bundles)方法。
注: 此过程使用的Bundle类和Manifest类都是bundle.json字符串对应的JavaBean类,其类图如下:
④ ⑤ ⑥的实现
下面我们来看一下Bundle.java中的loadBundles(List bundles)方法:
- 每个Bundle类对象都执行了Bundle.java中的prepareForLaunch()方法,它为插件类加载做了一些前提准备工作。打开Bundle.java里的prepareForLaunch()方法:
protected void prepareForLaunch() { if (mIntent != null) return; if (mApplicableLauncher == null && sBundleLaunchers != null) { for (BundleLauncher launcher : sBundleLaunchers) { if (launcher.resolveBundle(this)) { mApplicableLauncher = launcher; break; } } }}
Bundle.java中的prepareForLaunch()方法流程图:
在Bundle.java中的prepareForLaunch()方法中,遍历3种BundleLauncher,判断launcher.resolveBundle(this),如果为true,将launcher复制给mApplicableLauncher,插件对应的BundleLauncher的选定就在resolveBundle方法中实现的,那么我们先看看resolveBundle方法。
在resolveBundle方法中首先执行SoBundleLauncher类中的preloadBundle(bundle)方法进行判断。preloadBundle(bundle)方法的主要作用是,判断插件类型是否支持,如果支持,就更新插件so文件和BundleParser。如果插件文件被更新了,则进行进行签名CRC校验,如果校验成功,更新versionCode和versionName,返回true。
示意图如下:
首先,判断bundle是否支持,如果不支持,preloadBundle(bundle)方法返回false,resolveBundle方法也返回false;如果bundle支持,那么获取extractPath路径的so文件plugin,生成这个路径文件的BundleParser为parser,获取patch路径的so文件patch,生成这个路径的BundleParser为patchParser。然后令plugin和parser都设置为最新的,代码如下:
if (parser == null) { if (patchParser == null) { return false; } else { parser = patchParser; // use patch plugin = patch; }} else if (patchParser != null) { if (patchParser.getPackageInfo().versionCode <=parser.getPackageInfo().versionCode) { Log.d(TAG, "Patch file should be later than built-in!"); patch.delete(); } else { parser = patchParser; // use patch plugin = patch; }}
接下来设置bundle的BundleParser为parser。判断插件是否被修改,如果被修改则进行签名的CRC校验,如果校验通过,那么将插件中的C/C++本地.so文件复制到流程图中所示路径,并更新versionCode和versionName;如果校验不通过,则将此bundle设置失能。最后preloadBundle(bundle)方法返回true,resolveBundle方法也返回ture。那么,接下来则执行ApkBundleLauncher类中的loadBundle(bundle)方法。
⑦ 生成DexFile对象
ApkBundleLauncher类中的loadBundle(bundle)方法的主要作用是解析插件程序中的Activity, 将生成DexFile的线程添加到List中,其流程图如下:
获取插件中ActivityInfo信息,将这个插件所有的ActivityInfo信息都加入到List activities中,将List activities赋予mPackageInfo.activities。
接下来,获取LoadedApk的对象apk,LoadedApk类的定义如下:
private static class LoadedApk { //包名 public String packageName; //data/data/宿主包名/files/storage/插件包名 public File packagePath; //Application name public String applicationName; //data/app/宿主包名/lib/arm/.so public String path; //DexFile public DexFile dexFile; //data/data/宿主包名/files/storage/插件包名/bundle.dex public File optDexFile; //native library路径 public File libraryPath; // public boolean nonResources; /** no resources.arsc */}
将插件程序so文件路径path和bundle.dex文件路径等,作为LoadedApk的对象apk的成员变量。然后将此插件生成DexFile对象的过程放入到Runnable线程中,将此Runnable线程添加到List sIOActions中,将此pluginInfo.activities放入到ConcurrentHashMap
⑧ 线程池中执行sIOActions中的Runnable线程
接下来将sIOActions中的每个线程放入到线程池中进行执行,实现每个插件程序的DexFile的生成,在loadDex方法执行后,对应插件的类加载到内存。
⑨ Element数组的融合
- 然后,遍历sBundleLauncher中每个元素执行ApkBundleLauncher类中的postSetUp方法,代码如下:
@Overridepublic void postSetUp() { super.postSetUp(); Collection<LoadedApk> apks = sLoadedApks.values(); ...... ClassLoader cl = app.getClassLoader(); i = 0; int N = apks.size(); String[] dexPaths = new String[N]; DexFile[] dexFiles = new DexFile[N]; for (LoadedApk apk : apks) { dexPaths[i] = apk.path; dexFiles[i] = apk.dexFile; if (Small.getBundleUpgraded(apk.packageName)) { // If upgraded, delete the opt dex file for recreating if (apk.optDexFile.exists()) apk.optDexFile.delete(); Small.setBundleUpgraded(apk.packageName, false); } i++; } ReflectAccelerator.expandDexPathList(cl, dexPaths, dexFiles); ......}
- 代码中,首先获得宿主程序的类加载器,然后获得各个插件的so文件路径和对应的DexFile对象,将它们带入了下面方法:
ReflectAccelerator.expandDexPathList(cl, dexPaths, dexFiles)
执行到这里,终于到了我们最关键的地方,也是最激动人心的地方,这个方法中实现的最终目的就是我们能够通过宿主类加载器调用loadClass方法获得插件程序中的类,贴代码:
public static boolean expandDexPathList(ClassLoader cl, String[] dexPaths, DexFile[] dexFiles) { try { int N = dexPaths.length; Object[] elements = new Object[N]; for (int i = 0; i < N; i++) { String dexPath = dexPaths[i]; File pkg = new File(dexPath); DexFile dexFile = dexFiles[i]; elements[i] = makeDexElement(pkg, dexFile); } fillDexPathList(cl, elements); } catch (Exception e) { e.printStackTrace(); return false; } return true;}
- 首先调用makeDexElement方法,生成Element对象:
//生成Element对象private static Object makeDexElement(File pkg, DexFile dexFile) throws Exception { return makeDexElement(pkg, false, dexFile);}//生成Element对象private static Object makeDexElement(File pkg, boolean isDirectory, DexFile dexFile) throws Exception { //获取DexPathList类中的Element类 if (sDexElementClass == null) { sDexElementClass = Class.forName("dalvik.system.DexPathList$Element"); } //获取Element类的构造方法 if (sDexElementConstructor == null) { sDexElementConstructor = sDexElementClass.getConstructors()[0]; } //获取Element类构造方法的参数类型 Class<?>[] types = sDexElementConstructor.getParameterTypes(); //生成Element对象 switch (types.length) { //当API版本为14_17时,Element类构造方法的参数个数为3 case 3: if (types[1].equals(ZipFile.class)) { // Element(File apk, ZipFile zip, DexFile dex) ZipFile zip; try { zip = new ZipFile(pkg); } catch (IOException e) { throw e; } try { return sDexElementConstructor.newInstance(pkg, zip, dexFile); } catch (Exception e) { zip.close(); throw e; } } else { // Element(File apk, File zip, DexFile dex) return sDexElementConstructor.newInstance(pkg, pkg, dexFile); } //当API版本为18_时,Element类构造方法的参数个数为4 case 4: default: // Element(File apk, boolean isDir, File zip, DexFile dex) if (isDirectory) { return sDexElementConstructor.newInstance(pkg, true, null, null); } else { return sDexElementConstructor.newInstance(pkg, false, pkg, dexFile); } }}
- 接下来,将生成插件的Element数组加入到宿主程序Element数组中,调用ReflectAccelerator.java中的fillDexPathList方法:
private static void fillDexPathList(ClassLoader cl, Object[] elements) throws NoSuchFieldException, IllegalAccessException { //获取pathList属性 if (sPathListField == null) { sPathListField = getDeclaredField(DexClassLoader.class.getSuperclass(), "pathList"); } //实例化pathList属性 Object pathList = sPathListField.get(cl); //获取dexElements属性 if (sDexElementsField == null) { sDexElementsField = getDeclaredField(pathList.getClass(), "dexElements"); } expandArray(pathList, sDexElementsField, elements, true);}
ReflectAccelerator.java中的expandArray方法:
private static void expandArray(Object target, Field arrField, Object[] extraElements, boolean push) throws IllegalAccessException { //原来的 Element 数组 Object[] original = (Object[]) arrField.get(target); //用来存储最终结合后的 Element 数组,长度等于 original 和插件 Element 数组长度之和 Object[] combined = (Object[]) Array.newInstance( original.getClass().getComponentType(), original.length + extraElements.length); //插件 Element 数组放在前面 if (push) { System.arraycopy(extraElements, 0, combined, 0, extraElements.length); System.arraycopy(original, 0, combined, extraElements.length, original.length); //插件数组放在后面 } else { System.arraycopy(original, 0, combined, 0, original.length); System.arraycopy(extraElements, 0, combined, original.length, extraElements.length); } //替换数组 arrField.set(target, combined);}
- 用插件程序中类生成的 Element 对象就添加到了宿主程序 Element 数组中了,当程序调用 DexPathList 类中 findClass 方法遍历数组的时候,就能够找到插件程序对应的 Element 对象,进而就可以调用loadClass方法实现类的实例化。
⑩ LoadBundleThread线程结束
到此Bundle.java中的loadBundles(bundles)方法就执行完了,Bundle.java中的loadBundles(context)方法也结束。
继续执行LoadBundleThread线程run方法下面的程序,sLoading = false执行如下内容:
if (sUIActions != null) { for (Runnable action : sUIActions) { action.run(); } sUIActions = null;}
如果第一次启动,因为sUIAction为null,上面代码没有作用。
⑪ 给LoadBundleHandler发送MSG_COMPLETE消息
接下来执行如下代码:
sHandler.obtainMessage(MSG_COMPLETE).sendToTarget();
我们不妨先看一下Bundle.java中的LoadBundleHandler类的定义:
private static class LoadBundleHandler extends Handler { private Small.OnCompleteListener mListener; public LoadBundleHandler(Small.OnCompleteListener listener) { mListener = listener; } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_COMPLETE: if (mListener != null) { mListener.onComplete(); } mListener = null; sThread = null; sHandler = null; break; } }}
LoadBundleHandler类的handleMessage方法中调用了net.wequick.small.Small.OnCompleteListener类的onComplete()方法。
响应LoadBundleHandler的MSG_COMPLETE消息,调用Small.OnCompleteListener的onComplete方法,启动MainActivity。
- Small框架类加载实现
- Android Small插件化框架--类加载实现解析
- Android Small插件化框架--Android应用类加载机制
- Small框架
- android small资源加载
- 学习入门small框架
- Small插件框架源码解析-相关类介绍
- Small Word:物理引擎框架
- 从硬盘加载Damn Small Linux
- Android插件化(使用Small框架)
- small插件框架学习的分享
- Android插件化(使用Small框架)
- Android Small框架增量升级方案
- Android Small框架增量升级方案
- Android Small框架增量升级方案
- Android插件化(使用Small框架)
- small框架sample中的几个跳转api
- 插件化技术-Small插件框架
- Linux新手安装Ubuntu 16.04之后,如何快速搭建一个舒服的开发环境
- 细聊分布式ID生成方法
- iOS开发之UIScrollViewDelegate详解
- zstuoj 4243 牛吃草
- JMeter学习-007-JMeter 断言实例之一 - 响应断言
- Small框架类加载实现
- dijkstra最短路算法
- JAVA tutorial官方案例:学习笔记
- RabbitMQ简介
- 实现双向固定表头的表格
- iOS 获取手机的ip地址
- 最新stm32 USB库获取
- Angular(七)_服务Service,
- AR遮挡研究