您的位置:首页 > 移动开发 > Android开发

Android 插件式开发

2016-01-30 17:12 537 查看
Android插件式开发,顾名思义,就是有多个程序,其中有一个主程序,其它程序我们称之为插件。主程序在系统中有图标,其它程序在系统中没有图标,但可以独立安装。其它程序都被主程序所加载。发布的时候只需发布主程序就可以了。其它程序可以根据对服务器的请求,在主程序中动态扩展与升级。以下主程序我们称之为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程序相互通信的插件程序就完成了。

源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: