详述Android插件化原理

        本文基于singwhatiwanna的开源DL插件框架讲述,修改并重构了一些内容,任总的blog原理讲得比较浅,这里我基于自己的理解再详细讲一点东西,也算是一个记录吧~

预备知识:

        一. Java ClassLoader

        作用:

    加载Class文件到JVM,以供程序使用的。我们知道,java程序可以动态加载类定义,而这个动态加载的机制就是通过ClassLoader来实现的。既然ClassLoader是用来加载类到JVM中的,那么ClassLoader又是如何被加载呢?难道它不是java的类?没有错,在这里确实有一个ClassLoader不是用java语言所编写的,而是JVM实现的一部分,这个ClassLoader就是bootstrapclassloader(启动类加载器),这个ClassLoaderJVM运行的时候加载java核心的API以满足java程序最基本的需求,其中就包括用户定义的ClassLoader,这里所谓的用户定义是指通过java程序实现的ClassLoader,一个是ExtClassLoader,这个ClassLoader是用来加载java的扩展API的,也就是/lib/ext中的类,一个是AppClassLoader,这个ClassLoader是用来加载用户机器上CLASSPATH设置目录中的Class的,通常在没有指定ClassLoader的情况下,程序员自定义的类就由该ClassLoader进行加载。当运行一个程序的时候,JVM启动,运行bootstrapclassloader,该ClassLoader加载java核心APIExtClassLoaderAppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。文字太多,下面列个list供大家参考:

        核心classLoader:         

            1.bootstrapclassLoader(启动类加载器),加载java核心API,包括用户自定义的classLoader以及另外两个核心classLoader;

    2.ExtClassLoader,加载java扩展API,也就是/lib/ext中的类;
    3.AppClassLoader,加载程序员自定义类,也就是我们工程classPath下设置的class。

        Java程序加载过程:

            1.当运行一个程序的时候,JVM启动

    2.bootstrapclassloader,该ClassLoader加载java核心APIExtClassLoaderAppClassLoader也在此时被加载

    3.ExtClassLoader加载扩展API

    4.最后AppClassLoader加载CLASSPATH目录下定义的Class

  二.Android ClassLoader

        Android ClassLoader种类: 
        DexClassLoader :可以加载文件系统上的 jar dex apk
›         PathClassLoader :可以加载 /data/app 目录下的 apk ,这也意味着,它只能加载已经安装的 apk
›         URLClassLoader :可以加载 java 中的 jar ,但是由于 dalvik 不能直接识别 jar ,所以此方法在 android 中无法使用, 尽管还有这个类
            

        Android开发和普通的java开发不同的地方是把class文件再重新打包成dex类型的文件,这种重新打包会对Class文件内部的各种函数表、变量表等进行优化dex文件是一种经过android打包工具优化后的Class文件,因此加载这样特殊的Class文件就需要特殊的类装载器,所以android中提供了DexClassLoader类。加载流程如下:

           1. 通过PacageMangager获得指定的apk的安装的目录,dex的解压缩目录,c/c++库的目录

           2.创建一个 DexClassLoader实例

           3.加载指定的类返回一个Class

           4. 然后使用反射调用这个Class

        三. Android Resources

          详述Android插件化原理_第1张图片

   应用程序的每一个Activity组件都关联有一个ContextImpl对象,这个ContextImpl对象就是用来描述Activity组件的运行上下文环境的。Activity组件是从Context类继承下来的,而ContextImpl同样是从Context类继承下来的。我们在Activity组件调用的大部分成员函数都是转发给与它所关联的一个ContextImpl对象的对应的成员函数来处理的,其中就包括用来访问应用程序资源的两个成员函数getResources和getAssets。

   ContextImpl类的成员函数getResources返回的是一个Resources对象,有了这个Resources对象之后,我们就可以通过资源ID来访问那些被编译过的应用程序资源了。ContextImpl类的成员函数getAssets返回的是一个AssetManager对象,有了这个AssetManager对象之后,我们就可以通过文件名来访问那些被编译过或者没有被编译过的应用程序资源文件了。事实上,Resources类也是通过AssetManager类来访问那些被编译过的应用程序资源文件的,不过在访问之前,它会先根据资源ID查找得到对应的资源文件名。

