❶ 插件化原理
總的來說,組件化框架功能單一,專心於模塊化開發,但沒有黑科技,不存在android版本的兼容問題。而插件化框架功能強大,最關鍵的是具備熱修復、模塊動態載入、刪除的能力,但因為需要hook系統組件,所以存在可能的兼容性問題。
Atlas的熱修復使用的是自家的Andfix,基於Native hook。
插件化是體現在功能拆分方面的,它將某個功能獨立提取出來,獨立開發,獨立測試,再插入到主應用中動態載入。以此來規避主應用規模超限。通過代理或Hook來實現。
要正常打開插件中的Activity,需要以下資源:
1,通過DexClassLoader載入插件apk
2,通過包管理器,獲取當前已載入的類信息
3,通過AssetManager獲取插件apk中的資源
4,通過殼app中的代理Activity,提供上下文Context和生命周期管理(插件中的四大組件因為並沒有注冊到殼app的AndroidManifest.xml,所以並不具備生命周期)
通過代理Activity啟動和同步插件Activity的生命周期
Hook其中的第一步或第十步實現插件Activity啟動。
通過hook的方式啟動插件Activity需要解決如下問題:
a、插件Activity如何繞開Manifest中注冊的檢測
b、如何創建Activity實例,並同步生命周期
我們通過VirtualApk插件化框架來看其實現方案:
a、預先在Manifest中注冊各種啟動模式的Activity占坑,啟動時hook第1步,將Intent根據啟動模式替換成預先在Manifest占坑的Activity,這樣就解決了Manifest中注冊的檢測
b、hook第10步,使用插件的ClassLoader反射創建插件Activity,之後Activity的生命周期回調都通知給插件Activity,這樣就解決了創建Activity並同步生命周期的問題
1,關於dex的生成
我們可以用dx工具,將jar包轉成dex文件
2,dex的載入過程
通過DexClassLoader載入dex文件,然後解析其中的class、method等
參考:
https://www.jianshu.com/p/7e4958d02094
❷ android 插件化怎麼把幾個模塊一起打包
1、java 裡面直接把 .class 文件打包到 .jar 文件裡面就可以了,但是 Android 的 Dalvik VM 是不認 Java 的 byte code 的,所以不能直接這么打包,而要用 dx 工具轉成 Dalvik byte code 才可以。當然,dx 工具轉了之後,jar 包裡面就不是 .class 文件了,而是 .dex 文件。 2、可以做成server 利用broadcast,pendingIntent,Intent去通信,再provider數據共享過濾器設置下就能實現這樣的效果 3、國內的各大應用市場的安卓客戶端就是這么做的,由市場客戶端可以下載各個功能客戶端,在市場里可以對這些功能客戶端進行更新、刪除、打開操作。其實如果需求是定製化的應用市場,比如「辦公應用市場」,在功能性的規則介面定義好之後,可以增加更多的業務邏輯,比如說「從市場客戶端開啟功能客戶端的具體某個頁面」,或者「從市場客戶端調用功能客戶端的某個功能」。
❸ Android 插件化
原理:實現原理上都選擇盡量少的hook,通過在manifest上預埋一些組件實現四大組件的插件化。其中Small更形成了一個跨平台、組件化的框架。
VirtulApp:
能夠完全模擬app的運行環境,能夠實現免安裝應用和雙開技術。
Atlas:
阿里出品,號稱是一個容器化框架,結合了組件化和熱更新技術。
Android中有兩種類載入器,DexClassLoader和PathClassLoader,它們都繼承於BaseDexClassLoader。
兩者的區別:DexClassLoader多了一個optimizedDirectory的路徑參數,這個目錄必須是內部存儲路徑,用於緩存系統創建的Dex文件。
所以我們可以使用DexClassLoader去載入外部Apk中的類。
ClassLoader調用loadClass方法載入類採用了雙親委託機制來避免重復載入類。
首先,ClassLoader會查看自身已經載入的類中是否已經存在此類,如不存在,然後,則會使用父類來載入此類,如不能成功載入,則會使用自身重載於BaseDexClassLoader的findClass()方法來載入此類。
DexClass的DexPathList在DexClass的構造器中生成,findClass()方法則是從DexPathList下面找出對應的DexFile,循環DexElements,通過dexElement.dexFile取出對應的DexFile,再通過DexFile.loadClassBinaryName()載入對應的類。
作用:使用插件DexClassLoader載入出需要的類。
通過每一個插件的DexClassLoader載入出自身所需要的類,當每一個插件需要載入相同的類庫時,可採用該類庫的不同版本來使用。
通過把每一個插件的pathList(DexFile)合並到主app的DexClassLoader上,來使各個插件和主app直接能夠相互調用類和方法,並且各個插件中相同的功能可以抽取出來作為一個Common插件供其它插件使用。
插件調用主工程
在ClassLoader構造時指定主工程的DexClassLoader為父載入器即可直接調用主工程中的類和方法。
主工程調用插件
如果是多DexClassLoader的情況,則需要通過插件的DexClassLoader載入對應的類並反射調用其方法。此種情況,主工程一般會在一個統一的地方對訪問插件中的類和方法做一些訪問許可權的管理及配置。
如果是單DexClassLoader的情況,則可以直接調用插件中的類和方法。但是當多個插件引用的庫的版本不同時,會出現錯誤,因此,建議採用Gradle版本依賴管理統一處理主工程及各個插件的庫依賴。
Android通過Resource來載入資源,只要有插件apk,就可以使用assertManager.addAssertPath(apkPath)的方式來生成assertManager,再使用其new出對應的Resource對象即可。
注意:由於AssertManager並不是Public,所以需要通過反射的方式去調用它。並且由於一些Rom對Resource的處理,所以,需要兼容處理。
有2種處理方式:
產生的原因:由於主工程和各個插件引用的Resource id重復產生的沖突。
解決思路:Android中的資源在系統中是以8位16進制0XPPTTRRRR的方式存在,其中PP即是資源區分的區域(Android系統只用它來區分系統資源和應用資源),只要讓每一個插件的PP段取不同的值即可解決資源id沖突的問題。
具體解決方式:
1.修改aapt源碼,編譯期修改PP段。
2.修改Resource的arsc文件,其中的每一條都包含了資源id和映射路徑。
Activity的處理最為復雜,有兩種處理方式:
1.ProxyActivity的方式。
2.預埋StubActivity,hook系統啟動Activity的過程。
原理:VirtualAPK通過替換了系統的Instrumentation,hook了Activity的啟動和創建,省去了手動管理插件Activity生命周期的繁瑣,讓插件Activity像正常的Activity一樣被系統管理,並且插件Activity在開發時和常規一樣,即能獨立運行又能作為插件被主工程調用。
Android插件化方向主要有2個方向:
Android 插件化
❹ android插件化(四)Hook載入插件APK(ClassLoader方式)
前面插件化一和二說了下插樁式載入未安裝的APK,主要是重寫了getResource和getClassloader兩個方法來實現的。以及每個組件要實現一個介面,通過介面注入上下文來達到它的生命周期。
那麼插樁式和hook式的實現方式有什麼不同呢?
插樁式是怎麼載入到插件中的class文件呢,是通過將將APK轉化成插件的Classloader,然後想要載入插件的class文件,我們的去拿這個插件的classloader去loadClass。所以是有一個中間者的。
hook式呢是將插件apk融入到了我們的宿主apk,那直接在裡面就可以直接loadClass了,在不用這個插件的ClassLoader了,這樣的話對於插件和宿主就沒什麼區別了,不像插樁式有一個中間者。
那麼要實現hook式 就要知道android中一個class文件式怎樣被載入到內存中去的。其實就是通過PathClassLoader來載入的。
那麼我們先看下ClassLoader
任何一個java程序都是由一個或者多個class組成的,在程序運行時,需要將class文件載入到JVM中才可以使用,負責載入這些class文件的就是java的類載入機制。CLassLoader的作用就是載入class文件提供給程序運行時使用,每個Class對象內部都有一個ClassLoader來標示自己是有那個classLoade載入的。
Android app的所有的java文件都是通過PathClassLoader來載入的,那麼它的父類是BaseDexClassLoader,還有一個兄弟類是DexClassLoader,那麼他們有什麼區別呢。
從上面可以看出這兩個類的構造函數不同。(在26的源碼中DexClassLoader中的optimizedDirectory也廢棄了)
PathClassLoader:用於Android應用程序類載入器。可以載入指定的dex,以及jar、zip、apk中的classes.dex
DexClassLoader:載入指定的dex以及jar、zip、apk中的classes.dex。
可以看到創建ClassLoader的時候需要接收一個CLassLoader parent的參數,這個parent的目的就在於實現類載入的委託。
某個類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,一次遞歸,如果父載入器可以完成載入任務,那麼就返回,只有當父載入器無法完成載入任務時,才自己去載入。
因此我們自己創建的ClassLoader:newPathClassLoader("/sdcard/xx.dex",getClassLoader()),並不僅僅只能載入我們的xx.dex中的class。
需要注意的是,findBootstrapClassOrNull 這個方法,當parent為null的時候,去這個BootCLassLoader進行載入,
但是在Android當中的實現:
所以new PathClassLoader("/sdcard/xx.dex",null),是不能載入Activity.class的。
上面分析了載入了一個class,是利用了雙親委託機制,那麼要是都找不到那就開始調用自己的findCLass方法
在ClassLoader類中findClass:
任何ClassLoader的子類,都可以重寫loadClass和findClass。如果你不想使用雙親委託,就重寫loadClas修改實現,重寫findClass則表示在雙親委託機制下,父ClassLoader都找不到class的情況下,定義自己去查找一個class。
而我們的PathClassLoader會自己負責載入Activity這樣的類,利用雙親委託父類去載入activity,而我們的PathClassLoader沒有重寫findClass,是在它的父類裡面。因此我們可以看看父類的findClass是如何實現的。
可以看到載入PathClassLoader載入class,轉化為從DexPathList中載入class了,那麼我們看看DexPathList中的findClass
那麼從上面分析得到
到這里我們想要載入一個插件的apk ,其實最終載入的是一個dex文件(先說class文件,載入資源後面說),有沒有辦法吧這個dex文件給轉化成一個 Element 對象,給放到 Elemeng數組 當中,這樣直接就可以載入我們插件中的類了。
1、首先我們肯定是要得到插件APK的的中DexPathList對象中的dexElement數組
2、插件的dexElements數組我們拿到了,那麼是不是要開始拿我們系統裡面的 ,我們反射獲取,和上面的一樣。
3、上面我們獲取到了系統和我們插件的dexElement數組,然後我們將這個數組合並到一個新的數組裡面去,並且給注入到系統裡面
至此,載入插件的一個流程基本就完成了。但是上面只是處理了class文件,沒有處理資源。資源的話我們也是採用hook的方式去實現
在宿主的Application中hook這個方法,然後去重寫getAsserts和getResources兩個方法:
然後在插件的BaseActivity中繼續重寫getAssets和getResources兩個方法
這樣就可以完成hook式載入一個未安裝的APK了。至此基本就完成了插樁式和Hook式插件化的基本實現。(後面幾篇是優化)。