zoukankan      html  css  js  c++  java
  • Android应用开发的插件化 模块化

    在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 @Override
    133 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 @Override
    157 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 block
    174 e.printStackTrace();
    175 }
    176 }
    177 });
    178 llMainLayout.addView(btn);
    179 }
    180 }
    181
    182
    183 } catch (Exception e1) {
    184 // TODO Auto-generated catch block
    185 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下载:

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

  • 相关阅读:
    Attributes in C#
    asp.net C# 时间格式大全
    UVA 10518 How Many Calls?
    UVA 10303 How Many Trees?
    UVA 991 Safe Salutations
    UVA 10862 Connect the Cable Wires
    UVA 10417 Gift Exchanging
    UVA 10229 Modular Fibonacci
    UVA 10079 Pizza Cutting
    UVA 10334 Ray Through Glasses
  • 原文地址:https://www.cnblogs.com/super119/p/2288530.html
Copyright © 2011-2022 走看看