Android Small插件化框架源码分析

目录

概述 
Small如何使用 
插件加载流程 
待改进的地方

一、概述

Small是一个写得非常简洁的插件化框架,工程源码位置:https://github.com/wequick/Small 
插件化的方案,说到底要解决的核心问题只有三个:

  • 1.1 插件类的加载

    • 这个问题的解决和其它插件化框架的解决方法差不多。Android的类是由DexClassLoader加载的,通过反射可以将插件包动态加载进去。Small的gradle插件生成的是.so包,在初始化的时候会通过.so文件生成.zip文件,再由.zip文件生成一个dex元素,反射添加到宿主类加载器的dexPathList里。
  • 1.2 插件资源的处理

    • 这里各插件化框架解决办法一般有两种想法:一是插件间不共享资源访问,办法就是每个插件生成一个AssertManager来访问它自己的资源,这样就不会存在资源id冲突的问题;另一种是大家都共用一个AssetManager,这样插件的资源是共享的,可以相互访问,但是要解决资源id冲突的问题。Small采用的是后者,通过修改aapt的生成产物解决了资源id冲突问题,由于共享资源访问,可以做到极小或者根本没有资源冗余,从而减小插件包的大小;
  • 1.3 Activity注册和生命周期问题

    *大部分插件化框架解决办法都是采用在宿主工程里预先注册Activity占坑,然后通过占坑Activity将生命周期回调传回给插件Activity的方式。这里Small处理的比较有特色,通过替换 ActivityThread 里的mInstrumentation,在Instrumentation的newActivty实现里面实例化了插件Activity,通过较小改动就能完全解决生命周期回调的问题。

Small的功能模块主要有: 
gradle-small插件:Small中的一个gradle自定义插件,用于打包组件; 
small library:提供给用户使用的Android Library,主要提供插件加载,解析等功能;

二、使用Small

2.1工程命名 
首先Small对工程名称如下要求:

  • app:host工程
  • app.*:app插件工程;
  • lib.*:library插件工程;
  • web.*:web插件工程;
  • 其他:其他assert 工程;

2.2 插件引入 
在host工程的rootProject的build.gradle中需要引入small插件; 
引入small插件后,默认帮你的所有工程引入了一个library依赖small,我们通过small提供的各种接口来实现插件化得一些功能,比如加载插件,打开某个插件中的ui界面,创建某个插件提供的fragment对象等;

2.3 插件声明 
作为host程序,要做的最重要的事情就是插件管理,插件跳转uri声明: 
插件声明在host程序的assert/bundle.json中声明,格式如下:

{
"version": "1.0.0",
"bundles": [
{
"uri": "lib.utils",
"pkg": "net.wequick.example.small.lib.utils"
},
{
"uri": "lib.style",
"pkg": "com.example.mysmall.lib.style"
},
{
"uri": "main",
"pkg": "net.wequick.example.small.app.main"
},
{
"uri": "home",
"pkg": "net.wequick.example.small.app.home",
"rules"{
"page1",".MyPage1",
"page2","net.wequick.example.small.app.home.MyPage2"
}
},
{
"uri": "message",
"pkg": "net.wequick.example.small.app.message"
},
{
"uri": "find",
"pkg": "net.wequick.example.small.app.find"
},
{
"uri": "mine",
"pkg": "net.wequick.example.small.app.mine"
},
{
"uri": "detail",
"pkg": "net.wequick.example.small.app.detail"
},
{
"uri": "about",
"pkg": "net.wequick.example.small.web.about"
}
]
}

bundles 中的每个元素都是一个插件的声明;

    {
"uri": "home",
"pkg": "net.wequick.example.small.app.home",
"rules"{
"page1",".MyPage1",
"page2","net.wequick.example.small.app.home.MyPage2"
}
}

在采用small框架的应用中,跳转插件的界面都是通过uri来指定的,也就是一个uri唯一对应一个插件; 
pkg是插件的包名; 
rules:如果插件提供了多个界面供其他人使用,我们需要通过rules将它们区分开来;

举个例子:

Small.openUri("home", context); 

