插件机制实质上就是由主体程序定义接口,然后由插件去实现这些接口,以达到功能模块化。Android系统是基于Linux内核的,其安全机制也继承了Linux的特性,再加上android framework没有提供插件化编程的接口,使得在android上做插件开发显得很困难。经过与同事的研究和讨论,想到了一种在android上做开发插件的方法。下面直接通过一个demo来说明。

Step1:定义主程序中的接口。

[java] view
plain
copy

  1. public interface MyInterface {
  2. public void test();
  3. }
[java] view
plain
copy

  1. public interface MyInterface {
  2. public void test();
  3. }

然后将接口打包成.jar包,提供给插件去实现。

Step2:建立插件工程,实现接口。

将Step1中的jar包放到lib文件夹中,并把它加入build path,但千万记得在order and export项不要勾选,即build的时候不把这个jar包build进去,因为在运行时会把这个接口与主程序的接口当做两个不同的类。如下图:

             

实现接口的代码为:

[java] view
plain
copy

  1. public class PlugAppActivity extends Activity implements MyInterface{
  2. /** Called when the activity is first created. */
  3. @Override
  4. public void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.main);
  7. }
  8. @Override
  9. public void test() {
  10. System.out.println(getApplicationInfo().sourceDir);
  11. }
  12. }
[java] view
plain
copy

  1. public class PlugAppActivity extends Activity implements MyInterface{
  2. /** Called when the activity is first created. */
  3. @Override
  4. public void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.main);
  7. }
  8. @Override
  9. public void test() {
  10. System.out.println(getApplicationInfo().sourceDir);
  11. }
  12. }

为什么这里要继承Activity呢?这个在下一步说明,这里的Activity可以替代成service、receiver或provider。

在AndroidManifest加入这个Activity(其他组件同理)。

[html] view
plain
copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.intsig.plugApp"
  4. android:versionCode="1"
  5. android:versionName="1.0" android:sharedUserId="com.main">
  6. <uses-sdk android:minSdkVersion="7" />
  7. <application
  8. android:icon="@drawable/ic_launcher"
  9. android:label="@string/app_name" >
  10. <activity
  11. android:name=".PlugAppActivity"
  12. android:label="@string/app_name" >
  13. <intent-filter>
  14. <action android:name="com.intsig.appMain.PLUGIN" />
  15. <category android:name="android.intent.category.DEFAULT" />
  16. </intent-filter>
  17. </activity>
  18. </application>
  19. </manifest>
[html] view
plain
copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.intsig.plugApp"
  4. android:versionCode="1"
  5. android:versionName="1.0" android:sharedUserId="com.main">
  6. <uses-sdk android:minSdkVersion="7" />
  7. <application
  8. android:icon="@drawable/ic_launcher"
  9. android:label="@string/app_name" >
  10. <activity
  11. android:name=".PlugAppActivity"
  12. android:label="@string/app_name" >
  13. <intent-filter>
  14. <action android:name="com.intsig.appMain.PLUGIN" />
  15. <category android:name="android.intent.category.DEFAULT" />
  16. </intent-filter>
  17. </activity>
  18. </application>
  19. </manifest>

这里的sharedUserId是指插件与主程序共用一个Uid,这样就消除了权限的壁垒。Android系统继承了Linux系统管理文件的方法,为每一个应用程序分配一个独立的用户ID和用户组ID,而由这个应用程序创建出来的数据文件就赋予相应的用户以及用户组读写的权限,其余用户则无权对该文件进行读写。例如,如果我们进入到Android系统日历应用程序数据目录com.android.providers.calendar下的databases文件中,会看到一个用来保存日历数据的数据库文件calendar.db,它的权限设置如下所示:

[html] view
plain
copy

  1. root@android:/data/data/com.android.providers.calendar/databases # ls -l
  2. -rw-rw---- app_17   app_17      33792 2011-11-07 15:50 calendar.db
[html] view
plain
copy

  1. root@android:/data/data/com.android.providers.calendar/databases # ls -l
  2. -rw-rw---- app_17   app_17      33792 2011-11-07 15:50 calendar.db

这里的app_17就是系统自动分配的Uid。

至于给activity添加的intent-filter中的action也会在后面解释。

Step3:在主程序中获取插件,并调用接口方法。

