文档章节

Android插件化开发,初入殿堂

kymjs张涛
 kymjs张涛
发布于 2014/10/12 13:24
字数 1453
阅读 13178
收藏 59

好久没有写博客了,这次准备写写我这几天的研究成果——Android插件化开发框架CJFrameForAndroid。

背景交代

    首先,你需要知道什么是插件化开发。就拿最常见的QQ来说,在第三个界面动态那里有个管理,点开后可以选择很多的增植功能,这里腾讯只放了一些网页应用,那么如果未来想加入一个打飞机游戏,要怎么做?让用户重新安装吗,这就是插件化开发所解决的问题。

    用一句话来概括插件式开发:你基本上可以理解为让一个apk不安装也可以被运行。只不过这个运行是有很多限制的运行,所以才叫插件否则就叫病毒了。其实在目前淘宝、百度、腾讯、等都有成熟的动态加载框架,包括apkplug,但是它们都是不开源的。

    说一下我认为这项技术的难点:1、一个未被安装的apk正常情况无法被运行;2、这个apk的资源没办法被引用;3、这个apk的界面就算被加载,也没办法与用户交互。

    最初查遍了资料,第一点好解决,在Android中有一个dexClassLoad类加载器,大家应该明白了,就是通过反射加载一个类来运行。第二点,网上有两种方法:可以将插件的资源放到sd卡上通过流的形式读取,不过也有人反对说用流读取会有问题,通配性太差;一种比较好的解决办法是将apk中的资源复制一份到当前app内,然后就可以加载了。这种办法是不错,但是用户每下载一次插件就复制一份,久而久之,对空间要求太高了,还有就是第三点也没办法解决。而第三点,在github上有一个叫AndroidDynamicLoader的项目,是通过用Fragment做为插件的表现形式,由于Fragment特殊性(既可以处理逻辑交互又具备与Activity相同的生命周期)。可是Fragment限制性太大了,太过碎片化使得使用起来复杂性过高。直到我找到了一篇360的官方博客,博客给了一种思路:通过代理/委托模式设计的Application类去动态的改变一个apk所在的环境,实现动态加载的目的。抱着这种思路,我曾想自己去设计一个application类,但是技术有限,太复杂了,于是结合AndroidDynamicLoader的思路与360的思路,我自己设计了一个Activity去代理插件的Activity,于是就有了CJFrameForAndroid.

原理描述

首先解释几个名词:

APP项目:指要调用插件apk的那个已经安装到用户手机上的应用。
插件项目:指没有被安装且希望借助已经安装到手机上的项目运行的apk。
插件化:Activity继承自CJActivity,且与APP项目jar包冲突已经解决的插件项目称为已经被插件化。
Activity事务:在CJFrameForAndroid中,一个Activity的生命周期以及交互事件统称为Activity的事务。
托管所:指插件中的一个委派/代理Activity,通过这个Activity去处理插件中Activity的全部事务,从而表现为就像插件中的Activity在运行一样。