上面这行语句是打开一个插件的界面,home对应的就是上面的uri字段,我们通过home,查找到对应的插件,然后它会打开这个插件在AndroidManifest.xml中声明的第一个Activity。

如果你要调起插件中声明的其他acitivity,你就需要用到rules了,首先是在bundles.json中声明你要跳转的acitivity,如上,如果你想调用home插件中的界面activity MyPage1, 
你只需要写如下语句:

Small.openUri("home/page1", context); 

这个时候调用到的就是net.wequick.example.small.app.home.MyPage1这个类对应的activity了

你在调用插件的时候也可以通过queryparameter传参:

Small.openUri("home?from=main", context);

在调起的插件工程获取参数:
Uri uri = Small.getUri(this);
if (uri != null) {
String from = uri.getQueryParameter("from");
// Do stuff by `from'
}

2.4 插件加载管理 
在host中我们一般要做两件事情: 
初始化插件的baseUri; 
这个一般在Application的onCreate完成:Small.setBaseUri(“http://m.wequick.net/demo/“);

另外是加载所有插件(待优化,一次加载所有插件过于耗时,我们应该是只加载必备插件,然后再慢慢加载其他插件) 
Small.setUp(this, new net.wequick.small.Small.OnCompleteListener() { 
@Override 
public void onComplete() { 
mContentView.postDelayed(new Runnable() { 
@Override 
public void run() { 
Small.openUri(“main”, LaunchActivity.this); 
finish(); 

}, 2000); 

}); 
setUp提供了插件加载完成的回调,一般我们等插件加载完才能通过openUri去启动界面展示;

2.5 常用操作

  • 打开界面: 
    Small.openUri(“main”, context);

  • 创建插件提供的fragment : 
    Fragment fragment = Small.createObject(“fragment-v4”, “home”, context); 
    如果没有通过rules指定类名,默认的类名是 包名.MainFragment 
    如果指定了类名,和前面的规则一样。 
    createObject的第一个参数目前仅支持”fragment”或者”fragment-v4”

  • 获取某个插件界面的Intent 
    有时候我们不是直接打开界面,比如通知栏通知,我们需要设置一个PendingIntent ,那这个时候需要的是一个Intent 
    此时可通过获取:

Intent intent = Small.getIntentOfUri("main",context)
  • 获取调用时候的query信息:
//调用
Small.openUri("home?from=main", context); //参数获取;
Uri uri = Small.getUri(this);
if (uri != null) {
String from = uri.getQueryParameter("from");
// Do stuff by `from'
}

三、插件加载流程

Small的核心类比较少,主要包含三类:

  • Small:接口类,提供用户能使用的各类接口;
  • Bundle: 代表插件,保存了插件的全部信息,控制了插件的load流程,以及lauch流程;它会调用各类BundleLauncher来干活;
  • BundleLauncher:有多个子类,比如.app.,.lib.类的插件,对应的是ApkBundleLauncher,.web.*对应的就是WebBundleLauncher,其他对应的就是ActivityLauncher

插件相关的操作主要有load和lauch:

其中应用启动的时候要准备插件环境,进行的就是load操作,主要是解析插件信息并缓存起来,并将插件dex和资源添加到host;load完成才能进行其他插件操作

  • 在load插件的时候,一般分两步: 
    preloadBundle,一般判断插件能否被加载,返回false就不需要进行加载了;这里我们一般进行插件合法性检查 
    loadBundle(Bundle bundle):真正加载解析插件的各类信息并存入Bundle对象; 
    lauch指的是一般指加载插件界面,典型的就是调用Small.openUri打开插件界面;
  • lauch插件的时候也分两步: 
    prelaunchBundle(Bundle bundle):准备Bundle的一些必要信息:一般是生成bundle的intent信息,主要是要启动的类名的生成;(见ApkBundleLauncher) 
    launchBundle(Bundle bundle, Context context):判断插件是否能加载,能加载就启动acitivity;

3.1 load的流程

Small.setup   -->   Bundle.setupLaunchers   (调用各BundleLauncher的setUp,其中ApkBundleLauncher的setUp会替换掉ActivityThread的mInstrumentation成员变量)  -->Bundle.loadLaunchableBundles(解析bundle.json,并加载bundle)
-->Bundle.loadManifest(读取了bundle.json) --> Bundle.loadBundles ( 开启异步线程进行加载)
异步线程里面的流程:
-->Bundle.prepareForLaunch
-->遍历ActivityLauncher,WebBundleLauncher,ApkBundleLauncher,调用它们的BundleLauncher.resolveBundle 方法寻找合适的BundleLauncher解析插件包;
-->BundleLauncher.preloadBundle(主要是判断能否加载) -->BundleLauncher.loadBundle(正真的加载解析动作) 除了直接使用的三个BundleLauncher类(ActivityLauncher,WebBundleLauncher,ApkBundleLauncher),还有两个中间类,SoBundleLauncher 和AssetBundleLauncher,
其中SoBundleLauncher主要是提供了一个preloadBundle函数实现,里面实现了1 按支持的type与package名对比,快速判断此BundleLauncher能否解析此插件;2 校验插件签名是否合法来确定是否要解析次插件;
AssetBundleLauncher从SoBundleLauncher继承,干了WebBundleLauncher 要做的loadBundle大部分的活。

3.2 启动界面流程

Small.openUri --> Bundle.getLaunchableBundle -->Bundle.matchesRule(通过uri匹配合适的bundle) --> Bundle.launchFrom-->ApkBundleLauncher.launchBundle-->ApkBundleLauncher.prelaunchBundle-->BundleLauncher.launchBundle->Activity.startActivityForResult -->ApkBundleLauncher.InstrumentationWrapper.execStartActivity--》ApkBundleLauncher.InstrumentationWrapper.wrapIntent(将插件activity类保存在intent的category中,同时将intent的component里面的类替换为host 中声明的占位Activity,以通过ActivityManager的检查)--》ApkBundleLauncher.InstrumentationWrapper.newActivity(取出intent中的插件activity类,并实例化返回,用于接收生命周期回调) --》ApkBundleLauncher.InstrumentationWrapper.callActivityOnCreate(加入插件apk到AssertManager中,用于读取插件资源,应用插件Theme)

3.3 插件打包过程

todo

四、需要改进的点

  • 加载插件优先级 
    目前load插件的时候是把所有插件都加载进来才算准备好,应该改为只加载必备插件就可以发准备好回调让主流程继续跑,其他插件在后台继续加载;

  • 优化通过PackageManager获取包内Activity信息 
    经实际测试,通过PackageManager获取包内Activity信息会耗时很大(700ms),但是加载插件又需要插件包里的activity的信息,拟通过在打包时提取相应信息放入文件,加载插件只需要读取文件解析就行了;

  • 插件合法性校验 
    目前的插件合法性是通过包的签名对比来实现的,也是调用的PackageManager,效率比较低.

  • 字符串资源超过128编译报错问题 
    目前发现插件处理资源的时候,如果工程中存在strings.xml中有字符串资源超过128字符就会报错,还有存在字符串样式的也会有问题;应该是作者对资源索引表中的StringPoll结构理解有误导致。

      • 嵌入式企鹅圈原创团队由阿里、魅族、nvidia、龙芯、炬力、拓尔思等资深工程师组成。百分百原创,每周两篇,分享嵌入式、Linux、物联网、GPU、Android、自动驾驶等技术。欢迎扫码关注微信公众号:嵌入式企鹅圈,实时推送原创文章!

Android Small插件化框架源码分析的更多相关文章

  1. .NET MVC 插件化框架源码

    本来想把源码整理了放github上,但最近一直忙,就直接在这里放出来了,还写得不太完整,里面有几个例子,插件上传也没写,只写了插件zip包解压,如果大家在使用中有什么疑问,可以加QQ群:1429391 ...

  2. Android Small插件化框架解读——Activity注册和生命周期

    通过对嵌入式企鹅圈原创团队成员degao之前发表的<Android Small插件化框架源码分析>的学习,对Android使用的插件化技术有了初步的了解,但还是有很多需要认真学习的地方,特 ...

  3. YII框架源码分析(百度PHP大牛创作-原版-无广告无水印)

           YII 框架源码分析    百度联盟事业部——黄银锋 目 录 1. 引言 3 1.1.Yii 简介 3 1.2.本文内容与结构 3 2.组件化与模块化 4 2.1.框架加载和运行流程 4 ...

  4. 介绍开源的.net通信框架NetworkComms框架 源码分析

    原文网址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 语言编写的TCP/UDP通信框架  作者是英国人  以前是收费的 售价249英镑 我曾经花了 ...

  5. Spark RPC框架源码分析(一)简述

    Spark RPC系列: Spark RPC框架源码分析(一)运行时序 Spark RPC框架源码分析(二)运行时序 Spark RPC框架源码分析(三)运行时序 一. Spark rpc框架概述 S ...

  6. Spark RPC框架源码分析(二)RPC运行时序

    前情提要: Spark RPC框架源码分析(一)简述 一. Spark RPC概述 上一篇我们已经说明了Spark RPC框架的一个简单例子,Spark RPC相关的两个编程模型,Actor模型和Re ...

  7. Spark RPC框架源码分析(三)Spark心跳机制分析

    一.Spark心跳概述 前面两节中介绍了Spark RPC的基本知识,以及深入剖析了Spark RPC中一些源码的实现流程. 具体可以看这里: Spark RPC框架源码分析(二)运行时序 Spark ...

  8. nodejs的Express框架源码分析、工作流程分析

    nodejs的Express框架源码分析.工作流程分析 1.Express的编写流程 2.Express关键api的使用及其作用分析 app.use(middleware); connect pack ...

  9. Unity时钟定时器插件——Vision Timer源码分析之二

      Unity时钟定时器插件——Vision Timer源码分析之二 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面的已经介绍了vp_T ...

随机推荐

  1. Hibernate —— Hibernate 配置文件

    1.Hibernate 配置文件主要用于配置**数据库连接**和 Hibernate 运行时所需的**各种属性**. 2.每一个 Hibernate 配置文件对应一个 Configuration 对象 ...

  2. Web API 自动生成帮助文档并使用Web API Test Client 测试

    之前在项目中有用到webapi对外提供接口,发现在项目中有根据webapi的方法和注释自动生成帮助文档,还可以测试webapi方法,功能很是强大,现拿出来与大家分享一下. 先看一下生成的webapi文 ...

  3. 解决android sdk 无法更新

    今天更新sdk,遇到了更新下载失败问题: Fetching https://dl-ssl.google.com/android/repository/addons_list-2.xmlFetched ...

  4. 不可或缺 Windows Native (16) - C++: 函数重载, 缺省参数, 内联函数, 函数模板

    [源码下载] 不可或缺 Windows Native (16) - C++: 函数重载, 缺省参数, 内联函数, 函数模板 作者:webabcd 介绍不可或缺 Windows Native 之 C++ ...

  5. Mybatis添加返回主键ID

    1.对应xml文件<insert id="insert" parameterType="DetectStandard"useGeneratedKeys=& ...

  6. 项目总结笔记系列 Autonomy IDOL Server KT Session1

    在说上面这个图之前,先说一下背景知识. Autonomy地址: http://www.autonomy.com.cn http://www.autonomy.com.cn/content/home/i ...

  7. First day on cnblogs,破壳日~~

    突然很想找个地方写一点文章,又想到了这个老账号:没有因长期静默被删号实在是很感动...好吧,就这样纪念一下.第一天. I suddenly wanna write something and reca ...

  8. 「Ionic」使用chrom時的跨域問題

    前言:在angularjs請求數據時,會遇到跨域問題,解決辦法有很多,但是都不是我想要的(很多人云亦云,都解決不了問題).如果你只是想在本機測試使用,可以參考如下設置.   具體辦法: 1.在电脑上新 ...

  9. # 关于Apache的25个初中级面试题

    注:本文从自己的Markdown博客复制出,除标题字体加粗.代码风格改变.图片重新上传外其余均为markdown语法. 标签(空格分隔): LAMP --- 原文:[关于Apache的25个初中级面试 ...

  10. jvm三的三种类加载器

    JVM有三种类加载器:bootstrap负责加载系统类,extclassloader负责加载扩展类,appclassloader负责加载应用类.他们主要是分工不一样,各自负责不同的区域,另外也是为了实 ...