android插件开发机制

简介: <div class="article_title" style="margin:5px 0px; font-size:20px; line-height:30px; font-family:'Microsoft YaHei'"> <span style="font-size:16px">插件机制实质上就是由主体程序定义接口,然后由插件去实现这些接口,以达到功能模块化。Android系统
插件机制实质上就是由主体程序定义接口,然后由插件去实现这些接口,以达到功能模块化。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.   
  9.     @Override  
  10.     public void test() {  
  11.     System.out.println(getApplicationInfo().sourceDir);  
  12.     }  
  13. }  
[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.   
  9.     @Override  
  10.     public void test() {  
  11.     System.out.println(getApplicationInfo().sourceDir);  
  12.     }  
  13. }  

为什么这里要继承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.   
  7.     <uses-sdk android:minSdkVersion="7" />  
  8.   
  9.     <application  
  10.         android:icon="@drawable/ic_launcher"  
  11.         android:label="@string/app_name" >  
  12.         <activity  
  13.             android:name=".PlugAppActivity"  
  14.             android:label="@string/app_name" >  
  15.             <intent-filter>  
  16.                 <action android:name="com.intsig.appMain.PLUGIN" />  
  17.                 <category android:name="android.intent.category.DEFAULT" />  
  18.             </intent-filter>  
  19.         </activity>  
  20.     </application>  
  21.   
  22. </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.   
  7.     <uses-sdk android:minSdkVersion="7" />  
  8.   
  9.     <application  
  10.         android:icon="@drawable/ic_launcher"  
  11.         android:label="@string/app_name" >  
  12.         <activity  
  13.             android:name=".PlugAppActivity"  
  14.             android:label="@string/app_name" >  
  15.             <intent-filter>  
  16.                 <action android:name="com.intsig.appMain.PLUGIN" />  
  17.                 <category android:name="android.intent.category.DEFAULT" />  
  18.             </intent-filter>  
  19.         </activity>  
  20.     </application>  
  21.   
  22. </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.       
  3.     //</SPAN><SPAN style="FONT-SIZE: 12px">预定义的action</SPAN><SPAN style="FONT-SIZE: 18px">  
  4.     public static final String ACTION_PLUGIN = "com.intsig.mainApp.PLUGIN";  
  5.     @Override  
  6.     public void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.main);  
  9.         try {  
  10.             //</SPAN><SPAN style="FONT-SIZE: 12px">查找符合这个action的所有activity即插件,若插件使用的是其他组件换成对应的方法</SPAN><SPAN style="FONT-SIZE: 18px">  
  11.             List<ResolveInfo> infos = getPackageManager().queryIntentActivities(  
  12.                     new Intent(ACTION_PLUGIN), PackageManager.MATCH_DEFAULT_ONLY);  
  13.             ActivityInfo pluginInfo;  
  14.             for(ResolveInfo info:infos){  
  15.             <SPAN style="WHITE-SPACE: pre"> </SPAN>pluginInfo = info.activityInfo;  
  16.                 //</SPAN><SPAN style="FONT-SIZE: 12px">根据插件的安装路径获得ClassLoader</SPAN><SPAN style="FONT-SIZE: 18px">  
  17.                 ClassLoader cl = new PathClassLoader(pluginInfo.applicationInfo.sourceDir,getClassLoader());  
  18.                 //</SPAN><SPAN style="FONT-SIZE: 12px">获得插件类的实例</SPAN><SPAN style="FONT-SIZE: 18px">  
  19.                 MyInterface plugin = (MyInterface) cl.loadClass(pluginInfo.name).newInstance();  
  20.                 plugin.test();  
  21.             }  
  22.         } catch (Exception e) {  
  23.             e.printStackTrace();  
  24.         }  
  25.   
  26.     }  
  27. }</SPAN>  