Resources类有一个成员函数getAssets,通过它就可以获得保存在Resources类的成员变量mAssets中的AssetManager,例如,ContextImpl类的成员函数getAssets就是通过调用其成员变量mResources所指向的一个Resources对象的成员函数getAssets来获得一个可以用来访问应用程序的非编译资源文件的AssetManager。

   我们知道,Android应用程序除了要访问自己的资源之外,还需要访问系统的资源。系统的资源打包在/system/framework/framework-res.apk文件中,它在应用程序进程中是通过一个单独的Resources对象和一个单独的AssetManager对象来管理的。这个单独的Resources对象就保存在Resources类的静态成员变量mSystem中,我们可以通过Resources类的静态成员函数getSystem就可以获得这个Resources对象,而这个单独的AssetManager对象就保存在AssetManager类的静态成员变量sSystem中,我们可以通过AssetManager类的静态成员函数getSystem同样可以获得这个AssetManager对象。

   AssetManager类除了在Java层有一个实现之外,在C++层也有一个对应的实现,而Java层的AssetManager类的功能就是通过C++层的AssetManager类来实现的。Java层的每一个AssetManager对象都有一个类型为int的成员变量mObject,它保存的便是在C++层对应的AssetManager对象的地址,因此,通过这个成员变量就可以将Java层的AssetManager对象与C++层的AssetManager对象关联起来。

   C++层的AssetManager类有三个重要的成员变量mAssetPaths、mResources和mConfig。其中,mAssetPaths保存的是资源存放目录,mResources指向的是一个资源索引表,而mConfig保存的是设备的本地配置信息,例如屏幕密度和大小、国家地区和语言等等配置信息。有了这三个成员变量之后,C++层的AssetManager类就可以访问应用程序的资源了。

  

    以上知识是理解安卓插件化非常重要的原理知识,因为插件化的本质就是通过安卓的DexClassLoader去加载apk里的类,再通过AssetManager去加载资源文件,这两玩意儿都进入内存之后,我们的宿主就可以通过接口的方式来调用我们的插件类和资源文件了。


插件加载技术详细介绍

         

    1.  插件类的加载    

              宿主程序会到文件系统比如 SD 卡去加载 apk或者jar(经过测试,必须是jvm可以解压的后缀格式) ,然后通过一个 proxyA ctivity作为壳子,去加载 apk 中的 activity 。大致细节如下:
›            插件 Activity 本身无法启动(生命周期,资源等问题),是通过宿主提供的 ProxyActivity 来加载的;
›            当我们发 Intent 去启动插件当 Activity 时实质启动的时 ProxyActivity;
›            为了封装细节所以封装了 DXIntent;
›            所有插件实现了 IDXPlugin 接口;
           › PrxoyActivity 接管了所有插件 Activity。

    2. 获取AssetsManager

             
›             加载 的方法是通过反射,通过调用 AssetManager 中的 addAssetPath 方法,我们可以将一个 apk 中的资源加载到 Resources 中,由于 addAssetPath 是隐藏 api 我们无法直接调用,所以只能通过反射,下面是它的声明,通过注释我们可以看出,传递的路径可以是 zip 文件也可以是一个资源目录,而 apk 就是一个 zip ,所以直接将 apk 的路径传给它,资源就加载到 AssetManager 中了,然后再通过 AssetManager 来创建一个新的 Resources 对象,这个对象就是我们可以使用的 apk 中的资源 了。

      3. 插件加载入内存的流程

       详述Android插件化原理_第2张图片
        

插件类分析

        DXPluginBean

             封装了每个 Plugin 也就是 apk 的数据
       ›     维护在 DXPluginManger 类的 Map

        DXPluginManager

›            插件管理核心类
           › 加载插件
›            启动插件
›            插件维护
 

        IDXPlugin

›             把每个插件的 Activity 抽象成一个“插件”
›             IDXPlugin 实现了 Activity 的主要方法
            › onAttach 方法是插件专用的回调方法,当插件 Activity Proxy 加载当时候 proxy 的引用赋值给 that

        DXIntent      

             › pluginPackgeName 跳转的Plugin的包名,也就是Manifest里的packageName

             pluginClassName 跳转的Plugin中指定的ActivityName,可以传null,则默认时跳转main Activity

        DXPluginBaseActivity / DXPluginBaseFragmentActivity

›             所有插件 Activity 继承这两个 Activity
›             Activity 实现 IDXPlugin 接口
›             onAttach 方法中获得 proxyActivity 的引用
›             所有 activity 继承方法中需要对插件本身启动还是在宿主中被启动进行判断

       DXProxyActivity / DXProxyFragmentActivity           

            › 宿主 Activity
›            host 中调用插件 Activity 的跳转,本质就是这两个 Activity 之间的跳转
›            为插件提供真正的 Context
›           为减少重复代码将插件的初始化放在 DXPluginInitializer 类中     

       DXPluginInitializer

›            修复 theme 带来的崩溃问题
›            启动插件 Activity流程:

             1 通过反射获得插件Activity的默认构造函数

             2 通过反射new出一个插件并强转成IDXPlugin

             3 回调onAttach方法传入Prxoy的引用

             4 调用onCreate方法调起插件

       DXXMLManager

             通过配置文件可实现直接通过插件名称调用插件

        

        Service的部分和Activity差不多的原理,不再赘述~


         最后来一张UML看下类关系:

         详述Android插件化原理_第3张图片


你可能感兴趣的:(android,插件,详解)