zoukankan      html  css  js  c++  java
  • android 程序开发的插件化 模块化方法 之一

      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 
     public class AndoirdpluginActivity extends ActivityGroup implements OnClickListener ,OnScrollCompleteListener{
         private LinearLayout llMainLayout;
         
         //workspace,看看luncher的源码,这个就是桌面那个多屏的实现
         private WorkSpace wkMain;
         private Button btnFindPlugins;
         private CheckBox chbAttachMain;
         
         private LocalActivityManager m_ActivityManager;
         
         //这个bean的集合,就相当于插件的描述集合
     //每个bean也就是一个插件的各种描述
         private List<PluginBean> plugins;
         
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.main);
             
             llMainLayout=(LinearLayout) findViewById(R.id.main_llMainLayout);
             wkMain=(WorkSpace) findViewById(R.id.main_wkMain);
             btnFindPlugins=(Button) findViewById(R.id.main_btnFindPlugins);
             chbAttachMain=(CheckBox) findViewById(R.id.main_chbAttachMain);
             
             m_ActivityManager = getLocalActivityManager();
             
             wkMain.setOnScrollCompleteLinstenner(this);
             btnFindPlugins.setOnClickListener(this);
         }
     
         @Override
         public void onClick(View v) {
             attachPlugin(findPlugins());
             btnFindPlugins.setVisibility(View.GONE);
         }
         
         /**
          * 加载插件列表
          * @param plugins
     */
         private void attachPlugin(final List<PluginBean> plugins){
             Log.e("ydt", "   ");
             Log.e("ydt", "----- 列出插件");
             this.plugins=plugins;
             for(final PluginBean plugin:plugins){
                 Button btn=new Button(this);
                 btn.setTextColor(Color.RED);
                 btn.setText(plugin.getLabel());
                 
                 llMainLayout.addView(btn);
                 //添加事件
                 btn.setOnClickListener(new OnClickListener() {
                     
                     @Override
                     public void onClick(View v) {
                         boolean isAttack=chbAttachMain.isChecked();
                         
                         Intent it=new Intent();
                         it.setAction(plugin.getPakageName());
                         
                         //是否附加为view
                         if(isAttack){
                             //这里偷下懒,这是演示插件作为view附加到主程序中的
                             for(PluginBean plugin:plugins){
                                 
                                 Intent itt=new Intent();
                                 itt.setAction(plugin.getPakageName());
                                 ViewGroup view=(ViewGroup) (m_ActivityManager.startActivity("", itt)).getDecorView();
                                 wkMain.addView(view);
                             }
                             //一次性附加完毕算了,然后把按钮都删了,看着清净,这几个不是重点
                             llMainLayout.removeAllViews();
                             chbAttachMain.setVisibility(View.GONE);
                             wkMain.setToScreen(0);
                         }else{
                             //这里,不会把插件的窗体附加到主程序中,纯粹无用的演示
                             startActivity(it);
                         }
                     }
                 });
                 
             }
         }
         
         /**
          * 查找插件
          * @return
     */
         private List<PluginBean> findPlugins(){
             
             List<PluginBean> plugins=new ArrayList<PluginBean>();
             
             
             //遍历包名,来获取插件
             PackageManager pm=getPackageManager();
             
             
             List<PackageInfo> pkgs=pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
             for(PackageInfo pkg    :pkgs){
                 //包名
                 String packageName=pkg.packageName;
                 String sharedUserId= pkg.sharedUserId;
                 
                 //sharedUserId是开发时约定好的,这样判断是否为自己人
                 if(!"org.igeek.plugintest".equals(sharedUserId)||"org.igeek.plugintest.main".equals(packageName))
                     continue;
                 
                 //进程名
                 String prcessName=pkg.applicationInfo.processName;
                 
                 //label,也就是appName了
                 String label=pm.getApplicationLabel(pkg.applicationInfo).toString();
                 
                 PluginBean plug=new PluginBean();
                 plug.setLabel(label);
                 plug.setPakageName(packageName);
                 
                 plugins.add(plug);
             }
             
             
             return plugins;
             
         }
     
      
         /**
          * WorkSpace滚动到那个屏,会触发这个事件
          * 而worksapce中每一屏又是一个插件
          * 这个事件是用来列出当前屏幕插件所提供的应用,并且让用户调用
     */
         @Override
         public void onScrollComplete(final ScrollEvent e) {
             try {
                 final Context context = createPackageContext(plugins.get(e.curScreen).getPakageName(), Context.CONTEXT_INCLUDE_CODE|Context.CONTEXT_IGNORE_SECURITY);
                 llMainLayout.removeAllViews();
                 //这几行,通过反射获取了当前插件的描述信息,如同大部分框架的xml一样,这里算是模拟了一下IOC控制反转
                 Class clazz=context.getClassLoader().loadClass(plugins.get(e.curScreen).getPakageName()+".PluginApplication");
                 Object o=clazz.newInstance();
                 Map<String,List<String>>  r=(Map<String, List<String>>) clazz.getMethod("getDesciption").invoke(o);
                 List<String> classes=r.get("classes");
                 List<String> methods=r.get("methods");
                 
                 
                 //这里,根据获得的插件所提供的功能,来生成几个按钮显示,供我们调用
                 for(final String clas:classes){
                     for(final String method:methods){
                         Button btn=new Button(this);
                         
                         btn.setText(clas+" -> "+method+" 执行");
                         
                         
                         //点击后,就执行插件所提供的方法
                         btn.setOnClickListener(new OnClickListener() {
                             
                             @Override
                             public void onClick(View v) {
                                 try {
                                     Class c=context.getClassLoader().loadClass(plugins.get(e.curScreen).getPakageName()+"."+clas);
                                     Object o1=c.newInstance();
                                     
                                     //这里注意,context实际上就是句柄,这里如果涉及到窗体,plugin的句柄其实是不行的,因为它没有可以
     //依附的窗体
                                     
     //这个context是plugin的,通过测试,dialog这类行不通,Toast是可以的,因为
     //Toast是依附于屏幕主窗口的
     //c.getMethod(method,Context.class).invoke(o1,context); 
                                                             
     //这里则传递的是主程序的句柄
                                     c.getMethod(method,Context.class).invoke(o1,AndoirdpluginActivity.this);
                                 
                                 } catch (Exception e) {
                                     // TODO Auto-generated catch block
                                     e.printStackTrace();
                                 } 
                             }
                         });
                         llMainLayout.addView(btn);
                     }
                 }
                 
             
             } catch (Exception e1) {
                 // TODO Auto-generated catch block
                 e1.printStackTrace();
             }
         
         }
     }




    View Code


        看注释吧,主要有两点


    插件的扫描


            这种方案是,每个插件以一个单独的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://download.csdn.net/detail/by317966834/5304860




    最后    本人水平有限,难免有些疏漏或者错误,还请指正!!!

  • 相关阅读:
    【转】VS2010中 C++创建DLL图解
    [转]error: 'retainCount' is unavailable: not available in automatic reference counting mode
    [转]关于NSAutoreleasePool' is unavailable: not available in automatic reference counting mode的解决方法
    【转】 Tomcat v7.0 Server at localhost was unable to start within 45
    【转】Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds. If
    【转】SVN管理多个项目版本库
    【转】eclipse安装SVN插件的两种方法
    【转】MYSQL启用日志,和查看日志
    【转】Repository has not been enabled to accept revision propchanges
    【转】SVN库的迁移
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3045546.html
Copyright © 2011-2022 走看看