Android动态加载代码技术
2015-07-15 15:48
459 查看
在开发Android App的过程当中,可能希望实现插件式软件架构,将一部分代码以另外一个APK的形式单独发布,而在主程序中加载并执行这个APK中的代码。
实现这个任务的一般方法是:
但是,这个方法在Android 4.1及之后的系统中存在一些问题:对于收费应用,Google Play会将其安装在一个加密目录之下(具体就是/data/app-asec),而不是一个普通目录之下(具体就是/data/app);安装在加密目录中的应用,我们是无法使用上述方法来加载并执行代码的;而实际情况是,我们经常就是依靠插件应用来收费的。
解决上述问题的一个方案是:将插件的二进制代码拷贝到SD卡中,主程序从SD卡中加载并执行其代码。
实现这个任务的具体方法是:
插件工程这边也需要做相应的修改:
1.编译插件工程;
2.将bin目录之下的classes.dex拷贝到/res/raw目录之下;
3.重新编译插件工程;
4.发布插件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。
相关文章推荐
- Android px,dp ,sp之间的互相转换
- Android中visibility属性VISIBLE、INVISIBLE、GONE的区别
- Android中的动态加载机制
- [Android课堂笔记] 2D Graphic图形绘制全解析
- Android 缓存机制
- Android Studio Gradle项目中添加JNI so文件
- 【Android】ViewGroup全面分析
- Android 应用程序的图标 (Icon) 应该设计成多大
- Android System Property
- android-percent-support-lib-sample
- Android利用ContentResolver查询的三种方式
- Android 开源框架Universal-Image-Loader完全解析(三)---源代码解读
- Android开发之Touch事件的分发机制
- Android 开源框架Universal-Image-Loader完全解析(二)--- 图片缓存策略详解
- Android Studio中自定义快捷输入块
- Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用
- Android圆形散开动画
- 关于android布局的两个属性dither和tileMode
- Android抽象布局——include、merge 、ViewStub
- Android studio 去掉没用的import