天天看点

Android Small插件化框架--类加载实现解析

前言:上一篇已经分析了一下Android Framework的类加载的机制。基于上一篇的分析,这一篇我们来解析一下Small插件化框架的怎么Hook,来实现加载APK里面的类的。

一、总体流程图:

插件类的加载是Small插件化框架中重要的部分,我们首先列出Small类加载程序的总体流程图:

Android 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方法中的流程如下图:

Android Small插件化框架--类加载实现解析
  • 首先,注册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的对应关系示意图如下图:

Android Small插件化框架--类加载实现解析
  • 接下来,获取宿主程序签名信息,存放于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监听器,代码如下:
@Override
protected 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方法流程图如下:
Android Small插件化框架--类加载实现解析
  • 首先,判断sHasSetUp是否为true,如果sHasSetUp==true,调用net.wequick.small.Small.OnCompleteListener的onComplete方法;如果首次调用setUp方法,sHasSetUp==false,则调用Bundle.java的loadLaunchableBundles()方法,并设置sHasSetUp = true。
  • 接下来我们查看一下Bundle.java的loadLaunchableBundles()方法,loadLaunchableBundles()方法的示意图如下:
Android Small插件化框架--类加载实现解析

② 开启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字符串,其流程如下:

Android Small插件化框架--类加载实现解析
  • 声明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类,其类图如下:

Android Small插件化框架--类加载实现解析

④ ⑤ ⑥的实现

下面我们来看一下Bundle.java中的loadBundles(List bundles)方法:

Android Small插件化框架--类加载实现解析
  • 每个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()方法流程图:

Android Small插件化框架--类加载实现解析
  • 在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。

示意图如下:

Android Small插件化框架--类加载实现解析

首先,判断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中,其流程图如下:

Android Small插件化框架--类加载实现解析
  • 获取插件中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方法,代码如下:
@Override
public void postSetUp() {
    super.postSetUp();
    Collection<LoadedApk> apks = sLoadedApks.values();
    ......

    ClassLoader cl = app.getClassLoader();
    i = ;
    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 = ; 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()[];
    }

    //获取Element类构造方法的参数类型
    Class<?>[] types = sDexElementConstructor.getParameterTypes();

    //生成Element对象
    switch (types.length) {

        //当API版本为14_17时,Element类构造方法的参数个数为3
        case :
            if (types[].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 :
        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, , combined, , extraElements.length);
        System.arraycopy(original, , combined, extraElements.length, original.length);

    //插件数组放在后面
    } else {
        System.arraycopy(original, , combined, , original.length);
        System.arraycopy(extraElements, , 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。