android插件开发机制

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

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.”二者的区别我还没来得及研究,希望有兴趣的同学去研究下。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android相关

AAC---LiveData

LiveData是一个与Activity/Fragment生命周期相关(lifecycle-aware)的Observer类。而这种相关性(awareness ...

12720
来自专栏三流程序员的挣扎

Navigation 详解一

Navigation 是 JetPack 中的一个组件,用于方便的实现页面的导航,所以抽象出了一个 destination 的概念,大部分情况一个 destin...

23810
来自专栏向治洪

Universal Image Loader for Android 使用实例

<span style="white-space:pre">      </span>// 1.获取ImageLoader实例         ImageLo...

254100
来自专栏酷玩时刻

统一管理项目中的接口回调

无论是 Java Web 开发还是 Android 开发我们都避免不了异步处理业务的逻辑。有异步的出现一定就有异步结果(接口)的回调。你之前是不是有为每一个异步...

10240
来自专栏Android机动车

Dagger2系列——实例解析

这篇文章会分享一下实际应用中的Dagger2如何使用,以及Dagger2通过apt插件如何给我们生成代码,以及生成的代码之间的关联。

10920
来自专栏程序员互动联盟

【专业技术】Android内存泄漏简介

存在问题: 不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露。 解决方案: 其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它...

30330
来自专栏LeoXu的博客

[翻译]Android教程-保存数据-保存键值对

http://developer.android.com/training/basics/data-storage/index.html

13610
来自专栏学海无涯

Android开发之Retrofit小试牛刀

感觉好久没有写Android的文章了,囧囧囧!因为Retrofit实在是太火了, 火得我一直跃跃欲试,但是由于种种原因吧,一直都没有用过。周末闲来无事,利用一个...

29690
来自专栏知识分享

android 之TCP客户端编程

吸取教训!!!本来花了5个小时写完了,没想到,,,因为没点上面的自动保存查看一下,全没了,重新写呗 关于网络通信:每一台电脑都有自己的ip地址,每台电脑上的网络...

39480
来自专栏cloudskyme

gwt之mvc4g

Mvc4g是一个简单的框架来实现的GWT应用程序的MVC模式。 主要思想 其主要思想是,以减轻开发人员的工作,以单独的视图从模型。该框架是一个XML文件,将允...

34560

扫码关注云+社区

领取腾讯云代金券