http://www.cnblogs.com/hangxin1940/archive/2011/12/14/2288169.html

框架已经放出:

android-application-plug-ins-frame-work 安卓应用程序插件化开发框架 -AAP Framework

在android的项目开发中,都会遇到后期功能拓展增强与主程序代码变更的现实矛盾,也就是程序的灵活度。

由于linux平台的安全机制,再加上dalvik的特殊机制,各种权限壁垒,使得开发一个灵活多变的程序,变得比较困难,不像pc平台下那么容易。

瞅瞅elipse的插件,瞅瞅360的插件,在android下,我们一开始很难写好一个主程序,然后通过插件机制来应对以后的功能拓展,于是程序变得不那么灵活多变了。

比如一款android下的安全软件,新版本增加了一个功能,如短信拦截,往往会因为一个模块的增加,而重新编译一个apk包,这样周而复始,哪怕只增加50kb的功能代码,用户也需要升级一个完整的apk,往往是5~6M的体积。

最近思来想去,想到一个方法,既然tencent qq在android下面可以以apk的形式来换皮肤,这资源文件的拓展都可以这样简便的搞,为何功能性的拓展就不可以?

想出来了两种解决方案。

先来说说第一种。

demo下载在最后

先说分析思路。

android下,默认的情况是,每个apk相互独立的,基本上每个应用都是一个dalvik虚拟机,都有一个uid,再配合上linux本身的权限机制,使得apk互通很难直接进行。但作为一个独立应用的集成,不管多少个apk,都可以并为一个单独的dalvik虚拟机,直观的反映给开发人员就是在shell下列出进程,那几个apk同时加载后,会一个进程存在。

这主要就是工程的清单文件 Mainfest中配置了,只需要一句话,以我的测试demo为例:

....<manifest xmlns:android="http://schemas.android.com/apk/res/android"      package="org.igeek.plugintest.main"      <!-- 就是这句关键代码 -->      android:sharedUserId="org.igeek.plugintest"      android:versionCode="1"            android:versionName="1.0">.....
复制代码


在上面的代码中,android:sharedUserId是指共用一个uid,也就是,凡是这个属性相同的工程,都会共用同一个uid,这样,权限壁垒就消除了,dalvik也会融合为一个,可以测试一下,写几个工程,没有这个属性和有这个属性的情况下,同时运行,在列出当前进程,就直观的说明了。

程序拓展的插件化,当然需要一个主程序,主程序是实现基本功能,以及UI,还有插件的检索以及插件的调用。

这里贴出我demo中的主activity代码:

