android 基於dex的插件化開發
- 2021 年 12 月 20 日
- 筆記
Android裡邊可以用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工具將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://github.com/gangmiangongjue/Android-Dynamic-Plugin-demo
好用的話請點個星星~不好用歡迎提case,謝謝