[html]  view plain copy
  1. <span style="font-size:18px;">public class MainActivity extends Activity {  
  2.       
  3.     //</span><span style="font-size:12px;">预定义的action</span><span style="font-size:18px;">  
  4.     public static final String ACTION_PLUGIN = "com.intsig.mainApp.PLUGIN";  
  5.     @Override  
  6.     public void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.main);  
  9.         try {  
  10.             //</span><span style="font-size:12px;">查找符合这个action的所有activity即插件,若插件使用的是其他组件换成对应的方法</span><span style="font-size:18px;">  
  11.             List<ResolveInfo> infos = getPackageManager().queryIntentActivities(  
  12.                     new Intent(ACTION_PLUGIN), PackageManager.MATCH_DEFAULT_ONLY);  
  13.             ActivityInfo pluginInfo;  
  14.             for(ResolveInfo info:infos){  
  15.             <span style="WHITE-SPACE: pre"> </span>pluginInfo = info.activityInfo;  
  16.                 //</span><span style="font-size:12px;">根据插件的安装路径获得ClassLoader</span><span style="font-size:18px;">  
  17.                 ClassLoader cl = new PathClassLoader(pluginInfo.applicationInfo.sourceDir,getClassLoader());  
  18.                 //</span><span style="font-size:12px;">获得插件类的实例</span><span style="font-size:18px;">  
  19.                 MyInterface plugin = (MyInterface) cl.loadClass(pluginInfo.name).newInstance();  
  20.                 plugin.test();  
  21.             }  
  22.         } catch (Exception e) {  
  23.             e.printStackTrace();  
  24.         }  
  25.   
  26.     }  
  27. }</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.   
  7.     <uses-sdk android:minSdkVersion="7" />  
  8.   
  9.     <application  
  10.         android:icon="@drawable/ic_launcher"  
  11.         android:label="@string/app_name" >  
  12.         <activity  
  13.             android:name=".MainActivity"  
  14.             android:label="@string/app_name" >  
  15.             <intent-filter>  
  16.                 <action android:name="android.intent.action.MAIN" />  
  17.                 <category android:name="android.intent.category.LAUNCHER" />  
  18.             </intent-filter>  
  19.         </activity>  
  20.     </application>  
  21.   
  22. </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.   
  7.     <uses-sdk android:minSdkVersion="7" />  
  8.   
  9.     <application  
  10.         android:icon="@drawable/ic_launcher"  
  11.         android:label="@string/app_name" >  
  12.         <activity  
  13.             android:name=".MainActivity"  
  14.             android:label="@string/app_name" >  
  15.             <intent-filter>  
  16.                 <action android:name="android.intent.action.MAIN" />  
  17.                 <category android:name="android.intent.category.LAUNCHER" />  
  18.             </intent-filter>  
  19.         </activity>  
  20.     </application>  
  21.   
  22. </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.二者的区别我还没来得及研究,希望有兴趣的同学去研究下。

目录
相关文章
|
2月前
|
安全 数据库 Android开发
在Android开发中实现两个Intent跳转及数据交换的方法
总结上述内容,在Android开发中,Intent不仅是活动跳转的桥梁,也是两个活动之间进行数据交换的媒介。运用Intent传递数据时需注意数据类型、传输大小限制以及安全性问题的处理,以确保应用的健壯性和安全性。
124 11
|
2月前
|
监控 Android开发 数据安全/隐私保护
批量发送短信的平台,安卓群发短信工具插件脚本,批量群发短信软件【autojs版】
这个Auto.js脚本实现了完整的批量短信发送功能,包含联系人管理、短信内容编辑、发送状态监控等功能
|
2月前
|
API Android开发
微信虚拟摄像头模块,微信虚拟视频聊天,安卓虚拟摄像头插件
该实现包含虚拟摄像头服务核心、视频流生成和Android配置三个关键模块,使用Camera2
|
3月前
|
存储 JSON API
安卓ck提取工具,可提取手机cookie插件,AUTOJS即可实现
怎么用autojs提取手机端的CK?其实autojs是支持提取ck的但是他提取的不是浏览器的CK,二十他自身浏览器环境的c
|
6月前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
1138 77
|
3月前
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
152 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
|
4月前
|
消息中间件 Android开发
Android Handler的使用方式以及其机制的简单介绍
Handler 是 Android 中实现线程间通信的重要机制,可传递任意两线程数据。常用场景包括子线程向主线程(UI 线程)传递结果,以及主线程向子线程发送消息。其核心涉及四个类:Handler(发送/接收消息)、Message(消息载体)、MessageQueue(消息队列)和 Looper(消息循环泵)。基本流程为:Handler 发送 Message 至 MessageQueue,Looper 从队列中按 FIFO 取出并处理。
153 0
|
7月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
398 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
7月前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
190 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
7月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
157 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex