Android应用插件式开发

         一般的,一个Android应用在开发到了一定阶段以后,功能模块将会越来越多,APK安装包也越来越大,用户在使用过程中也没有办法选择性的加载自己需要的功能模块。此时可能就需要考虑如何分拆整个应用了,将一个应用分成若干个小的应用。类似于其他平台插件的方式,用户可以在主应用中可以选择性的下载需要的插件,不需要该功能,则不需要下载。     

   有人可能会想到,是否可以像其他平台那样,下载一个类似于dll文件,或者jar包,就能自动识别并且加载该功能?可惜的是,在Android平台上是不允许直接动态加载jar包的,所以,想实现这种功能,还是要以独立APK的方式来加载。从设计的角度,具体的插件是没有独立运行的入口的,也不允许有桌面图标存在,必须从主应用中打开,关闭后回到主应用。从用户的角度看,可以在应用中加载需要的功能并且使用,也就类似于其他平台插件的方式了。所以我们再开发的时候,需要考虑以下步骤:

1.    识别具体的插件是否已经安装

2.      如果已经安装要判断是否需要升级(服务器端获取最新的版本和本地的比较)

3.      下载并且安装(或者升级)插件

4.      卸载该插件其中需要注意的是:插件apk对应的Manifest文件中不要提供启动的入口;主应用和插件之间交互的提示,主应用和插件应用最好有相同的android:sharedUserId属性,这样插件可以方便的获取主应用的资源、数据库等等。主应用可以以Intent方式启动具体的插件,同时带入Map类型参数或者json串参数,在插件APK中解析具体参数,实现业务逻辑。

     具体实现:

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

具体的Manifest文件配置如下:

 

<manifestxmlns: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中AndroidPluginFrameworkActivity类里面的相关方法。其中findMyPlugin为查找可用的的插件,主要是查找uid相关的并且已经安装到系统的apk,并结合每个插件应用assets文件夹下的plugin.xml保存每个插件中的可以调用的方法。

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

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

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

     这个可以用xml描述,比如这个xml是插件apk的一个资源,就像spring这个框架一样。xml中描述了这个插件有哪些类,提供哪些方法,这些方法需要传入什么参数,返回什么类型。我的demo中为了方便,是用接口,每个插件有一个类提供一个相同的方法,来获取一个map集合,获得这个插件的描述。如果插件有用到context时,记得传递进去的是主程序的context,这样窗体才能附加到这个句柄中,如果传递的是插件的context,它没有一个窗体实例,是无法将一些窗体附加进去的,无任何效果。


  

(一)   需要说明的几点

1.       插件的权限问题,如果插件需要的一些权限在主程序中没有声明,这时只要在插件中声明就可以了不要在主程序中声明。

2.       每一个插件方法有且只有一个Context参数传入,并且插件向主程序提供调用的类,也必须是个activity,其方法没有返回值,有且仅有一个Context参数,框架会反射这个方法,并将主程序的context句柄传入,插件方法无法返回个主应用任何值,插件应用要传递给主应用的值可以再描述类中体现。

3.       plugin.xml不能改名,并且要放在工程的assets目录下。

plugin.xml的格式一般如下:

<?xmlversion=”1.0” encoding=”UTF-8”>

<plugin-features>

        <description name=”com.package.name.and.description.name”/>

        <feature name=”com.package.name.and.activity.name1”>

                  <method need-context=”true” name=”methodName1”>描述信息</method>

                  <method need-context=”true” name=”methodName2”>描述信息</method>

        </feature>

        <feature name=”com.package.name.and.activity.name2”>

                  <method need-context=”true” name=”methodName1”>描述信息</method>

        </feature>

</plugin-features>

 

其中 <description name=”com.package.name.and.description.name”/>的属性所代表的类是描述插件信息的类,这个类必须继承于和主应用程序一模一样的类(连包名也必须一样)否则会抛出类无法找到的错误,feature代表提供插件方法的类(必须是activity类)method为相关的方法。

 

(二)   可以完成的功能

 

1.    可以用插件中资源的id访问参加的相关资源(图片,音频,string.xml配置文件中的信息,布局文件描述等),至于能否获取插件中的view,目前还没有试成功。

2.    可以调用插件模块中的插件方法(插件方法必须在activity中)。

3.    可以通过调用插件方法来,调用插件内的相关方法。

4.    在插件方法中,所有改变UI的操作都需要借助于context来完成。

5.    当然,插件也可以访问主应用的图片,string.xml,以及assets文件夹中存储的资源利用主应用调用插件方法,传过来的context对象。

6.    插件可以访问主应用的shareperence中存储的内容,以及被contentprivoder封装好的数据库等的相关访问。

7.     

(一)   需要说明的几点

1.       插件的权限问题,如果插件需要的一些权限在主程序中没有声明,这时只要在插件中声明就可以了不要在主程序中声明。

2.       每一个插件方法有且只有一个Context参数传入,并且插件向主程序提供调用的类,也必须是个activity,其方法没有返回值,有且仅有一个Context参数,框架会反射这个方法,并将主程序的context句柄传入,插件方法无法返回个主应用任何值,插件应用要传递给主应用的值可以再描述类中体现。

3.       plugin.xml不能改名,并且要放在工程的assets目录下。

plugin.xml的格式一般如下:

<?xmlversion=”1.0” encoding=”UTF-8”>

<plugin-features>

        <description name=”com.package.name.and.description.name”/>

        <feature name=”com.package.name.and.activity.name1”>

                  <method need-context=”true” name=”methodName1”>描述信息</method>

                  <method need-context=”true” name=”methodName2”>描述信息</method>

        </feature>

        <feature name=”com.package.name.and.activity.name2”>

                  <method need-context=”true” name=”methodName1”>描述信息</method>

        </feature>

</plugin-features>

 

其中 <description name=”com.package.name.and.description.name”/>的属性所代表的类是描述插件信息的类,这个类必须继承于和主应用程序一模一样的类(连包名也必须一样)否则会抛出类无法找到的错误,feature代表提供插件方法的类(必须是activity类)method为相关的方法。

 

(二)   可以完成的功能

 

1.    可以用插件中资源的id访问参加的相关资源(图片,音频,string.xml配置文件中的信息,布局文件描述等),至于能否获取插件中的view,目前还没有试成功。

2.    可以调用插件模块中的插件方法(插件方法必须在activity中)。

3.    可以通过调用插件方法来,调用插件内的相关方法。

4.    在插件方法中,所有改变UI的操作都需要借助于context来完成。

5.    当然,插件也可以访问主应用的图片,string.xml,以及assets文件夹中存储的资源利用主应用调用插件方法,传过来的context对象。

6.    插件可以访问主应用的shareperence中存储的内容,以及被contentprivoder封装好的数据库等的相关访问。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值