android 基於dex的外掛化開發

長尾景虎發表於2021-12-20

安卓裡邊可以用DexClassLoader實現動態載入dex檔案,通過訪問dex檔案訪問dex中封裝的方法,如果dex檔案本身還呼叫了native方法,也就間接實現了runtime呼叫native方法,這一流程主要包括:構建dex和so檔案、在主工程新增動態呼叫程式碼、移除dex的module,將dex和so push到手機的指定路徑

 

構建dex和so檔案

首先在主工程裡邊新建一個名為testdepence的module,新建一個add類,在add類裡邊我們建立一個單例方法和一個native方法

public class Add {
    private final static String TAG = "Add";
    private static Add add = null;
    static {
        System.loadLibrary("anclivejni");
    }

    public static Add getInstance(){
        Log.d(TAG, "getInstance: ");
        if (add == null){
            add = new Add();
        }
        return add;
    }
    
    public native void init(byte[] data);


}

然後新建jni.cpp,實現init方法,這裡我們就只是列印一個log


#define TAG "SXF"
#define LOG(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)

extern
"C" JNIEXPORT void JNICALL Java_com_example_testdepence_Add_init(JNIEnv * env, jobject thiz ,jbyteArray array) { LOG("call init from native"); // TODO: implement init() }

然後在gradle task裡邊選擇 assembleRelease,執行,在testdepence module outputs/aar下會生成一個aar檔案,把它的字尾改成zip,解壓,得到classes.jar和so檔案,然後用/your sdk dir/build-tools/plarform id下的dx(build 32 中已經把dx移除了,只能使用d8)工具將jar轉換為dex檔案,就ok了

## 在主工程新增動態呼叫程式碼

新建一個DynamicLoader類,新增使用DexClassLoader反射呼叫dex檔案的程式碼

public class DynamicLoader {
    private final static String TAG = "DanymicLoader";
    private static Object handle;
    /**
     * 載入dex檔案中的class,並呼叫其中的方法
     * 這裡由於是載入 jar檔案,所以採用DexClassLoader
     * 下面開始載入dex class
     */
    private static Method getMethod(Context context,String methodName,Class<?>... methodArgs) {
        File cacheFile = context.getCacheDir();
        Log.d(TAG, "loadDexClass file path: " + cacheFile.getAbsolutePath());
        String internalPath = cacheFile.getAbsolutePath() + File.separator + "classes.dex";
        File desFile = new File(internalPath);
        Method method = null;
        if (desFile.exists()) {
            DexClassLoader dexClassLoader = new DexClassLoader(internalPath//dex檔案路徑, cacheFile.getAbsolutePath()//dex檔案解壓路徑, cacheFile.getAbsolutePath()//so的搜尋路徑, context.getClass().getClassLoader());
            try {
                Class<?> libClazz = dexClassLoader.loadClass("com.example.testdepence.Add");
                Method getInstance = libClazz.getMethod("getInstance");
                getInstance.setAccessible(true);
                handle = getInstance.invoke(null);
                if (handle ==null){
                    Log.d(TAG, "getInstance error handle is null !!! ");
                }else {
                    Log.d(TAG, "getInstance success!!! ");
                }

                method = libClazz.getMethod(methodName,methodArgs);
                method.setAccessible(true);

            } catch (Exception e) {
                e.printStackTrace();
                Log.d(TAG, "getMethod error: " + e.getMessage());
                return method;
            }
        }else{
            Log.d(TAG, "aar not exist!!!");
        }
        return method;
    }

    public static int dynamicInit(Context context,byte[] data){
        Method initHandle = getMethod(context,"init",byte[].class);
        if (initHandle !=null && handle != null && data != null){
            try {
                initHandle.invoke(handle,data);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
                return -1;
            }
        }
        return 0;
    }
}

然後在activity裡邊呼叫之

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DynamicLoader.dynamicInit(this,new byte[1024]);
    }

之後在setting.gradle裡邊將testdepence裡這個module移除,編譯app,準備執行

將dex和so push到手機的指定路徑

按照“internalPath//dex檔案路徑, cacheFile.getAbsolutePath()//dex檔案解壓路徑, cacheFile.getAbsolutePath()//so的搜尋路徑”,將dex和so push到手機對應的目錄,然後就可以開啟app執行啦。

 

原始碼見github:https://github.com/gangmiangongjue/Android-Dynamic-Plugin-demo

好用的話請點個星星~不好用歡迎提case,謝謝

 

相關文章