[html] view
plain
copy

  1. <SPAN style="FONT-SIZE: 18px">public class MainActivity extends Activity {
  2. //</SPAN><SPAN style="FONT-SIZE: 12px">预定义的action</SPAN><SPAN style="FONT-SIZE: 18px">
  3. public static final String ACTION_PLUGIN = "com.intsig.mainApp.PLUGIN";
  4. @Override
  5. public void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.main);
  8. try {
  9. //</SPAN><SPAN style="FONT-SIZE: 12px">查找符合这个action的所有activity即插件,若插件使用的是其他组件换成对应的方法</SPAN><SPAN style="FONT-SIZE: 18px">
  10. List<ResolveInfo> infos = getPackageManager().queryIntentActivities(
  11. new Intent(ACTION_PLUGIN), PackageManager.MATCH_DEFAULT_ONLY);
  12. ActivityInfo pluginInfo;
  13. for(ResolveInfo info:infos){
  14. <SPAN style="WHITE-SPACE: pre"> </SPAN>pluginInfo = info.activityInfo;
  15. //</SPAN><SPAN style="FONT-SIZE: 12px">根据插件的安装路径获得ClassLoader</SPAN><SPAN style="FONT-SIZE: 18px">
  16. ClassLoader cl = new PathClassLoader(pluginInfo.applicationInfo.sourceDir,getClassLoader());
  17. //</SPAN><SPAN style="FONT-SIZE: 12px">获得插件类的实例</SPAN><SPAN style="FONT-SIZE: 18px">
  18. MyInterface plugin = (MyInterface) cl.loadClass(pluginInfo.name).newInstance();
  19. plugin.test();
  20. }
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }</SPAN>
[html] view
plain
copy

  1. <span style="font-size:18px;">public class MainActivity extends Activity {
  2. //</span><span style="font-size:12px;">预定义的action</span><span style="font-size:18px;">
  3. public static final String ACTION_PLUGIN = "com.intsig.mainApp.PLUGIN";
  4. @Override
  5. public void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.main);
  8. try {
  9. //</span><span style="font-size:12px;">查找符合这个action的所有activity即插件,若插件使用的是其他组件换成对应的方法</span><span style="font-size:18px;">
  10. List<ResolveInfo> infos = getPackageManager().queryIntentActivities(
  11. new Intent(ACTION_PLUGIN), PackageManager.MATCH_DEFAULT_ONLY);
  12. ActivityInfo pluginInfo;
  13. for(ResolveInfo info:infos){
  14. <span style="WHITE-SPACE: pre"> </span>pluginInfo = info.activityInfo;
  15. //</span><span style="font-size:12px;">根据插件的安装路径获得ClassLoader</span><span style="font-size:18px;">
  16. ClassLoader cl = new PathClassLoader(pluginInfo.applicationInfo.sourceDir,getClassLoader());
  17. //</span><span style="font-size:12px;">获得插件类的实例</span><span style="font-size:18px;">
  18. MyInterface plugin = (MyInterface) cl.loadClass(pluginInfo.name).newInstance();
  19. plugin.test();
  20. }
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }</span>

这里通过intent来找到所有符合条件的activity,即我们之前实现的插件,通过动态的加载类来获得插件实例。主程序的AndroidManifest如下:

[html] view
plain
copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.intsig.mainApp"
  4. android:versionCode="1"
  5. android:versionName="1.0" android:sharedUserId="com.main">
  6. <uses-sdk android:minSdkVersion="7" />
  7. <application
  8. android:icon="@drawable/ic_launcher"
  9. android:label="@string/app_name" >
  10. <activity
  11. android:name=".MainActivity"
  12. android:label="@string/app_name" >
  13. <intent-filter>
  14. <action android:name="android.intent.action.MAIN" />
  15. <category android:name="android.intent.category.LAUNCHER" />
  16. </intent-filter>
  17. </activity>
  18. </application>
  19. </manifest>
[html] view
plain
copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.intsig.mainApp"
  4. android:versionCode="1"
  5. android:versionName="1.0" android:sharedUserId="com.main">
  6. <uses-sdk android:minSdkVersion="7" />
  7. <application
  8. android:icon="@drawable/ic_launcher"
  9. android:label="@string/app_name" >
  10. <activity
  11. android:name=".MainActivity"
  12. android:label="@string/app_name" >
  13. <intent-filter>
  14. <action android:name="android.intent.action.MAIN" />
  15. <category android:name="android.intent.category.LAUNCHER" />
  16. </intent-filter>
  17. </activity>
  18. </application>
  19. </manifest>

插件中的sharedUserId要与这里的保持一致。

上面三步描述了用android的四大组件来实现插件,但除此之外还有另一种方式。从上面的demo可以发现所有的插件与主程序的sharedUserId都是一致的,那么就可以通过检索所有安装程序的sharedUserId,只要与主程序的一致便可当做是它的插件。在上面的方法中我们获得了插件的路径以及实现接口类的类名,从而能够动态的加载这个类,而通过检索sharedUserId能够获得到路径却无法获得到类名,那么可以在插件中加入一个xml文件来说明插件中包含的实现类,通过读取这个xml来获取出类名和其他一些可能需要的描述信息,这个就会比第一种要复杂一些。总结一下,当插件的功能比较简单,选择第一种方法比较容易实现;当插件功能较多,逻辑复杂时,可以将插件再细分成模块,同时xml文件可以表现出插件的组织结构,那么第二种方法更好一些。

上面所讲的两种方法都是适用于将安装的apk作为插件,实现插件开发还可以通过在sd卡中的指定目录放入插件的jar包或apk文件,原理与上述类似,只是将PathClassLoader换成DexClassLoader,换成它的原因是DexClassLoader的文档描述有一句:“A
class loader that loads classes from .jar and .apk files
containing a classes.dex entry. This can be used to execute code not installed as part of an application.”二者的区别我还没来得及研究,希望有兴趣的同学去研究下。

