|
在開發Android App的過程當中,可能希望實現插件式軟件架構,將一部分代碼以另外一個APK的形式單獨發佈,而在主程序中加載並執行這個APK中的代碼。 實現這個任務的一般方法是:- // 加載類cls
- Context pluginContext = mainContext.createPackageContext(PLUGIN_PKG, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
- ClassLoader loader = pluginContext.getClassLoader();
- Class<?> cls = loader.loadClass(CLASS_NAME);
- // 通過反射技術,調用cls中的方法,下面是一個示例,實際代碼因情況而定
- Object obj = cls.newInstance();
- Method method = cls.getDeclaredMethod("someMethod");
- method.invoke(obj);
複製代碼 但是,這個方法在Android 4.1及之後的系統中存在一些問題:對於收費應用,Google Play會將其安裝在一個加密目錄之下(具體就是/data/app-asec),而不是一個普通目錄之下(具體就是/data/app);安裝在加密目錄中的應用,我們是無法使用上述方法來加載並執行代碼的;而實際情況是,我們經常就是依靠插件應用來收費的。
解決上述問題的一個方案是:將插件的二進制代碼拷貝到SD卡中,主程序從SD卡中加載並執行其代碼。
實現這個任務的具體方法是:- Class<?> cls = null;
- try {
- // 嘗試第一種方法
- cls = loadClass1(mainContext, pkg, entryCls);
- } catch (Exception e) {
- // 嘗試第二種方法
- cls = loadClass2(mainContext, pkg, entryCls);
- }
- // 示例代碼
- Object obj = cls.newInstance();
- Method method = cls.getDeclaredMethod("someMethod");
- method.invoke(obj);// 第一種加載方法
- private Class<?> loadClass1(Context mainContext, String pkg, String entryCls) throws Exception {
- Context pluginContext = mainContext.createPackageContext(pkg, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
- ClassLoader loader = pluginContext.getClassLoader();
- return loader.loadClass(entryCls);
- }
- // 第二種加載方法
- private Class<?> loadClass2(Context mainContext, String pkg, String entryCls) throws Exception {
- Context pluginContext = mainContext.createPackageContext(pkg, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
- String path = generatePluginDexPath(mainContext, pkg);
- ensureFileExist(pluginContext, pkg, path);
- // cacheDir必須是主程序的私有目錄,否則DexClassLoader可能會拒絕加載
- String cacheDir = mainContext.getApplicationInfo().dataDir;
- ClassLoader parentLoader = pluginContext.getClassLoader();
- DexClassLoader loader = new DexClassLoader(path, cacheDir, null, parentLoader);
- return loader.loadClass(entryCls);
- }
- // 獲取程序版本號
- private int getVersionCode(Context context, String pkg) {
- PackageInfo info = null;
- int versionCode = 0;
- try {
- info = context.getPackageManager().getPackageInfo(pkg, PackageManager.GET_ACTIVITIES);
- versionCode = info.versionCode;
- } catch (Exception e) {}
- return versionCode;
- }
- // 獲取插件二進制代碼的存儲位置,注意做好版本控制;路徑必須是以.dex結束,否則加載會出問題
- private String generatePluginDexPath(Context context, String pkg) {
- int version = getVersionCode(context, pkg);
- String path = getMyAppPath() + ".classes/" + pkg + version + ".dex";
- return path;
- }
- // 主程序在SD卡上的數據目錄
- private String getMyAppPath() {
- return Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyApp/";
- }// 拷貝插件的二進制代碼到SD卡
- private void ensureFileExist(Context pluginContext, String pkg, String path) throws Exception {
- File file = new File(path);
- if(file.exists()) return;
- file.getParentFile().mkdirs();
- Resources res = pluginContext.getResources();
- int id = res.getIdentifier("classes", "raw", pkg);
- InputStream in = res.openRawResource(id);
- FileOutputStream out = new FileOutputStream(file);
- try {
- byte[] buffer = new byte[1024 * 1024];
- int n = 0;
- while((n = in.read(buffer)) > 0) {
- out.write(buffer, 0, n);
- } out.flush();
- } catch (IOException e) {
- in.close();
- out.close();
- }
- }
複製代碼 插件工程這邊也需要做相應的修改:
1.編譯插件工程;
2.將bin目錄之下的classes.dex拷貝到/res/raw目錄之下;
3.重新編譯插件工程;
4.發佈插件APK。
http://www.cnblogs.com/frydsh/archive/2012/12/21/2828561.html |
|