CJFrameForAndroid的实现原理是通过类加载器,动态加载存在于SD卡上的apk包中的Activity。通过使用一个托管所,插件Activity全部事务(包括声明周期与交互事件)将交由托管所来处理,间接实现插件的运行。
一句话描述:CJFrameForAndroid中的托管所,复制了插件中的Activity,来替代插件中的Activity与用户交互。
看到这里你应该就明白了,整个框架最核心的部分就是这个托管所。这里给出CJFrameForAndroid中这个托管所的细节代码:

    /**
     * 通过反射,获取到插件的资源访问器
     */
    protected void initResources() {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod(
                    "addAssetPath", String.class);
            addAssetPath.invoke(assetManager, mDexPath);
            mAssetManager = assetManager;
        } catch (Exception e) {
            e.printStackTrace();
        }
        Resources superRes = super.getResources();
        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),
                superRes.getConfiguration());
        mTheme = mResources.newTheme();
        mTheme.setTo(super.getTheme());
    }

    /**
     * 启动插件的Activity
     */
    protected void launchPluginActivity() {
        PackageInfo packageInfo = CJTool.getAppInfo(this, mDexPath);
        if ((packageInfo.activities != null)
                && (packageInfo.activities.length > 0)) {
            String activityName = packageInfo.activities[mAtyIndex].name;
            mClass = activityName;
            launchPluginActivity(mClass);
        }
    }

    /**
     * 启动指定的Activity
     * 
     * @param className
     *            要启动的Activity完整类名
     */
    protected void launchPluginActivity(final String className) {
        try {
            Class<?> atyClass = getClassLoader().loadClass(className);
            Constructor<?> atyConstructor = atyClass
                    .getConstructor(new Class[] {});
            Object instance = atyConstructor.newInstance(new Object[] {});
            setRemoteActivity(instance);
            mPluginAty.setProxy(this, mDexPath);
            Bundle bundle = new Bundle();
            bundle.putInt(CJConfig.FROM, CJConfig.FROM_PROXY_APP);
            mPluginAty.onCreate(bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 保留一份插件Activity对象
     */
    protected void setRemoteActivity(Object activity) {
        if (activity instanceof I_CJActivity) {
            mPluginAty = (I_CJActivity) activity;
        } else {
            throw new ClassCastException(
                    "plugin activity must implements I_CJActivity");
        }
    }

本框架目前仅仅是一个开发阶段,仅仅是实现了插件Activity的运行(原理上来说,动态注册的广播也可以运行),而Service、contentProvider都没办法使用,这些都仍在研究中。
在未来的某一天,也许会将这个CJFrameForAndroid插件框架与KJFrameForAndroid快捷开发框架合并,组成一个更完善应用开发框架,对自己说:加油!

●目前仅支持Activity和Fragment,其他特殊组件暂未测试。
●APP项目和插件项目中,都需要使用到CJFrameForAndroid的jar包。
●在项目中必须加入托管所声明。
●在开发插件的时候,必须继承CJActivity;
●在插件的Activity中,一切使用this的部分必须使用that来替代;
●在插件Activity跳转时,推荐使用CJActivityUtils类来辅助跳转;
●在插件和APP两个工程中不能引用相同的jar包;

© 著作权归作者所有

共有 人打赏支持
kymjs张涛

kymjs张涛

粉丝 508
博文 64
码字总数 76485
作品 4
普陀
Android工程师
私信 提问
加载中

评论(9)

kymjs张涛
kymjs张涛

引用来自“maybewaityou”的评论

东西很给力,但我想知道,插件里的Activity和Service,都不用注册的吗?
是的,不用。举个例子,插件就好像是傀儡,真正执行Activity和Service的实际上是在应用中注册的托管所类。
maybewaityou
maybewaityou

引用来自“闲适_人生”的评论

楼主,动态加载插件apk启动不了,报错 Caused by: java.lang.RuntimeException: Binary XML file line #31: You must supply a layout_height attribute. 我都不知道哪个布局的31行啊
我同事的三星手机也出现了同样的问题,但是我的米2能够正常运行。。。
maybewaityou
maybewaityou

引用来自“闲适_人生”的评论

楼主,动态加载插件apk启动不了,报错 Caused by: java.lang.RuntimeException: Binary XML file line #31: You must supply a layout_height attribute. 我都不知道哪个布局的31行啊
我同事的三星手机也出现了同样的问题,但是我的米2能够正常运行。。。
maybewaityou
maybewaityou
东西很给力,但我想知道,插件里的Activity和Service,都不用注册的吗?
闲适_人生
闲适_人生
楼主,动态加载插件apk启动不了,报错 Caused by: java.lang.RuntimeException: Binary XML file line #31: You must supply a layout_height attribute. 我都不知道哪个布局的31行啊
DannyZhou
DannyZhou
很给力啊。希望继续看到这样的好文章。
michaelpan
michaelpan
认真学习一下
kymjs张涛
kymjs张涛

引用来自“火蚁”的评论

感觉你研究得有点深入啊,得慢慢才能消化这些东西
没有,看了很多的博客和代码才有了个思路。
火蚁
火蚁
感觉你研究得有点深入啊,得慢慢才能消化这些东西
Android插件化开发,运行未安装apk中的Service

如果你还不知道什么叫插件化开发,那么你应该先读一读之前写的这篇博客:Android插件化开发,初入殿堂 上一篇博客主要从整体角度分析了一下Android插件化开发的几个难点与动态加载没有被安装...

kymjs张涛
2014/10/15
0
5
Android组件化开发实践(七):开发常见问题及解决方案

我们在单一工程里开发时代码运行良好,但是在进行组件化开发时,经常会出现一些莫名其妙的问题。 1. ButterKnife无法使用 组件化之后,在library中使用ButterKnife,会发现引用R.id的地方都会...

云之崖
10/09
0
0
Android插件化开发之动态加载技术学习

Android插件化开发之动态加载技术学习 为什么要插件化开发和动态加载呢?我认为原因有三点: 可以实现解耦 可以解除单个dex函数不能超过65535的限制 可以给apk瘦身,比如说360安全卫士,整个...

幸运券发放
05/18
0
0
高手问答第 212 期 — 连 Binder 都不知道?来看看 Android 插件化开发技术分享

OSCHINA 本期高手问答(2018 年 9 月 13 日 — 9 月 19 日)我们请来了包建强@北漂包包 为大家解答关于 Android 插件化开发方面的问题。 包建强,毕业于复旦大学数学系。先后在多家互联网公司担...

局长
09/12
2.3K
27
Android组件化开发实践(九):自定义Gradle插件

本文紧接着前一章Android组件化开发实践(八):组件生命周期如何实现自动注册管理,主要讲解怎么通过自定义插件来实现组件生命周期的自动注册管理。 1. 采用groovy创建插件 新建一个Java Li...

云之崖
10/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Centos 7 JDK、Tomcat9 安装并配置

Centos 7 JDK、Tomcat9 安装并配置 2017年04月17日 22:44:36 我是一座离岛 阅读数:5707更多 个人分类: webCentOS 版权声明:博主原创文章,转载请注明出处。 https://blog.csdn.net/ngl272...

linjin200
15分钟前
1
0
Spring boot遇到的问题

2018年12月11日 Ver:2.0.2升级到2.1.1文件上传遇到问题 The method setMaxFileSize(String) from the type MultipartConfigFactory is deprecated 是不是就不用设置了?...

夏碌冬藏
16分钟前
1
0
Spark sql操作Hive

这里说的是最简便的方法,通过Spark sql直接操作hive。前提是hive-site.xml等配置文件已经在Spark集群配置好。 val logger = LoggerFactory.getLogger(SevsSpark4.getClass) def main(args:...

守望者之父
16分钟前
1
0
炫酷粒子表白 | 听说女神都想谈恋爱了!

最近听女神说想谈恋爱了,✧(≖ ◡ ≖) 嘿嘿,一定不能放过这个机会,给她来个不一样的表白。 那么咱们就一起来把这个粒子系统玩出花来吧 演示地址: https://es2049.studio/work-show/text...

我的卡
18分钟前
2
0
Spark Streaming管理Kafka偏移量

前言 为了让Spark Streaming消费kafka的数据不丢数据,可以创建Kafka Direct DStream,由Spark Streaming自己管理offset,并不是存到zookeeper。启用Spark Streaming的 checkpoints是存储偏移...

架构师springboot
20分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部