【Android 热修复与插件化 一】带你入门Android插件化(附demo)

本文为博主Colin原创文章,欢迎转载。 https://blog.csdn.net/colinandroid/article/details/79431502

一. 背景
Android插件化作为每个合格的Android程序员都必须会的技术,被各大厂广泛使用。随着各大厂对移动互联网的垄断,我们渐渐发现app集成的功能越来越多。比如如下几个app(携程、淘宝、支付宝): 
   
可以看到每一个app都被集成了无数的功能入口,就拿淘宝来说,“天猫”、“外卖”、“飞猪”、“拍卖”,这任何一个入口都其实是一个app,只不过被集成到“淘宝”这个入口里了。如果没有插件化技术,很难想象淘宝app的size会有多大。很可能有几个GB!!

再来看看支付宝,可以发现支付宝中提供了很多第三方app的入口,而点击这些入口跳转的也都是native页面。应用市场上的支付宝app一共只有二三十MB,而如果这些app都集成到支付宝中,那支付宝的size就不是二三十MB了,那就是二三十GB了!!

本篇blog的主题是介绍Android插件化技术,并且会提供一个仿支付宝插件化技术的demo,告诉你支付宝是如何把一个第三方app作为插件集成到自己的app里的。

二. 插件化好处
宿主和插件分开编译 
编译时只需要编译宿主app,插件app是在编译好后下发到宿主app里的。
并发开发 
宿主app什么时候发布版本跟插件app什么时候开发完没有关系,宿主app只要开发完并且为插件app提供一个入口就可以了。
动态更新插件 
插件app在开发完后下发到宿主app里,点击相应的入口就可以跳转到最新版的插件app了。
按需下载模块
解决方法数或变量数爆棚(65536)
三. 随便一个app都能集成到支付宝吗?
答案是:不能! 
我们来思考,支付宝要跳转到一个插件的Activity,而插件是没有被安装的,它没有上下文,也就没有生命周期,那么插件Activity的生命周期就要由宿主app来控制。为此,我们需要建立一套标准。

四. 插件化程序结构
我们先来看下插件化程序结构,了解下其大致框架,对程序有宏观感受。下面我们直接开始撸代码。 


五. 动态加载apk
1. 插件app的activity没有在宿主app中注册,该怎么办?
插桩,一个空的Activity,专门用来加载插件app中的activity,这个Activity叫ProxyActivity,后面我会具体去讲这个空Activity该如何实现。我们只需要在宿主app里注册这个Activity就可以了。 


2. 加载插件app中的Activity
实际场景中插件apk肯定是由服务端下发后,保存到SD卡的某个文件夹下。这里将编译好的插件apk放到手机外置SD卡的根目录中,我们来演示宿主app如何去加载插件app中的Activity。 


六. 资源加载
接下来我们来看下FluginManager的loadPath方法如何实现。如果要实现这个功能,首先想到的肯定是用反射。 
 
可是你别忘记了,插件app根本就没有安装,这里是无法找到这个Class的。我们需要DexClassLoader来完成Activity类的加载。 
PluginManager的getDexClassLoader的实现如下: 


讲完了如何加载Activity,我们来讲下如何加载Activity中用到的资源文件。我们在日常开发中需要资源文件时,我们是通过getResources()来获取。例如加载一个图片:

getResources().getDrawable()
1
可现在我们需要获取的是另外一个app的资源,所以这里就需要自己实现一个getResources()方法。 
PluginManager的getResources方法实现如下: 


至此,一个插件app的activity加载功能就实现完成了,下面我们来看如何跳转。

七. 跳转到插件app中的Activity

由于我们需要读取SD卡中的插件apk,这里别忘记加上SD卡的读写权限 

这就可以实现跳转了。下面我们来看下效果 
 
奇怪,为什么我们跳转到插件app的activity是空白的?我们来看下插件app的activity应该长什么样子。 
 
当然这里我只在插件app的主activity里放了一张图片,并没有写复杂的布局。可是为什么我们跳过来的是空白页呢?我们再看下ProxyActivity的代码:

