Android 插件式开发
2016-01-30 17:12
537 查看
Android插件式开发,顾名思义,就是有多个程序,其中有一个主程序,其它程序我们称之为插件。主程序在系统中有图标,其它程序在系统中没有图标,但可以独立安装。其它程序都被主程序所加载。发布的时候只需发布主程序就可以了。其它程序可以根据对服务器的请求,在主程序中动态扩展与升级。以下主程序我们称之为Host程序,插件程序称之为Plugin程序。下面有提供下载源码,附上贴图。那我们来看看如何实现插件式开发吧。
1:将一个程序变为Host程序。新建一个Android项目,取名为testHost。
2:将一个程序变为Plugin程序。再新建一个Android项目取名为testPlugin。将testPlugin里面AndroidManifest.xml文件里面的1,2处的代码去掉。
3:让Plugin程序能够被Host程序搜索到。我们把testHost和testPlugin都安装在系统中,那么Host程序如何去找到系统中所有符合条件的插件呢?我们可以给每个Plugin程序都定义一个action,那么Host程序就可以根据这个action搜索到系统中所有符合条件的Plugin。将步骤2中testPlugin里面AndroidManifest.xml文件里面代码替换如下,也就是增加了一个action-->com.example.testplugin.client:
5:Host程序获取到Plugin程序的名称版本信息。Plugin其实也是一个app程序,我们可以给每个Plugin定义一个名称,版本号以便在Host程序中可以更好的显示区别及升级Plugin程序。
在testPlugin程序的string.xml文件里添加如下:
在testHost程序里就可以获取到Plugin程序里的这些信息了。
7:通信方式一:通过类加载器并反射机制实现。类加载器(ClassLoader)可以动态装载Class文件,标准的Java SDK中有一个ClassLoader类,它可以装载想要的Class文件。在我们以前的开发中,需要使用哪个类,直接import进来就可以了,使用import引用类文件有两个特点:
1:必须存在本地,当程序运行时需要改类时,内部类装载器会自动装载该类,这对程序员来说是透明的,程序员感知不到该过程。
2:编译时必须在现场,否则编译过程会因找不到引用文件而不能正常编译。
但是在有些情况下所需要的类不能满足以上条件。就如Host程序里可能需要调用Plugin程序里面某个类的一个函数的功能。此时这个过程是在运行是动态调用的。对Android程序而言,虽然本质是Java开发,并使用标准的Java编译器编译出Class文件,但最终的APK文件中包含的确是dex类型的文件。dex文件是将所需要的所有Class文件重新打包,打包的规则不是简单的压缩,而是完全对Class文件内部的各种函数表,变量表等进行优化,并产生一个新的文件,这就是dex文件。由于dex文件是一种经过优化的Class文件,因此要加载这样特殊的Class文件需要特殊的类加载器,这就是DexClassLoader。
在testPlugin程序里添加一个PluginClass.java的类文件:
在testHost程序里通过如下方式可以调用到PluginClass类里面的function1函数完成计算。
在testHost程序中定义一个接口Comm.java文件:
8:通信方式二:AIDL通信方式。AIDL通信方式之前有讲过,这里不做详解,不明白的可以前往Binder与Service
通信机制详解四 (源码分析AIDL工作机制)下载demo查看,若想明白其工作机制的,可以认真阅读该博文。也可以直接参考本博文上传的对应的例子
9:通信方式三:广播方式。广播方式也比较简单,可以直接查看本博文上传的对应的例子。
10:到此一个可以Host程序和Plugin程序相互通信的插件程序就完成了。
源码下载
1:将一个程序变为Host程序。新建一个Android项目,取名为testHost。
2:将一个程序变为Plugin程序。再新建一个Android项目取名为testPlugin。将testPlugin里面AndroidManifest.xml文件里面的1,2处的代码去掉。
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.testplugin.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> //1 <category android:name="android.intent.category.LAUNCHER" /> //2 </intent-filter> </activity> </application>此时安装testPlugin 这个程序时它在系统中没有图标,我们也无法直接启动它,此时可以认为该程序是个Plugin程序。
3:让Plugin程序能够被Host程序搜索到。我们把testHost和testPlugin都安装在系统中,那么Host程序如何去找到系统中所有符合条件的插件呢?我们可以给每个Plugin程序都定义一个action,那么Host程序就可以根据这个action搜索到系统中所有符合条件的Plugin。将步骤2中testPlugin里面AndroidManifest.xml文件里面代码替换如下,也就是增加了一个action-->com.example.testplugin.client:
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.testplugin.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="com.example.testplugin.client" /> </intent-filter> </activity> </application>4:Host程序搜索到Plugin程序。在Host程序中此时就可以搜索到符合条件的Plugin了。
Intent intent = new Intent("com.example.testplugin.client",null); PackageManager pm = context.getPackageManager(); List<ResolveInfo> plugins = pm.queryIntentActivities(intent, 0);for(int i = 0;i < plugins.size();i++) { ResolveInfo rinfo = plugins.get(i); ActivityInfo ainfo = rinfo.activityInfo; }在plugins里面就包含了我们符合条件的Plugin。在ResolveInfo里面的有个ActivityInfo activityInfo成员变量。这个变量包含了app的一些基本信息,在后面构造一个类加载器,并利用反射机制进行Host程序和Plugin程序进行通信时会用到。
5:Host程序获取到Plugin程序的名称版本信息。Plugin其实也是一个app程序,我们可以给每个Plugin定义一个名称,版本号以便在Host程序中可以更好的显示区别及升级Plugin程序。
在testPlugin程序的string.xml文件里添加如下:
<string name="version">1</string> <string name="name">Plugin</string>
在testHost程序里就可以获取到Plugin程序里的这些信息了。
Resources res = pm.getResourcesForApplication(ainfo.packageName); //包名可以通过步骤4中ResolveInfo里面ActivityInfo成员变量获取到 int id = 0; id = res.getIdentifier("name", "string", ainfo.packageName); //获取到testPlugin里面定义的name name = res.getString(id); id = res.getIdentifier("version", "string", ainfo.packageName);//获取到testPlugin里面定义的version version = res.getString(id);6:Host程序和Plugin程序之间进行通信的几种方式。到这里一个简单的Android插件式程序就可以搭建,并管理起来了。是不是看起来很简单。但是我们还需要一个重要的功能要实现Host程序和Plugin程序间的通信。
7:通信方式一:通过类加载器并反射机制实现。类加载器(ClassLoader)可以动态装载Class文件,标准的Java SDK中有一个ClassLoader类,它可以装载想要的Class文件。在我们以前的开发中,需要使用哪个类,直接import进来就可以了,使用import引用类文件有两个特点:
1:必须存在本地,当程序运行时需要改类时,内部类装载器会自动装载该类,这对程序员来说是透明的,程序员感知不到该过程。
2:编译时必须在现场,否则编译过程会因找不到引用文件而不能正常编译。
但是在有些情况下所需要的类不能满足以上条件。就如Host程序里可能需要调用Plugin程序里面某个类的一个函数的功能。此时这个过程是在运行是动态调用的。对Android程序而言,虽然本质是Java开发,并使用标准的Java编译器编译出Class文件,但最终的APK文件中包含的确是dex类型的文件。dex文件是将所需要的所有Class文件重新打包,打包的规则不是简单的压缩,而是完全对Class文件内部的各种函数表,变量表等进行优化,并产生一个新的文件,这就是dex文件。由于dex文件是一种经过优化的Class文件,因此要加载这样特殊的Class文件需要特殊的类加载器,这就是DexClassLoader。
在testPlugin程序里添加一个PluginClass.java的类文件:
package com.example.testplugin; import com.example.testhost.Comm; public class PluginClass{ @Override public int function1(int a, int b) { // TODO Auto-generated method stub return a+b; } }
在testHost程序里通过如下方式可以调用到PluginClass类里面的function1函数完成计算。
public void classLoader() { Intent intent = new Intent("com.example.testplugin.client",null); PackageManager pm = getPackageManager(); final List<ResolveInfo> plugins = pm.queryIntentActivities(intent, 0); ResolveInfo rinfo = plugins.get(0); ActivityInfo ainfo = rinfo.activityInfo; String div = System.getProperty("path.separator"); // 分隔符 String packagename = ainfo.packageName; String dexPath = ainfo.applicationInfo.sourceDir; // 目标类所在apk或者jar文件的路径 String dexOutputDir = getApplicationInfo().dataDir; // 指定解压出的dex文件存放路径 String libPath = ainfo.applicationInfo.nativeLibraryDir; // 目标类所使用的C/C++类库存放路径 DexClassLoader cl = new DexClassLoader(dexPath, dexOutputDir, libPath,this.getClass().getClassLoader()); try { Class<?> clazz = cl.loadClass(packagename + ".PluginClass"); // Class是ClassLoader所能识别的类,此处只是装载了PluginClass的程序代码 Object obj = clazz.newInstance(); // 此处才真正返回了PluginClass对象, // 尽管返回了PluginClass对象,但是本地并没有任何PluginClass类的定义,所以只能通过反射机制调用PluginClass类里面的方法。 Class[] params = new Class[2]; params[0] = Integer.TYPE; params[1] = Integer.TYPE; Method action = clazz.getMethod("function1", params); // 此处可以返回该类中的任何方法 Integer ret = (Integer) action.invoke(obj, 1, 2); // 第一个参数为执行目标函数的对象 Toast.makeText(this, "" + ret, Toast.LENGTH_SHORT).show(); }... }在上面方式中,通过ClassLoader装载的类有点繁琐,获取了到了PluginClass对象还需要反射方式去调用类里面的函数,那么能不能直接通过对象.函数的方式去调用PluginClass里面的函数呢?
在testHost程序中定义一个接口Comm.java文件:
package com.example.testhost; public interface Comm { public int function1(int a,int b); }重写testPlugin里面的PluginClass.java的类文件,我们看到PluginClass继承了Comm接口,但是在testPlugin里面不能直接把testHost程序里的Comm.java文件拷贝过来,否则会抛出异常,需要将testHost里面Comm.java导出成jar包,然后再添加到testPlugin程序里面:
package com.example.testplugin; import com.example.testhost.Comm; public class PluginClass implements Comm{ @Override public int function1(int a, int b) { // TODO Auto-generated method stub return a+b; } }重写testHost里面的classLoader函数:
public void classLoader() { Intent intent = new Intent("com.example.testplugin.client",null); PackageManager pm = getPackageManager(); final List<ResolveInfo> plugins = pm.queryIntentActivities(intent, 0); ResolveInfo rinfo = plugins.get(0); ActivityInfo ainfo = rinfo.activityInfo; String div = System.getProperty("path.separator"); // 分隔符 String packagename = ainfo.packageName; String dexPath = ainfo.applicationInfo.sourceDir; // 目标类所在apk或者jar文件的路径 String dexOutputDir = getApplicationInfo().dataDir; // 指定解压出的dex文件存放路径 String libPath = ainfo.applicationInfo.nativeLibraryDir; // 目标类所使用的C/C++类库存放路径 DexClassLoader cl = new DexClassLoader(dexPath, dexOutputDir, libPath,this.getClass().getClassLoader()); try { Class<?> clazz = cl.loadClass(packagename + ".PluginClass"); Comm com = (Comm) clazz.newInstance(); Integer ret = com.function1(1,2); Toast.makeText(this, "" + ret, Toast.LENGTH_SHORT).show(); }... }好了,通过上述方式就可以在Host程序和Plugin程序里面相互调用对方的类功能了。
8:通信方式二:AIDL通信方式。AIDL通信方式之前有讲过,这里不做详解,不明白的可以前往Binder与Service
通信机制详解四 (源码分析AIDL工作机制)下载demo查看,若想明白其工作机制的,可以认真阅读该博文。也可以直接参考本博文上传的对应的例子
9:通信方式三:广播方式。广播方式也比较简单,可以直接查看本博文上传的对应的例子。
10:到此一个可以Host程序和Plugin程序相互通信的插件程序就完成了。
源码下载
相关文章推荐
- SMSDemo android 简单拦截短信例子可以实现发 远程控制
- NestedScrollView 嵌套 ListView 实现滑动折叠效果
- 安卓开发——android window 一些属性说明
- Android 开发规范
- Android平台下使用lua调用Java代码经验总结
- Android导出数据到Excel
- 关于Android Studio如何导入library project
- Android RecyclerView的使用
- Android 自定义Fragment切换管理类,自动管理Fragment的生命周期,支持FragmentActivity和Fragment里面嵌套fragment
- Android消息推送(一)--AndroidPn(XMPP协议)Demo版
- 安卓开发——AndroidStudio中对于新定义变量提示Private field ‘变量名’ is never used
- 这些年正Android - 大学
- 监听android actionbar上overmenu是否显示
- ActiveAndroid TableInfo TypeSerializer
- Android progressBar进度条
- Android DiskLruCache完全解析,硬盘缓存的最佳方案
- Android6.0 按键流程(三)InputDispatcher分发输入消息
- 一个Android上的弹幕控件Open Danmaku
- Android 打包so动态库文件到APK
- Android 打包so动态库文件到APK