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。

1 0