Android 使用动态加载框架DL进行插件化开发
如有转载,请声明出处: 时之沙: http://blog.csdn.net/t12x3456 (来自时之沙的csdn博客)
概述:
随着应用的不断迭代,应用的体积不断增大,项目越来越臃肿,冗余增加.项目新功能的添加,无法确定与用户匹配性,发生严重异常往往牵一发而动全身,只能紧急发布补丁版本,强制用户进行更新.结果频繁的更新,反而容易降低用户使用黏性.或者是公司业务的不断发展,同系的应用越来越多,传统方式需要通过用户量最大的主项目进行引导下载并安装.
怎么办?参考浏览器-插件开发模式:
一. 来可以将自己的应用分拆,某些功能可以在插件中实现,用到时再进行下载,而且不用安装. 如果有新功能的添加,不需要更新应用,只要预留插件管理,我们就可以通过添加插件的方式,动态更新自己的应用,该功能需要改进或扩展,更新插件即可,无需频繁安装或卸载(容易造成用户反感).
二. 对应同系应用,正常的引流方式只能引导用户进行新应用的下载和安装,如果使用插件化开发,则无需安装应用,关闭插件功能也十分方便,省去应用安装和卸载的过程,可以实现无缝引流.
这里要向大家推荐一个开源的动态加载框架 DL, 该项目由 singwhatiwanna发起,目前一共有三个人开发,我有幸成为了其中的contributor.
如果你对DL动态加载框架还不熟悉,建议先看一下这篇文章:
APK动态加载框架DL解析 http://blog.csdn.net/singwhatiwanna/article/details/39937639
如果你看过之后还会不太清晰,请看下DL插件化框架的全景图,如下
这里我主要向大家介绍一下利用DL框架进行开发的具体步骤:
1. 首先我们需要从github上获取项目代码:
https://github.com/singwhatiwanna/dynamic-load-apk
这里我们可以看到下载后的目录如下
lib目录就是我们的公共插件库
sample目录是对应的demo, 具体的工程可以参照上面的DL全景图中的三种模式:
由于一般项目中,代码管理和开发团队相对独立, 一般插件工程的团队是很难接触主项目团队的代码.因此,这里主要以第一种,也是我们最为推荐的方式进行, 采用插件不依赖宿主的方式进行开发. 不需要两个团队过多的交互,开发效率相对较高.
2 导入lib工程,如下所示:
可以看到bin目录下的dl-lib.jar, 如果我们需要对lib工程进行修改,重新build获取对应的dl-lib.jar即可
3. 插件工程的开发, 导入demo中的main-plugin工程
首先还是要强调插件开发的注意事项,以免出现不必要的错误
插件也需要引用DL的jar包,但是不能放入到插件工程的libs目录下面,换句话说,就是插件编译的时候依赖jar包但是打包成apk的时候不要把jar包打进去,这是因为,dl-lib.jar已经在宿主工程中存在了,如果插件中也有这个jar包,就会发生类链接错误,原因很简单,内存中有两份一样的类,重复了。至于support-v4也是同样的道理。对于eclipse很简单,只需要在插件工程中创建一个目录,比如external-jars,然后把dl-lib.jar和support-v4.jar放进去,同时在.classpath中追加如下两句即可:
<classpathentry kind="lib" path="external-jars/dl-lib.jar"/> <classpathentry kind="lib" path="external-jars/android-support-v4.jar"/>然后是插件开发中的具体步骤
(1) 如果原有的为Activity,这里需要改为继承DLBasePluginActivity,如果原来为FragmentActivity,那么需要继承DLBasePluginFragmentActivity, for example:
public class MainActivity extends DLBasePluginActivity
TestFragmentActivity extends DLBasePluginFragmentActivity
(2) 如果需要插件独立安装运行, 只要将jar放到libs下面即可,若支持动态加载,仍需按上述注意事项加入 exteral-jars中
(3)插件所需要权限需要在宿主工程中声明
如果是实际开发,一般为从服务器获取插件,这里我们方便自己调试演示,将运行生成对应的插件apk,放入sd卡上的DynamicLoadHost目录中
4. 宿主工程中将上述生成的dl-lib.jar加入libs即可,如下所示为demo中提供的宿主工程
在宿主工程中,首先我们需要获取要调用的插件apk对应的MainActivity,DL的demo中插件路径为 sd卡上的DynamicLoadHost目录,没有的话需要创建,或者根据自己需求进行修改.
String pluginFolder = Environment.getExternalStorageDirectory() + "/DynamicLoadHost"; File file = new File(pluginFolder); File[] plugins = file.listFiles(); if (plugins == null || plugins.length == 0) { mNoPluginTextView.setVisibility(View.VISIBLE); return; } for (File plugin : plugins) { PluginItem item = new PluginItem(); item.pluginPath = plugin.getAbsolutePath(); item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath); if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) { item.launcherActivityName = item.packageInfo.activities[0].name; } mPluginItems.add(item); }
接着是调起响应的apk,这时需要使用dl-lib.jar:
(1)通过Class.forName的方式获取我们需要调用的插件apk中MainActivity的class对象
(2) 就上面提到的,我们需要判断该对象继承自DLProxActivity还是DLProxFragmentActivity,得到对应的代理class对象
(3)使用对应的代理class对象调起插件apk
PluginItem item = mPluginItems.get(position); Class<?> proxyCls = null; try { Class<?> cls = Class.forName(item.launcherActivityName, false, DLClassLoader.getClassLoader(item.pluginPath, getApplicationContext(), getClassLoader())); if (cls.asSubclass(DLBasePluginActivity.class) != null) { proxyCls = DLProxyActivity.class; } } catch (ClassNotFoundException e) { e.printStackTrace(); Toast.makeText(this, "load plugin apk failed, load class " + item.launcherActivityName + " failed.", Toast.LENGTH_SHORT).show(); } catch (ClassCastException e) { // ignored } finally { if (proxyCls == null) { proxyCls = DLProxyFragmentActivity.class; } Intent intent = new Intent(this, proxyCls); intent.putExtra(DLConstants.EXTRA_DEX_PATH, mPluginItems.get(position).pluginPath); startActivity(intent); }
最后运行宿主工程 main-host,就可以看到最终的效果:
相信大家经过如上步骤,可以对DL动态加载框架进行开发有了一定了解.目前DL框架仍在不断扩展中,欢迎对我们的项目进行star,fork,或者提出宝贵的建议.,如有问题请及时反馈,我们会在后续版本中进行修复或者改进.