View Code
  1 public class AndoirdpluginActivity extends ActivityGroup implements OnClickListener ,OnScrollCompleteListener{  2     private LinearLayout llMainLayout;  3       4     //workspace,看看luncher的源码,这个就是桌面那个多屏的实现  5     private WorkSpace wkMain;  6     private Button btnFindPlugins;  7     private CheckBox chbAttachMain;  8       9     private LocalActivityManager m_ActivityManager; 10      11     //这个bean的集合,就相当于插件的描述集合 12 //每个bean也就是一个插件的各种描述 13     private List<PluginBean> plugins; 14      15     @Override 16     public void onCreate(Bundle savedInstanceState) { 17         super.onCreate(savedInstanceState); 18         setContentView(R.layout.main); 19          20         llMainLayout=(LinearLayout) findViewById(R.id.main_llMainLayout); 21         wkMain=(WorkSpace) findViewById(R.id.main_wkMain); 22         btnFindPlugins=(Button) findViewById(R.id.main_btnFindPlugins); 23         chbAttachMain=(CheckBox) findViewById(R.id.main_chbAttachMain); 24          25         m_ActivityManager = getLocalActivityManager(); 26          27         wkMain.setOnScrollCompleteLinstenner(this); 28         btnFindPlugins.setOnClickListener(this); 29     } 30  31     @Override 32     public void onClick(View v) { 33         attachPlugin(findPlugins()); 34         btnFindPlugins.setVisibility(View.GONE); 35     } 36      37     /** 38      * 加载插件列表 39      * @param plugins 40 */ 41     private void attachPlugin(final List<PluginBean> plugins){ 42         Log.e("ydt", "   "); 43         Log.e("ydt", "----- 列出插件"); 44         this.plugins=plugins; 45         for(final PluginBean plugin:plugins){ 46             Button btn=new Button(this); 47             btn.setTextColor(Color.RED); 48             btn.setText(plugin.getLabel()); 49              50             llMainLayout.addView(btn); 51             //添加事件 52             btn.setOnClickListener(new OnClickListener() { 53                  54                 @Override 55                 public void onClick(View v) { 56                     boolean isAttack=chbAttachMain.isChecked(); 57                      58                     Intent it=new Intent(); 59                     it.setAction(plugin.getPakageName()); 60                      61                     //是否附加为view 62                     if(isAttack){ 63                         //这里偷下懒,这是演示插件作为view附加到主程序中的 64                         for(PluginBean plugin:plugins){ 65                              66                             Intent itt=new Intent(); 67                             itt.setAction(plugin.getPakageName()); 68                             ViewGroup view=(ViewGroup) (m_ActivityManager.startActivity("", itt)).getDecorView(); 69                             wkMain.addView(view); 70                         } 71                         //一次性附加完毕算了,然后把按钮都删了,看着清净,这几个不是重点 72                         llMainLayout.removeAllViews(); 73                         chbAttachMain.setVisibility(View.GONE); 74                         wkMain.setToScreen(0); 75                     }else{ 76                         //这里,不会把插件的窗体附加到主程序中,纯粹无用的演示 77                         startActivity(it); 78                     } 79                 } 80             }); 81              82         } 83     } 84      85     /** 86      * 查找插件 87      * @return 88 */ 89     private List<PluginBean> findPlugins(){ 90          91         List<PluginBean> plugins=new ArrayList<PluginBean>(); 92          93          94         //遍历包名,来获取插件 95         PackageManager pm=getPackageManager(); 96          97          98         List<PackageInfo> pkgs=pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES); 99         for(PackageInfo pkg    :pkgs){100             //包名101             String packageName=pkg.packageName;102             String sharedUserId= pkg.sharedUserId;103             104             //sharedUserId是开发时约定好的,这样判断是否为自己人105             if(!"org.igeek.plugintest".equals(sharedUserId)||"org.igeek.plugintest.main".equals(packageName))106                 continue;107             108             //进程名109             String prcessName=pkg.applicationInfo.processName;110             111             //label,也就是appName了112             String label=pm.getApplicationLabel(pkg.applicationInfo).toString();113             114             PluginBean plug=new PluginBean();115             plug.setLabel(label);116             plug.setPakageName(packageName);117             118             plugins.add(plug);119         }120         121         122         return plugins;123         124     }125 126  127     /**128      * WorkSpace滚动到那个屏,会触发这个事件129      * 而worksapce中每一屏又是一个插件130      * 这个事件是用来列出当前屏幕插件所提供的应用,并且让用户调用131 */132     @Override133     public void onScrollComplete(final ScrollEvent e) {134         try {135             final Context context = createPackageContext(plugins.get(e.curScreen).getPakageName(), Context.CONTEXT_INCLUDE_CODE|Context.CONTEXT_IGNORE_SECURITY);136             llMainLayout.removeAllViews();137             //这几行,通过反射获取了当前插件的描述信息,如同大部分框架的xml一样,这里算是模拟了一下IOC控制反转138             Class clazz=context.getClassLoader().loadClass(plugins.get(e.curScreen).getPakageName()+".PluginApplication");139             Object o=clazz.newInstance();140             Map<String,List<String>>  r=(Map<String, List<String>>) clazz.getMethod("getDesciption").invoke(o);141             List<String> classes=r.get("classes");142             List<String> methods=r.get("methods");143             144             145             //这里,根据获得的插件所提供的功能,来生成几个按钮显示,供我们调用146             for(final String clas:classes){147                 for(final String method:methods){148                     Button btn=new Button(this);149                     150                     btn.setText(clas+" -> "+method+" 执行");151                     152                     153                     //点击后,就执行插件所提供的方法154                     btn.setOnClickListener(new OnClickListener() {155                         156                         @Override157                         public void onClick(View v) {158                             try {159                                 Class c=context.getClassLoader().loadClass(plugins.get(e.curScreen).getPakageName()+"."+clas);160                                 Object o1=c.newInstance();161                                 162                                 //这里注意,context实际上就是句柄,这里如果涉及到窗体,plugin的句柄其实是不行的,因为它没有可以163 //依附的窗体164                                 165 //这个context是plugin的,通过测试,dialog这类行不通,Toast是可以的,因为166 //Toast是依附于屏幕主窗口的167 //c.getMethod(method,Context.class).invoke(o1,context); 168                                                         169 //这里则传递的是主程序的句柄170                                 c.getMethod(method,Context.class).invoke(o1,AndoirdpluginActivity.this);171                             172                             } catch (Exception e) {173                                 // TODO Auto-generated catch block174                                 e.printStackTrace();175                             } 176                         }177                     });178                     llMainLayout.addView(btn);179                 }180             }181             182         183         } catch (Exception e1) {184             // TODO Auto-generated catch block185             e1.printStackTrace();186         }187     188     }189 }
复制代码


看注释吧,主要有两点

插件的扫描

这种方案是,每个插件以一个单独的apk发布,这样可以在程序中很灵活的知道是否有新的插件,提示用户下载安装,插件的apk清单描述为Action为非Luncher,Category为Default。

主程序侦听packgeManager的安装完成广播,之后扫描同包名(插件当然得这么定义了,只要通过packgeManager能判断是否为自己的插件就行)的apk,之后列出来,让用户选择是否加载。

插件的加载与调用

在获取包后,通过调用系统的api可以得到sharedUserId与主程序相同的apk的context,也就是句柄,获得了句柄,通过这个context可以得到classloader,之后就简单了,如何知道这个插件提供什么功能?

这个可以用xml描述,比如这个xml是插件apk的一个资源,就像spring这个框架一样。xml中描述了这个插件有哪些类,提供哪些方法,这些方法需要传入什么参数,返回什么类型。我的demo中为了方便,是用接口,每个插件有一个类提供一个相同的方法,来获取一个map集合,获得这个插件的描述。

ok,到这里就知道加载的插件提供什么功能了。

在上面贴出来的代码中,是循环遍历每个插件,并把每个插件提供的功能以Button的方式显示给用户,点击按钮,就执行了插件的功能,执行时,并不是activity转向(这样就无意义了),而是在主程序自身的context句柄中执行,也就是在自身的窗体中执行。

代码中有一段注释,说明,如果插件有用到context时,记得传递进去的是主程序的context,这样窗体才能附加到这个句柄中,如果传递的是插件的context,它没有一个窗体实例,是无法将一些窗体附加进去的,无任何效果。

这里只提供思路,有时间的话研究一下,看能不能搞个通用的框架出来。还有另一种方法,不通过apk形式,以后会写出来。

这里有一些待验证的问题,比如插件的权限问题,如果插件需要的一些权限在主程序中没有声明,会是个什么情况,能不能实时申请呢?这个需要高人指点。或者在主程序中把能声明的权限预先声明了也不错。还有就是native层代码的问题,如果插件包含了native层代码,会是个什么情况,这也需要验证。

这是demo下载:

http://files.cnblogs.com/hangxin1940/android_plugin_program.rar


更多相关文章

  1. Android开发环境搭建
  2. 使用NetBeans搭建Android开发环境
  3. android 创建桌面快捷方式 、插件
  4. Android开发者实用代码片段 与大家分享
  5. android插件汇总
  6. 深入Gradle插件开发
  7. unity内置浏览器插件UniWebView的使用(支持Android,ios,Mac)
  8. Android中ListView中Item的设置
  9. Android(安卓)SDK与ADT不匹配的问题 This Android(安卓)SDK requ

随机推荐

  1. Linux学习之路第一篇——关于Linux的认知
  2. 冷月手撕408之操作系统(15)-内存分配之基
  3. 冷月手撕408之操作系统(9)-进程同步与互
  4. 冷月手撕408之计算机组成原理(1)-导学
  5. 冷月手撕408之操作系统(14)-内存分配之非
  6. 冷月手撕408之操作系统(6)-线程概述
  7. 冷月手撕408之操作系统(5)-进程概述
  8. 冷月手撕408之操作系统(23)-输入输出管理
  9. 冷月手撕408之操作系统(12)-内存的分配与
  10. 冷月手撕408之数据结构(4)-链表