android插件开发机制
Step1:定义主程序中的接口。
- public interface MyInterface {
- public void test();
- }
- public interface MyInterface {
- public void test();
- }
然后将接口打包成.jar包,提供给插件去实现。
Step2:建立插件工程,实现接口。
将Step1中的jar包放到lib文件夹中,并把它加入build path,但千万记得在order and export项不要勾选,即build的时候不把这个jar包build进去,因为在运行时会把这个接口与主程序的接口当做两个不同的类。如下图:
实现接口的代码为:
- public class PlugAppActivity extends Activity implements MyInterface{
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- @Override
- public void test() {
- System.out.println(getApplicationInfo().sourceDir);
- }
- }
- public class PlugAppActivity extends Activity implements MyInterface{
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- @Override
- public void test() {
- System.out.println(getApplicationInfo().sourceDir);
- }
- }
为什么这里要继承Activity呢?这个在下一步说明,这里的Activity可以替代成service、receiver或provider。
在AndroidManifest加入这个Activity(其他组件同理)。
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.intsig.plugApp"
- android:versionCode="1"
- android:versionName="1.0" android:sharedUserId="com.main">
- <uses-sdk android:minSdkVersion="7" />
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name" >
- <activity
- android:name=".PlugAppActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="com.intsig.appMain.PLUGIN" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
- </application>
- </manifest>
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.intsig.plugApp"
- android:versionCode="1"
- android:versionName="1.0" android:sharedUserId="com.main">
- <uses-sdk android:minSdkVersion="7" />
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name" >
- <activity
- android:name=".PlugAppActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="com.intsig.appMain.PLUGIN" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
- </application>
- </manifest>
这里的sharedUserId是指插件与主程序共用一个Uid,这样就消除了权限的壁垒。Android系统继承了Linux系统管理文件的方法,为每一个应用程序分配一个独立的用户ID和用户组ID,而由这个应用程序创建出来的数据文件就赋予相应的用户以及用户组读写的权限,其余用户则无权对该文件进行读写。例如,如果我们进入到Android系统日历应用程序数据目录com.android.providers.calendar下的databases文件中,会看到一个用来保存日历数据的数据库文件calendar.db,它的权限设置如下所示:
- root@android:/data/data/com.android.providers.calendar/databases # ls -l
- -rw-rw---- app_17 app_17 33792 2011-11-07 15:50 calendar.db
- root@android:/data/data/com.android.providers.calendar/databases # ls -l
- -rw-rw---- app_17 app_17 33792 2011-11-07 15:50 calendar.db
这里的app_17就是系统自动分配的Uid。
至于给activity添加的intent-filter中的action也会在后面解释。
Step3:在主程序中获取插件,并调用接口方法。
- <SPAN style="FONT-SIZE: 18px">public class MainActivity extends Activity {
- //</SPAN><SPAN style="FONT-SIZE: 12px">预定义的action</SPAN><SPAN style="FONT-SIZE: 18px">
- public static final String ACTION_PLUGIN = "com.intsig.mainApp.PLUGIN";
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- try {
- //</SPAN><SPAN style="FONT-SIZE: 12px">查找符合这个action的所有activity即插件,若插件使用的是其他组件换成对应的方法</SPAN><SPAN style="FONT-SIZE: 18px">
- List<ResolveInfo> infos = getPackageManager().queryIntentActivities(
- new Intent(ACTION_PLUGIN), PackageManager.MATCH_DEFAULT_ONLY);
- ActivityInfo pluginInfo;
- for(ResolveInfo info:infos){
- <SPAN style="WHITE-SPACE: pre"> </SPAN>pluginInfo = info.activityInfo;
- //</SPAN><SPAN style="FONT-SIZE: 12px">根据插件的安装路径获得ClassLoader</SPAN><SPAN style="FONT-SIZE: 18px">
- ClassLoader cl = new PathClassLoader(pluginInfo.applicationInfo.sourceDir,getClassLoader());
- //</SPAN><SPAN style="FONT-SIZE: 12px">获得插件类的实例</SPAN><SPAN style="FONT-SIZE: 18px">
- MyInterface plugin = (MyInterface) cl.loadClass(pluginInfo.name).newInstance();
- plugin.test();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }</SPAN>
- <span style="font-size:18px;">public class MainActivity extends Activity {
- //</span><span style="font-size:12px;">预定义的action</span><span style="font-size:18px;">
- public static final String ACTION_PLUGIN = "com.intsig.mainApp.PLUGIN";
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- try {
- //</span><span style="font-size:12px;">查找符合这个action的所有activity即插件,若插件使用的是其他组件换成对应的方法</span><span style="font-size:18px;">
- List<ResolveInfo> infos = getPackageManager().queryIntentActivities(
- new Intent(ACTION_PLUGIN), PackageManager.MATCH_DEFAULT_ONLY);
- ActivityInfo pluginInfo;
- for(ResolveInfo info:infos){
- <span style="WHITE-SPACE: pre"> </span>pluginInfo = info.activityInfo;
- //</span><span style="font-size:12px;">根据插件的安装路径获得ClassLoader</span><span style="font-size:18px;">
- ClassLoader cl = new PathClassLoader(pluginInfo.applicationInfo.sourceDir,getClassLoader());
- //</span><span style="font-size:12px;">获得插件类的实例</span><span style="font-size:18px;">
- MyInterface plugin = (MyInterface) cl.loadClass(pluginInfo.name).newInstance();
- plugin.test();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }</span>
这里通过intent来找到所有符合条件的activity,即我们之前实现的插件,通过动态的加载类来获得插件实例。主程序的AndroidManifest如下:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.intsig.mainApp"
- android:versionCode="1"
- android:versionName="1.0" android:sharedUserId="com.main">
- <uses-sdk android:minSdkVersion="7" />
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name" >
- <activity
- android:name=".MainActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- </manifest>
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.intsig.mainApp"
- android:versionCode="1"
- android:versionName="1.0" android:sharedUserId="com.main">
- <uses-sdk android:minSdkVersion="7" />
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name" >
- <activity
- android:name=".MainActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- </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插件开发机制的更多相关文章
- Android随笔之——Android广播机制Broadcast详解
在Android中,有一些操作完成以后,会发送广播,比如说发出一条短信,或打出一个电话,如果某个程序接收了这个广播,就会做相应的处理.这个广播跟我们传统意义中的电台广播有些相似之处.之所以叫做广播,就 ...
- Android广播机制的深入学习
部分内容转载自http://www.cnblogs.com/lwbqqyumidi/p/4168017.html 1.Android广播机制概述 Android广播分为两个方面:广播发送者和广播接收者 ...
- Android签名机制
Android APK 签名比对 发布过Android应用的朋友们应该都知道,Android APK的发布是需要签名的.签名机制在Android应用和框架中有着十分重要的作用. 例如,Android系 ...
- (转)Android消息处理机制(Handler、Looper、MessageQueue与Message)
转自 http://www.cnblogs.com/angeldevil/p/3340644.html Android消息处理机制(Handler.Looper.MessageQueue与Messag ...
- Android消息机制
每一个Android应用在启动的时候都会创建一个线程,这个线程被称为主线程或者UI线程,Android应用的所有操作默认都会运行在这个线程中. 但是当我们想要进行数据请求,图片下载,或者其他耗时操作时 ...
- Android总结篇系列:Android广播机制
1.Android广播机制概述 Android广播分为两个方面:广播发送者和广播接收者,通常情况下,BroadcastReceiver指的就是广播接收者(广播接收器).广播作为Android组件间的通 ...
- 理解Android安全机制
本文从Android系统架构着手,分析Android的安全机制以SE Android,最后给出一些Android安全现状和常见的安全解决方案. 1.Android系统架构 Android采用分层的系统 ...
- 【Android 开发】: Android 消息处理机制之一: Handler 与 Message
最近几讲内容,我们学习了Android中关于多线程的一些知识,上一讲我们讲解了异步任务 AsyncTask 的操作,Android中还提供了其他的线程操作,如Handler Message Messa ...
- Android消息机制:Looper,MessageQueue,Message与handler
Android消息机制好多人都讲过,但是自己去翻源码的时候才能明白. 今天试着讲一下,因为目标是讲清楚整体逻辑,所以不追究细节. Message是消息机制的核心,所以从Message讲起. 1.Mes ...
随机推荐
- 【Codeforces 851D Arpa and a list of numbers】
Arpa的数列要根据GCD变成好数列. ·英文题,述大意: 给出一个长度为n(n<=5000000)的序列,其中的元素a[i]<=106,然后输入两个数x,y(x,y<=1 ...
- Android毕业设计-微圈
勤拂拭软件出品,必属精品 : 勤拂拭软件 去年做了一个类似于我的朋友圈的项目,主要功能就是用户可以实时发表自己的动态,好友可以评论点赞什么的,可以建群,聊天啊,拉黑啊,反正是一个不错的社交软件,包含服 ...
- Linux下实现普通用户免密码登录【超详细】
现有需求,需要把所有服务器的root和密码登录都禁用,只开放普通用户登录,这时需要给普通用户配置秘钥文件,实现无密码登录 如果普通用户需要root权限,在root用户下执行命令:visudo [roo ...
- Spring学习笔记2——创建Product对象,并在其中注入一个Category对象
第一步:创建Product类.在Product类中有对Category对象的set和get方法 package com.spring.cate; public class Product { priv ...
- nodejs基础教程回顾01
最近在复习nodejs,因为框架太多隔一段时间不用就会忘了,所以没办法必须时常拿出来练练,就像家里有好几辆车,要时不常的轮流开一圈.我就从最基础的开始写,怎么下载安装就不说了,首先是nodejs的三类 ...
- Dapper.Contrib拓展及数据库生成实体
1.关于Dapper.Contrib Dapper.Contrib是Dapper的一个拓展类库,Dapper是一个轻量级ORM框架,这个不了解的请自行收集资料,本文主要讲讲,数据库生成实体,并通过实体 ...
- AngularJs开发——指令与控制器间的通信
(原文:http://www.html5jscss.com/controller-between-directive.html) 指令与控制器之间通信,跟控制器间的通信.指令间通信也类似,也是下几种方 ...
- JVM Class字节码之三-使用BCEL改变类属性
使用BCEL动态改变Class内容 之前对Class文件中的常量池,Method的字节码指令进行了说明.JVM Class详解之一JVM Class详解之二 Method字节码指令现在我们开始实际动手 ...
- Mysql根据一个基库生成其他库与其不同的库升级脚本
今天研究了一下不同数据库之间如何做同步.弄了一个升级工具类,希望以后还能有所帮助. public class UpgradeDataBase { public static void main(Str ...
- bootstrap 栅格系统 HTTP协议 软件架构 B/S C/S 常见的WEB服务器
Day32 bootstrap Bootstrap就是响应式布局最成功的实现,为了兼容不同的浏览器采用jQuery,为了适配不同的终端采用CSS3 Media Query (媒体查询) 1.1.1 栅 ...