package com.ctrip.pluginapplication

import android.content.res.Resources
import android.os.Bundle
import android.support.v7.app.AppCompatActivity

/**
 * 壳!专门用来加载插件Activity
 * @author Zhenhua on 2018/3/3.
 * @email [email protected] ^.^
 */
class ProxyActivity : AppCompatActivity() {

    /**
     * 要跳转的activity的name
     */
    private var className = ""

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        /**
         * step1:得到插件app的activity的className
         */
        className = intent.getStringExtra("className")
        /**
         * step2:通过反射拿到class,
         * 但不能用以下方式,因为插件app没有被安装!
         */
//        classLoader.loadClass(className)
//        Class.forName(className)


    }

    override fun getClassLoader(): ClassLoader {
        //不用系统的ClassLoader,用dexClassLoader加载
        return PluginManager.getInstance().getDexClassLoader() as? ClassLoader
                ?: super.getClassLoader()
    }

    override fun getResources(): Resources {
        //不用系统的resources,自己实现一个resources
        return PluginManager.getInstance().getResources() ?: super.getResources()
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
我们发现,我们这里还没有在ProxyActivity里写逻辑啊,我们只是得到了插件app的主activity的name,这时activity还没有生命周期。?我们接着来实现。我们需要让ProxyActivity控制插件app的activity的生命周期,所以我们需要得到插件app的activity的实例,然后去控制其生命周期:

package com.ctrip.pluginapplication

import android.content.res.Resources
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.ctrip.standard.AppInterface

/**
 * 壳!专门用来加载插件Activity
 * @author Zhenhua on 2018/3/3.
 * @email [email protected] ^.^
 */
class ProxyActivity : AppCompatActivity() {

    /**
     * 要跳转的activity的name
     */
    private var className = ""
    private var appInterface: AppInterface? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        /**
         * step1:得到插件app的activity的className
         */
        className = intent.getStringExtra("className")
        /**
         * step2:通过反射拿到class,
         * 但不能用以下方式
         * classLoader.loadClass(className)
         * Class.forName(className)
         * 因为插件app没有被安装!
         * 这里我们调用我们重写过多classLoader
         */
        var activityClass = classLoader.loadClass(className)
        var constructor = activityClass.getConstructor()
        var instance = constructor.newInstance()

        appInterface = instance as?AppInterface
        appInterface?.attach(this)
        var bundle = Bundle()
        appInterface?.onCreate(bundle)

    }

    override fun onStart() {
        super.onStart()
        appInterface?.onStart()
    }

    override fun onResume() {
        super.onResume()
        appInterface?.onResume()
    }

    override fun onDestroy() {
        super.onDestroy()
        appInterface?.onDestroy()
    }

    override fun getClassLoader(): ClassLoader {
        //不用系统的ClassLoader,用dexClassLoader加载
        return PluginManager.getInstance().getDexClassLoader() as? ClassLoader
                ?: super.getClassLoader()
    }

    override fun getResources(): Resources {
        //不用系统的resources,自己实现一个resources
        return PluginManager.getInstance().getResources() ?: super.getResources()
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
这时我们就可以成功跳转了。ok,插件化实现完成。我们来看下效果。 
 
这里附上demo(点击下载),如有任何疑问可留言提问,博主每天都会查看。

~~~~华丽丽的分割线:在插件app中实现更多功能~~~ 
之前我们的插件app的activity其实就只是加载了一个imageView,我们现在来实现这样一个功能:“点击ImageView,弹出一个toast”。 
代码如下: 

我们来看下效果。。 
然而,点击竟然crash了。我们来贴下错误日志: 

原来我们在插件Activity中不能用自己的上下文,我们应该用that!! 
 
代码已经更新到github上,欢迎下载体验。
--------------------- 
作者:Colin_Mindset 
来源:CSDN 
原文:https://blog.csdn.net/colinandroid/article/details/79431502 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/qq_38335172/article/details/85341376