android插件开发机制的更多相关文章

  1. Android随笔之——Android广播机制Broadcast详解

    在Android中,有一些操作完成以后,会发送广播,比如说发出一条短信,或打出一个电话,如果某个程序接收了这个广播,就会做相应的处理.这个广播跟我们传统意义中的电台广播有些相似之处.之所以叫做广播,就 ...

  2. Android广播机制的深入学习

    部分内容转载自http://www.cnblogs.com/lwbqqyumidi/p/4168017.html 1.Android广播机制概述 Android广播分为两个方面:广播发送者和广播接收者 ...

  3. Android签名机制

    Android APK 签名比对 发布过Android应用的朋友们应该都知道,Android APK的发布是需要签名的.签名机制在Android应用和框架中有着十分重要的作用. 例如,Android系 ...

  4. (转)Android消息处理机制(Handler、Looper、MessageQueue与Message)

    转自 http://www.cnblogs.com/angeldevil/p/3340644.html Android消息处理机制(Handler.Looper.MessageQueue与Messag ...

  5. Android消息机制

    每一个Android应用在启动的时候都会创建一个线程,这个线程被称为主线程或者UI线程,Android应用的所有操作默认都会运行在这个线程中. 但是当我们想要进行数据请求,图片下载,或者其他耗时操作时 ...

  6. Android总结篇系列:Android广播机制

    1.Android广播机制概述 Android广播分为两个方面:广播发送者和广播接收者,通常情况下,BroadcastReceiver指的就是广播接收者(广播接收器).广播作为Android组件间的通 ...

  7. 理解Android安全机制

    本文从Android系统架构着手,分析Android的安全机制以SE Android,最后给出一些Android安全现状和常见的安全解决方案. 1.Android系统架构 Android采用分层的系统 ...

  8. 【Android 开发】: Android 消息处理机制之一: Handler 与 Message

    最近几讲内容,我们学习了Android中关于多线程的一些知识,上一讲我们讲解了异步任务 AsyncTask 的操作,Android中还提供了其他的线程操作,如Handler Message Messa ...

  9. Android消息机制:Looper,MessageQueue,Message与handler

    Android消息机制好多人都讲过,但是自己去翻源码的时候才能明白. 今天试着讲一下,因为目标是讲清楚整体逻辑,所以不追究细节. Message是消息机制的核心,所以从Message讲起. 1.Mes ...

随机推荐

  1. hdu 3397 线段树双标记

    Sequence operation Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Othe ...

  2. hdu5630 BestCoder Round #73 (div.2)

    Rikka with Chess  Accepts: 393  Submissions: 548  Time Limit: 2000/1000 MS (Java/Others)  Memory Lim ...

  3. Linux(CentOs6.3)网络配置

    新装好的虚拟机往往还无法连接网络,本文描述了如何在CentOs6.3系统上配置网络信息 1.windows系统下快捷键windows+r,输入cmd并确定,打开黑窗口 2.黑窗口中输入ipconfig ...

  4. Spring MVC页面重定向

    以下示例显示如何编写一个简单的基于Web的重定向应用程序,这个应用程序使用重定向将http请求传输到另一个页面. 基于Spring MVC - Hello World实例章节中代码,创建创建一个名称为 ...

  5. RHEL 7修改ssh默认端口号

    RHEL7修改默认端口号(默认port22)初次安装系统完毕后默认情况下系统已经启动了sshd服务当然我们也可以先进行检查: 步骤1,检查是否已安装ssh服务 步骤2,检查服务是否已开启 如上图所示显 ...

  6. 解决nodejs中json序列化时Date类型为UTC格式

    在nodejs中,json序列化时Date类型时,默认转为UTC格式. 如下图 zhupengfei@DESKTOP-HJASOE3 MINGW64 /d/MyProject/exp2 $ node ...

  7. PHP开发中Redis安装(CentOS6.5)

    1.安装Redis 1 wget http://download.redis.io/releases/redis-3.2.8.tar.gz 2 tar xzf redis-3.2.8.tar.gz 3 ...

  8. map内置函数、lambda表达式、快捷生成想要的列表、filter内置函数

      map函数                             语法 map(function, iterable, ...) 参数 function -- 函数,有两个参数 iterable ...

  9. C指针 指针和数组 二维数组的指针 指针应用

    直接到变量名标识的存储单元中读取变量的值--直接寻址 通过其他变量间接找到变量的地址读取变量的值--间接寻址 指针变量存放地址 显示变量的地址 指针变量使用前必须初始化,若不知指向哪,可先指向NULL ...

  10. JAX-RPC 与 JAX-WS 的比较

    引言 Web 服务已经出现很久了.首先是 SOAP,但 SOAP 仅描述消息的情况,然后是 WSDL,WSDL 并不会告诉您如何使用 Java™ 编写 Web 服务.在这种情况下,JAX-RPC 1. ...