插件相关介绍
首先插件只是一个逻辑概念,而不是什么技术标准,主要包含如下几个意思:
- 插件不能独立运行,必须运行一个宿主程序中,宿主程序去调用插件(ps:微信的游戏算不算插件?感觉算是一种)
- 插件一般情况下可以独立安装,android中就可以设计一个apk
- 宿主程序中可以管理插件,比如添加,删除,禁用等。
- 宿主程序应该保证插件向下兼容,新的宿主程序应该兼容老的插件
新浪微博的主题就是通过插件实现的切换。
由于ClassLoader具有动态装载程序的特点,因此可以使用此技术来一种插件框架。下面就是实现的细节。
使用DexClassLoader实现插件框架
大家了解高焕堂老师的EIT造型吗?如果不了解的话可以去网上搜一下,这种造型可以用来设计一个插件,一个框架,甚至一个平台。
高老师总结的确实精炼,容易理解,很多设计模式都是在这个EIT的之上或者变型生做的设计。
那么今天咱们用到EIT造型了吗?当然用到了,咱们直接上代码吧。
上一篇博客中是通过反射调用插件中的类的方法的,今天咱们要把接口作为联系主程序和插件程序的桥梁。
要实现住程序通过接口调用插件的程序,那么主程序和插件程序必须有相同的接口文件,也就是两个程序里都有接口的java类文件。
首先,在主程序里定义一个接口文件,然后原样的copy到插件程序中去,接口如下:
package com.suchangli.plugin;
public interface CommonInterface {
int function1(int a, int b);
}
包结构如下所示:
宿主程序:
插件程序
主程序的代码修改成使用接口:
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- useDexClassLoader2();
- }
- @SuppressLint("NewApi") private void useDexClassLoader2(){
- //创建一个意图,用来找到指定的apk
- Intent intent = new Intent("com.suchangli.android.plugin", null);
- //获得包管理器
- PackageManager pm = getPackageManager();
- List<ResolveInfo> resolveinfoes = pm.queryIntentActivities(intent, 0);
- //获得指定的activity的信息
- ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;
- //获得包名
- String pacageName = actInfo.packageName;
- //获得apk的目录或者jar的目录
- String apkPath = actInfo.applicationInfo.sourceDir;
- //dex解压后的目录,注意,这个用宿主程序的目录,android中只允许程序读取写自己
- //目录下的文件
- String dexOutputDir = getApplicationInfo().dataDir;
- //native代码的目录
- String libPath = actInfo.applicationInfo.nativeLibraryDir;
- //创建类加载器,把dex加载到虚拟机中
- DexClassLoader calssLoader = new DexClassLoader(apkPath, dexOutputDir, libPath,
- this.getClass().getClassLoader());
- //利用反射调用插件包内的类的方法
- try {
- Class<?> clazz = calssLoader.loadClass(pacageName+".Plugin1");
- CommonInterface obj = (CommonInterface)clazz.newInstance();
- int ret = obj.function1(1, 13);
- Log.i("Host", "return result is " + ret);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (InstantiationException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- }
- }
也就这几句代码不同:
插件程序的类现接口:
- package com.suchangli.plugin1;
- import com.suchangli.plugin.CommonInterface;
- public class Plugin1 implements CommonInterface{
- public int function1(int a, int b){
- return a+b;
- }
- }
直接安装两个程序,调用的时候会报这种错误:
copy过去报错,并且这种方式也不太现实,因为提供给插件开发者的时候肯定是以jar包的形式进行提供,而不是以原文件的形式提供,
更何况现在还报错。
究其原因是什么呢?
其实是这样的,这个java文件被当做程序的一部分(本来就是一部分)(
jar包是以外部jar的方式添加进去的,外部jar包会作为程序的一部分被最终的程序文件中,也会报同样的错误),从而使得在主程序和插件程序中存在包名相同但验证码不同的类文件。
导出jar包,这个大家应该都会,不会的到网上搜一下。
把jar包放进插件的libs文件加下
引用jar包
使用红色框的“Add Libary”,而不是蓝色框的“Add External JARs”.
如果还是不行就通过这种方式:
再重新安装一次插件,运行一次主程序,结果如下:
主程序如何搜索到插件的,是在插件程序的menifest.xml文件中,定义了一个activity,定义了一个action:
这样主程序就可以使用PacageManager类的queryIntentActivites()方法查询相关的插件程序列表了。
程序主题插件的实现
在主程序中添加如下一个方法:
- private void useDexClassloader3(){
- //创建一个意图,用来找到指定的apk
- Intent intent = new Intent("com.suchangli.android.plugin", null);
- //获得包管理器
- PackageManager pm = getPackageManager();
- List<ResolveInfo> resolveinfoes = pm.queryIntentActivities(intent, 0);
- //获得指定的activity的信息
- ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;
- //获得包名
- String pacageName = actInfo.packageName;
- try {
- Resources res = pm.getResourcesForApplication(pacageName);
- int id = 0;
- id = res.getIdentifier("ic_launcher", "drawable", pacageName);
- Log.i("", "resId is " + id);
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }
- }
从上面的代码可以看出,我们能获得插件程序的资源文件的id,那么资源文件就很容易获得了,使用插件的形式进行主题替换就会很容易实现了。
期待大家能在DexClassLoader的基础上开发出一个开源的插件框架,或者通用的程序主题替换程序架构。
如果想一块写这种开源框架的可以和我联系,我能帮忙的尽量帮忙。谢谢大家了。