[Android plug-in] "Plug-in" plug-in framework (class loader creation | resource loading)

[Android plug-in] series of blogs:


Article Directory



Refer to the implementation ideas given in [Android plug-in] "Plug-in" plug-in framework (principles and realization ideas) , and gradually realize the "Plug-in" plug-in framework;




One, create a core dependency library



Create the "Android Library" dependent library as the core dependent library of the "plug-in" framework;

Insert picture description here

"Host" module application, which rely on "Instrumentation" type plug core framework libraries, relying on the frame core library management "plug-in" module is compiled into a package apk file;





Two, create a class loader



Create DexClassLoader , use its constructor to create, you need to pass in four parameters to the constructor;

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!");
    }
}


DexClassLoader constructor parameter description:

① String dexPath : Load path of the plug-in package;

② String optimizedDirectory : The cache path after decompression of the apk plugin package specified by the developer;

③ String librarySearchPath : the search path of the function library, can be set to empty, ignored;

④ ClassLoader parent : the parent class loader of the DexClassLoader loader;


Create the cache path after decompression of the plug-in package: Note that the path corresponding to the String optimizedDirectory parameter must be private;

// DexClassLoader 的 optimizedDirectory 操作目录必须是私有的
// ( 模式必须是 Context.MODE_PRIVATE )
File optimizedDirectory = context.getDir("plugin", Context.MODE_PRIVATE);

Create a class loader: pass in the above  4 4 4 Parameters, create a class loader;

// 创建 DexClassLoader
mDexClassLoader = new DexClassLoader(
        loadPath, // 加载路径
        optimizedDirectory.getAbsolutePath(), // apk 解压缓存目录
        null,
        context.getClassLoader() // DexClassLoader 加载器的父类加载器
);


Note: When the class is loaded, it will only be loaded once, if there are duplicate classes, it will not be loaded repeatedly;

The main function of BootClassLoader is to load bytecode class objects in the JDK;

The main function of DexClassLoader and PathClassLoader is to load bytecode class objects in Android and imported third-party libraries;





Three, load resources



AssetManager needs to be used when loading resources , but its constructor is hidden and annotated by @Hide, developers cannot call it directly, and need to use reflection to call;

Create an AssetManager object through reflection: pay attention to exception capture;

// 加载资源
try {
    // 通过反射创建 AssetManager
    AssetManager assetManager = AssetManager.class.newInstance();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
}

After creating the AssetManager object, call the addAssetPath method to add the path of the resource, which is used to load the resource file under the plugin package path;

addAssetPath is also a hidden method, and it is also necessary to use reflection to call this method;

// 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法
Method addAssetPathMethod = assetManager.
        getClass().
        getDeclaredMethod("addAssetPath");
// 调用反射方法
addAssetPathMethod.invoke(assetManager, loadPath);


AssetManager sample code:

public final class AssetManager implements AutoCloseable {
    /**
     * @hide
     */
    @UnsupportedAppUsage
    public AssetManager() {
    }
    
    /**
     * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage
    public int addAssetPath(String path) {
        return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
    }
}


Get the Resources resource object: Create the Resources resource object through the AssetManager object after the plugin resources are loaded above;

// 获取资源
mResources = new Resources(
        assetManager,
        context.getResources().getDisplayMetrics(),
        context.getResources().getConfiguration()
);

The passed DisplayMetrics metrics and Configuration config parameters are obtained from the context of calling the plugin package;


Sample code for loading resources:

First, create the AssetManager object through reflection;
then, call and execute the addAssetPath method of the AssetManager object through reflection to load the plugin package resources;
finally, call the Resources constructor, create the resource, and pass in the AssetManager object and context-related parameters;

// 加载资源
try {
    // 通过反射创建 AssetManager
    AssetManager assetManager = AssetManager.class.newInstance();
    
    // 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法
    Method addAssetPathMethod = assetManager.
            getClass().
            getDeclaredMethod("addAssetPath");
            
    // 调用反射方法
    addAssetPathMethod.invoke(assetManager, loadPath);
    
    // 获取资源
    mResources = new Resources(
            assetManager,
            context.getResources().getDisplayMetrics(),
            context.getResources().getConfiguration()
    );
    
} catch (IllegalAccessException e) {
    // 调用 AssetManager.class.newInstance() 反射构造方法异常
    e.printStackTrace();
} catch (InstantiationException e) {
    // 调用 AssetManager.class.newInstance() 反射构造方法异常
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    // getDeclaredMethod 反射方法异常
    e.printStackTrace();
} catch (InvocationTargetException e) {
    // invoke 执行反射方法异常
    e.printStackTrace();
}





Fourth, the complete code of the plug-in manager



The complete code of the plugin manager:

package com.example.plugin_core;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.res.AssetManager;
import android.content.res.Resources;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

/**
 * 插件化框架核心类
 */
public class PluginManager {

    /**
     * 类加载器
     * 用于加载插件包 apk 中的 classes.dex 文件中的字节码对象
     */
    private DexClassLoader mDexClassLoader;

    /**
     * 从插件包 apk 中加载的资源
     */
    private Resources mResources;

    /**
     * 插件包信息类
     */
    private PackageInfo mPackageInfo;

    /**
     * 加载插件的上下文对象
     */
    private Context mContext;

    /**
     * PluginManager 单例
     */
    private static PluginManager instance;

    private PluginManager(){

    }

    /**
     * 获取单例类
     * @return
     */
    public static PluginManager getInstance(){
        if (instance == null) {
            instance = new PluginManager();
        }
        return instance;
    }

    /**
     * 加载插件
     * @param context 加载插件的应用的上下文
     * @param loadPath 加载的插件包地址
     */
    public void loadPlugin(Context context, String loadPath) {
        this.mContext = context;

        // DexClassLoader 的 optimizedDirectory 操作目录必须是私有的
        // ( 模式必须是 Context.MODE_PRIVATE )
        File optimizedDirectory = context.getDir("plugin", Context.MODE_PRIVATE);

        // 创建 DexClassLoader
        mDexClassLoader = new DexClassLoader(
                loadPath, // 加载路径
                optimizedDirectory.getAbsolutePath(), // apk 解压缓存目录
                null,
                context.getClassLoader() // DexClassLoader 加载器的父类加载器
        );

        // 加载资源
        try {
            // 通过反射创建 AssetManager
            AssetManager assetManager = AssetManager.class.newInstance();

            // 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法
            Method addAssetPathMethod = assetManager.
                    getClass().
                    getDeclaredMethod("addAssetPath");

            // 调用反射方法
            addAssetPathMethod.invoke(assetManager, loadPath);

            // 获取资源
            mResources = new Resources(
                    assetManager,
                    context.getResources().getDisplayMetrics(),
                    context.getResources().getConfiguration()
            );

        } catch (IllegalAccessException e) {
            // 调用 AssetManager.class.newInstance() 反射构造方法异常
            e.printStackTrace();
        } catch (InstantiationException e) {
            // 调用 AssetManager.class.newInstance() 反射构造方法异常
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // getDeclaredMethod 反射方法异常
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // invoke 执行反射方法异常
            e.printStackTrace();
        }
    }
}





Five, blog resources



Blog resources: