1. 程式人生 > >Android外掛化框架介紹

Android外掛化框架介紹

本框架是建立在 Dynamic-load-apk進行的上層封裝。增加外掛動態載入到libs目錄和針對模組Service的注入。

本文將以H5Core(Hybrid)為外掛進行講解。

更新日誌:

>
* 2016/7/6 commit -m “增加懶載入功能” hash: 2a335dc49654c80fb6779cacefdf3ed712c23a8

外掛化框架簡介

  • 外掛化是將Apk中功能類似的模組封裝到獨立的Application中,並根據框架約定好的規則完成Apk的動態載入和Service的注入。
  • 本框架是將每一個Apk作為so並使用定製化打包指令碼將so檔案打到主Project/libs/jniLibs,這樣在apk編譯的時候就可以將so檔案直接裝載進data/data/xxxxx/lib目錄,支援後續的DexClassLoader載入該檔案。
  • 每一個模組分為Api和Core,Api作為模組對外提供的介面,Core作為封裝好的獨立模組,每一個模組做好自己的混淆。注入操作需在Core中定義,下文將介紹這塊。
  • 主Client增加bundleList.config檔案,檔案配置:

    bundleName=h5core    //直接載入的外掛
    lazyBundle=h5core.H5Service&H5Api //懶載入外掛
    

一、Framework

  • Framework提供了一個動態載入apk的框架,並提供一個載入獨立模組的BaseMateinfo。

簡介

  1. 開發模組時需要在 module(core)/package name/下定義Metainfo繼承自BaseMateinfo。 這樣該模組在主Apk安裝的時候就會動態將模組的介面注入到框架,後續提供給其它元件呼叫。
  2. 模組提供的主要方法類有:BasePluginActivity,BasePluginFragmentActivity,BasePluginService,BaseMateinfo,VivaApplication.

    BasePluginActivity: 基礎的Activity,每一個模組中的Activity都需要繼承該類,完成模組中的Activity的代理化。
    BasePluginFragmentActivity: 基礎的FragmentActivity,同上。需要繼承該類
    BasePluginService: 基礎的Service,同上。
    BaseMateinfo: 模組Service注入的基類,其它模組的Core層都需要定義一個Metainfo來繼承該類,並完成Service的注入。(後面會介紹如何注入)
    VivaApplication:模組的Application,可以拿到模組的Context,並提供查詢Service,啟動Activity等方法。
    

二、Activity層

  • 為了讓proxy全面接管apk中所有activity的執行,需要為activity定義一個基類BaseActivity,在基類中處理代理相關的事情,同時BaseActivity還對是否使用代理進行了判斷,如果不使用代理,那麼activity的邏輯仍然按照正常的方式執行,也就是說,這個apk既可以按照執行,也可以由宿主程式來執行。

獨立模組架構

  • 模組分類:Api和Core,針對不同業務可追加字首。
  • 每一個模組對外提供一個Service供其他模組引用。Service的Interface類放在Api模組,實現類放在Core。實現獨立模組的封裝。
  • Service註冊:在Core的根包目錄建立MetaInfo類,繼承Framework模組的BaseMetaInfo.如下:

    public class MetaInfo extends BaseMetaInfo {
    private static final String TAG = "MetaInfo.Init";
    public MetaInfo() {
        Log.d(TAG,"Service init");
        ServiceDescription serviceDescription = new ServiceDescription();
        serviceDescription.setInterfaceName(XXService.class.getName());
        serviceDescription.setClassName(XXServiceImpl.class.getName());
        services.add(serviceDescription);
    }
    }
    註解:
    ServiceDescription類是針對Service的描述類,將介面和實現封裝在該物件,並將其新增到services列表中。
    

    以上工作就完成了模組的注入。

模組之間依賴

  • 模組只要是通過Api包的依賴進行訪問。由於Api是作為一個Jar存在的,因此可以直接被其它模組依賴,並切記使用 provided來依賴,防止Api的jar包被編譯進模組。
  • 模組之間訪問:主要的類有VivaApplication、MicroApplicationContext。

    比如其他模組訪問Core:
    XXService xxservice = VivaApplication.getInstance().getMicroApplicationContext().findServiceByInterface(XXService.class.getName());
    這樣就可以拿到容器的Service,從而呼叫其提供的方法。
    

模組內部資源的訪問

  • 由於每一個模組作為獨立的apk打入主apk,因此訪問該apk的上下文不再是該apk的,而是框架層的代理上下文。

    示例:
    1、Resourse獲取
        VivaApplication.getInstance().getMicroApplicationContext().getResourcesByBundle("xxcore");  
    2、Assets獲取
        VivaApplication.getInstance().getMicroApplicationContext().getAssetsByBundle("xxcore");
    

Gradle打包命令詳解

  • gradle build :編譯當前模組。
  • gradle buidleJar:針對本模組生成jar包,儲存目錄在 xxx/build/libs/xxxx.jar
  • gradle uploadArchives:上傳本專案包到Nexus伺服器,提供給其他模組依賴

例子:

1、Api包的build.gradle模版

 
apply plugin: 'com.android.library'
apply plugin: 'maven'
apply plugin: 'signing'

//定義GroupID和Version,ArtefactID會自動使用Project名
group = 'com.xxxx.mobile'
version = '1.0.1'

android {
    compileSdkVersion 22
    buildToolsVersion "22"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

artifacts {
    archives file('build/libs/' + project.name + '.jar')
}

signing {
    required { has("release") && gradle.taskGraph.hasTask("uploadArchives") }
    sign configurations.archives
}

uploadArchives {
    configuration = configurations.archives
    repositories.mavenDeployer {
        beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
        repository(url: 'http://192.168.1.3:8081/nexus/xxxx/') {//倉庫地址
            authentication(userName: "admin",//使用者名稱
                    password: "admin123")//密碼
        }

        pom.project {
            name project.name
            packaging 'jar'
            description 'none'
            url 'http://192.168.1.3:8081/nexus/xxxxx/'//倉庫地址

            developers {
                developer {
                    id 'mc'
                    name 'ma machao'
                    email '[email protected]'
                }
            }
        }
    }
}
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'
    }
}

//dependsOn 可根據實際需要增加或更改 dependsOn: ['compileReleaseJava'],
task buildJar(type: Jar) {

    appendix = project.name
    baseName = project.name
    version = "1.0.0"
    classifier = "release"

    //字尾名
    extension = "jar"
    //最終的 Jar 包名,如果沒設定,預設為 [baseName]-[appendix]-[version]-[classifier].[extension]
    archiveName = project.name + ".jar"

    //需打包的資源所在的路徑集
    def srcClassDir = [project.buildDir.absolutePath + "/intermediates/classes/release"];
    //初始化資源路徑集
    from srcClassDir

    //去除路徑集下部分的資源
    exclude "com/xxxx/" + project.name + "/BuildConfig.class"
    exclude "com/xxxx/" + project.name + "/BuildConfig\$*.class"
    exclude "**/R.class"
    exclude "**/R\$*.class"
    //只匯入資源路徑集下的部分資源
    include "com/xxxx/" + project.name + "/**/*.class"
}

//倉庫地址
allprojects {
    repositories {
        mavenCentral()
        //這裡加入自己的maven地址
        maven {
            url "http://192.168.1.3:8081/nexus/xxxxx"
        }
    }
}

dependencies {
    compile 'com.android.support:support-v4:22.0.0'
    provided 'com.xxxx.mobile:framework:1.0' //依賴其他模組的jar包
}

2、core模組的例子


apply plugin: 'com.android.application'
apply plugin: 'maven'
apply plugin: 'signing'

//定義GroupID和Version,ArtefactID會自動使用Project名
group = 'com.xxxx.mobile'
version = '1.0.1'

android {
    compileSdkVersion 22
    buildToolsVersion '22'

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

artifacts {
    archives file('build/libs/' + project.name + '.jar')
}

signing {
    required { has("release") && gradle.taskGraph.hasTask("uploadArchives") }
    sign configurations.archives
}

uploadArchives {
    configuration = configurations.archives
    repositories.mavenDeployer {
        beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
        repository(url: 'http://192.168.1.3:8081/nexus/xxxx/') {//倉庫地址
            authentication(userName: "admin",//使用者名稱
                    password: "admin123")//密碼
        }

        pom.project {
            name project.name
            packaging 'jar'
            description '容器核心'
            url 'http://192.168.1.3:8081/nexus/xxxxx/'//倉庫地址

            developers {
                developer {
                    id 'mc'
                    name 'ma machao'
                    email '[email protected]'
                }
            }
        }
    }
}
//dependsOn 可根據實際需要增加或更改 dependsOn: ['compileReleaseJava'],
task buildJar(type: Jar) {

    appendix = project.name
    baseName = project.name
    version = "1.0.0"
    classifier = "release"

    //字尾名
    extension = "jar"
    //最終的 Jar 包名,如果沒設定,預設為 [baseName]-[appendix]-[version]-[classifier].[extension]
    archiveName = project.name+".jar"

    //需打包的資源所在的路徑集
    def srcClassDir = [project.buildDir.absolutePath + "/intermediates/classes/release"];
    //初始化資源路徑集
    from srcClassDir

    //去除路徑集下部分的資源
    exclude "com/xxx/mobile/" + project.name + "/BuildConfig.class"
    exclude "com/xxx/mobile/" + project.name + "/BuildConfig\$*.class"
    exclude "**/R.class"
    exclude "**/R\$*.class"
    //只匯入資源路徑集下的部分資源
    include "com/xxx/mobile/" + project.name + "/**/*.class"
}

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'
    }
}

allprojects {
    repositories {
        mavenCentral()
        //這裡加入自己的maven地址
        maven {
            url "http://192.168.1.3:8081/nexus/xxxx/"
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:support-v4:22.0.0'
    provided 'com.xxx.mobile:framework:1.0'
    provided 'com.xxx.mobile:xxapi:1.0.1'
}

三、依賴關係介紹

  • 如今模組化之後,依賴關係的複雜度也相比之前複雜了不少,因此梳理好依賴關係是必須考慮的問題。

模組化主要的依賴關係:

框架主要有Portal、Framework、Module三個模組:
1、Portal是專案的Launcher目錄。
2、Framework是框架的架構模組。
3、Module是每一個模組,並分為Api和Core,並且Api作為Android.library、Core作為Android.application.
4、每一個模組通過依賴其它模組的Api進行元件的呼叫。並且每一個Core都需要依賴Framework。

外掛apk的開發規範

開發外掛apk所需要遵循的規範:

1. 不能用this:因為this指向的是當前物件,即apk中的activity,但是由於activity已經不是常規意義上的activity,所以this是沒有意義的

2. 使用that:既然this不能用,那就用that,that是apk中activity的基類BaseActivity中的一個成員,它在apk安裝執行的時候指向this,而在未安裝的時候指向宿主程式中的代理activity,anyway,that is better than this.

3. 不能直接呼叫activity的成員方法:而必須通過that去呼叫,由於that的動態分配特性,通過that去呼叫activity的成員方法,在apk安裝以後仍然可以正常執行。

  1. 啟動新activity的約束:啟動外部activity不受限制,啟動apk內部的activity有限制,首先由於apk中的activity沒註冊,所以不支援隱式呼叫,其次必須通過BaseActivity中定義的新方法startActivityByProxy和startActivityForResultByProxy,還有就是不支援LaunchMode。
  2. 目前暫不支援Service、BroadcastReceiver等需要註冊才能使用的元件。

四、更新功能

  • 2016/7/6 懶載入功能

    1、bundleList.config 檔案增加lazyBundle欄位來標示是否進行懶載入。欄位值格式:bundleName.bundleService*bundleService。這樣在該外掛被呼叫的時候,框架採取load這個dex。 
    2、優化效果:681kb的so,首次啟動懶載入優化100ms。
    

Thankd for your reading, by Mc… Thanks Dynamic-load-apk

update

Contact me

Any further question?

Email me please!

License

    Copyright 2016 xiyouMc

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

相關推薦

Android外掛框架介紹

本框架是建立在 Dynamic-load-apk進行的上層封裝。增加外掛動態載入到libs目錄和針對模組Service的注入。 本文將以H5Core(Hybrid)為外掛進行講解。 更新日誌: > * 2016/7/6 commit -m “

滴滴開源Android外掛框架VirtualAPK原理分析

概述 Activity 支援 Hook ActivityManagerService Hook Instrumentation 啟動外掛Acti

android外掛框架-Replugin

Replugin是360開源的一款外掛化框架,同樣過多的資料就不在這展現了,github上有最完整的官網介紹。 github地址:https://github.com/DroidPluginTeam/DroidPlugin 宿主接入工作 project的gradle中加入:

自己動手寫Android外掛框架,讓老闆對你刮目相看

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~ 本文由達文西發表於雲+社群專欄 最近在工作中接觸到了Android外掛內的開發,發現自己這種技術還缺乏最基本的瞭解,以至於在一些基本問題上浪費不少時間,如外掛Context和主工程Context的區別,許可權必須在主工程申明等,因此花了點時間瞭解

(4.6.29)Android外掛框架總結

一、概述 所謂外掛化,就是讓我們的應用不必再像原來一樣把所有的內容都放在一個apk中,可以把一些功能和邏輯單獨抽出來放在外掛apk中,然後主apk做到[按需呼叫],這樣的好處是一來可以減少主apk的體積,讓應用更輕便,二來可以做到熱插拔,更加動態化 採集

Android外掛框架實戰

最近在工作中接觸到了Android外掛內的開發,發現自己這種技術還缺乏最基本的瞭解,以至於在一些基本問題上浪費不少時間,如外掛Context和主工程Context的區別,許可權必須在主工程申明等,因此花了點時間瞭解了一下外掛的歷史,並寫了兩個Demo作為總結。本文旨在通過兩個

Android外掛框架--360RePlugin基本使用

360RePlugin是360公司推出的外掛化框架 一、先看效果圖: 二、程式碼實現: 1.主程式開發:專案的Gradle的dependencies中新增: classpath 'com.qihoo360.replugin:replugin-host-gr

Android外掛框架 RePlugin 初探

為什麼要使用Android外掛化框架? 寫軟體時,軟體的擴充套件性至關重要。而軟體的擴充套件性跟其程式碼的解耦程度相關。解耦程度大,就是模組化強。解耦程度小,即模組化弱。 外掛化框架RePlugin,所做的就是將軟體解耦,實現了程式碼的模組化。這對軟體本身的後續開發、功能新

Android外掛框架和熱修復技術的資料收集和彙總

外掛化框架 一個APP功能的堆疊和業務的蓬勃發展,導致APP越來越龐大和臃腫,每一個APP都有一顆超級APP的理想和成為系統第二的願望,如何減少APP的釋出成本和更新成本,外掛化的方式是一條不錯的捷徑。 外掛化的介紹與原理 開源的外掛化框架

2017 Android外掛框架總結

文章目錄引言發展歷史基礎類庫主流框架0引言先簡單介紹一下Android外掛化。很早之前已經有公司在研究這項技術,淘寶做得比較早,但淘寶的這項技術一直是保密的。直到2015年才陸續出現很多框架,Android外掛化分成很多技術流派,實現的方式都不太一樣。1發展歷史首先,要記住2

Android 外掛框架VirtualAPK :(一)基本整合》

一、VirtualAPK 介紹 VirtualAPK 是我們推出的一款Android平臺上的外掛化方案,通過VirtualAPK,可以讓APP無需重新發版即可上線新功能。 之所以做這件事,是因為傳統的開發流程中,APP進行版本迭代的時候,有著嚴格的時間週期的

Android外掛框架使用心得 (使用篇)

最近專案中,有些子功能需要按需載入,於是考慮使用外掛化方案實現,看過幾個外掛化方案後,最終選擇了360之前開源的DroidPlugin方案。 在使用中,還是有一些心得體會,網上的原始碼分析,詳細原理分析的文章一經很多(文末會有連結),本篇文章,主要從使用的角度

Android--›360全面外掛框架RePlugin使用概述

官網的wiki文件, 把RePlugin的接入, 外掛的使用, 元件的呼叫介紹的很清楚. 但是關於宿主和外掛的互動,介紹的比較少. 本文主要介紹關於Binder互動方式的使用. 1.1 在專案根目錄的 build.gradle(注意:不是 app/bui

外掛框架Android P的適配

Android P non-sdk 對於非sdk介面的hide field和方法,Android P進行限制呼叫,可以參考如下文章https://juejin.im/post/5b8959f96fb9a019fe685eb3 為了適配P,整體原則就是能繞過non

Android主流HOOK框架介紹與應用--遊戲破解遊戲外掛的必殺技

概述 使用HOOK方案主要是在分析的時候會經常用到,雖然二次打包重新修改程式碼也可以做到,但是一方面效率低,另一方面如果APP有校驗的邏輯就需要進一步繞過,總體還是比較費時費力。所以,通過動態HO

Android Small外掛框架解讀——Activity註冊和生命週期[阿里工程師分享]

通過對嵌入式企鵝圈原創團隊成員degao之前發表的《Android Small外掛化框架原始碼分析》的學習,對Android使用的外掛化技術有了初步的瞭解,但還是有很多需要認真學習的地方,特別是大部分知識都需要結合虛擬機器和Androidframwork的原理才能慢慢理解。比

關於360外掛框架Replugin豎屏修改為橫屏解決方案

預備工作 去GitHub搜尋replugin工程,下載replugin原始碼,解壓如下,游標選中的部分是我們要修改的地方。 1.1.20 為什麼要修改Gradle 因為Replugin原生的預設生成activity坑位都是豎屏的,而車機是橫屏的,導致外掛activity 實際效果與預期

Android外掛技術簡介

https://blog.csdn.net/io_field/article/details/79084630 可以通過反射 事先定義統一介面的方式,訪問外掛中的類和方法 還可以在AndroidManifest.xml中動態註冊元件Activity、Service、BroadcastReceiver、

Android外掛最佳方案--Phantom 實踐指南

PhantomTest 滿幫集團外掛化框架Phantom使用演示 Phantom外掛化演示(請star支援) 演示demo下載 注意:請將外掛apk拷貝至sdcard下。 Phantom介紹 Phantom 是滿幫集團開源的一套穩定、靈

360外掛框架整合填坑

在整合框架是一定記住新增相應的許可權,官方文件沒有提到這個。在demo 中需要以下幾個許可權 <!-- SDK Only (4Stats)。也是“常用的”幾個許可權 --> <uses-permission android:name="android.pe