插件机制实质上就是由主体程序定义接口,然后由插件去实现这些接口,以达到功能模块化。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. Uva 437 巴比伦塔 && UVA10003

    要求底面严格小于它下方立方体的长宽,求出最高情况,一块石头可以多次使用 用结构体记录一块石头的三种放置情况,按面积排序. dp[i] = max(dp[i],dp[j] + block[i].high ...

  2. ●BZOJ 3129 [Sdoi2013]方程

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=3129 题解: 容斥,扩展Lucas,中国剩余定理 先看看不管限制,只需要每个位置都是正整数时 ...

  3. SpringCloud学习之SpringCloudBus

    一.spring-cloud-bus是什么? 回答这个问题之前,我们先回顾先前的分布式配置,当配置中心发生变化后,我们需要利用spring-boot-actuator里的refresh端点进行手动刷新 ...

  4. Gradle学习之基础篇

    一.gradle基础概念 Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具.Gradle抛弃了基于各种繁琐的XML,使用一种基于Groovy的特定领域语言( ...

  5. Yii2.0源码阅读-behavior的实现原理

    Yii2.0中的一个思想就是组件化的思想,所以.大多数的类都直接或间接的继承自yii\base\Component,而组件的三大功能:属性.事件.行为. 行为的目的是为了方便的扩展一个类的功能,而不需 ...

  6. python的运算符与表达式

    Python运算符与表达式 1. 运算符分类 运算符主要分5种: 1. 算术运算符 2. 比较运算符 3. 位运算符 4. 逻辑运算符 5. 成员运算符 6. 身份运算符 7. 赋值运算符 1. 算术 ...

  7. vsftpd详解(ubuntu)

    安装 apt-get instll vsftpd 配置vsftp vim vsftpd.conf listen=YES listen_port= anonymous_enable=NO local_e ...

  8. windows的常用快捷键(实用篇)

    整理一下windows的常用快捷键,有些快捷键老不用都忘记了,这里整理一下方便自己以后忘记时翻阅. 一.Fn键的使用 1.F1帮助 2.F2重命名 3.F3打开搜索 4.F4打开地址栏常用地址 5.F ...

  9. Eclipse 一直不停 building workspace完美解决总结

    一.产生这个问题的原因多种1.自动升级 2.未正确关闭  3.maven下载lib挂起 等.. 二.解决总结(1).解决方法        方法1.修改eclipse启动文件 eclipse.ini ...

  10. javascript装饰器模式

    装饰器模式 什么是装饰器 原名decorator 被翻译为装饰器 可以理解为装饰 修饰 包装等意 现实中的作用 一间房子通过装饰可以变得更华丽,功能更多 类似一部手机可以单独使用 但是很多人都愿意家个 ...