大家好,我係蒼王。
以下是我這個系列的相關文章,有興趣可以參考一下,可以給個喜歡或者關注我的文章。
[Android]如何做一個崩潰率少於千分之三噶應用app–章節列表
一.佔坑
什麼是佔坑?為什麼要佔坑?
Android外掛化中,從一個外掛Activity跳轉到不同外掛的Activity的時候,是否可以能正常跳轉成功?
宣告Activity需要配置什麼?
宣告Activity是需要AndroidManifest中宣告,但是外掛是依賴於宿主的,外掛宣告瞭Activity,但是外掛的AndroidManifest資訊,是無法動態配置到宿主裡面的。
那AndroidManifest在什麼時候注入的?其實這個是在App安裝的時候,PackageManagerService就會讀取到AndroidManifest裡面的配置資訊並儲存一份到PackageManagerService.Settings當中,那麼基本無法動態的改變這份配置資訊。如果以後能動態的改變Android中記錄的App配置資訊,那麼我們就不需要佔坑了。
正因為一開始就已經將配置AndroidManifest記錄到PackageManagerService,裡面的記錄的Activity的資訊,將也會儲存到PackageManagerService中。我們使用startActivity的時候,ActivityManagerService將會Activity的合法資訊傳送到Native層作配置驗證,如果無法找到跳轉Activity的配置,那麼將丟擲異常。
外掛是app執行時,動態將外掛資訊插入classLoader的dex列表當中。但是宿主的AndroidManifest配置是無法動態去配置修改的。那麼外掛中的跳轉,如何越過這種困境呢?
工程師聰明的,他們提前在宿主宣告一些空Activity資訊到AndoridManifest當中,然後在使用startActivity後在ActivityManagerService中在跳轉到Native層前將替換成員AndroidManifest的空Activity,欺騙驗證,然後Native層驗證過後,在傳回ActivityManagerService層後替換回需要跳轉的Activity的資訊。這種宣告空Activity資訊到AndroidManifest的行為,我們就叫做佔坑了。
結合上一節,hook點來看,佔坑替換是需要hook掉ActivityManagerService來完成這樣的操作的,但是上一節已經介紹過Replugin唯一hook點在classloader了,那麼這個佔坑替換又是如何完成呢?
二.Replugin 佔坑處理
宿主在引入gradle-host-library的時候,就已經引入了Replugin的佔坑操作了。
Replugin在庫中的AndroidManifest,已經提前的宣告瞭各種各樣的Activity Service Proivder,然後BroadcastReceiver可以動態註冊,所以並不需要佔坑。
我們可以看到${applicationId}它將會直接引用到宿主app build.gradle中的applicationId完成。
我們可以看到這些坑位會被合併到在宿主的full的AndroidManifest.xml裡面。
你拖到最後,會發現除了這些坑位外,還會有很多360的坑位新增了,這是如何做到的呢?
這裡關鍵在於引用了replugin-host-gradle中的配置,我們在ComponentsGenerator.groovy檔案,會使用Gradle命令編譯時生成這些佔坑宣告。之後深入介紹replugin的gradle檔案的時候,會給大家更加深入介紹。
二.Replugin 跳轉流程
我們看一下使用Replugin封裝的的跳轉
很清晰的看到pluginName,相當於Android的包名來填寫。
startActivity的時候,從intent中獲取包名和類名,然後再呼叫Factory.startActivityWithNoInjectCN
然後繼續使用外掛管理的繼續跳轉函式
這裡IPluginManager對引數等說明非常情況,說明是公司的技術追求還是很高的。
進入到底層的PmLocalImpl中
然後更深入新增引數
在PmInternalImpl終於可以看真正實現,這裡先要判斷是否外掛已經下載,getPluginConfigInfo會獲取是否存在手機中是否存在外掛。
然後isNeedToDownLoad來啟動下載。
這裡就是一些基礎的下載封裝了。
下載時需要上鎖處理,表示當前外掛正在生效。ProcessLocker是自定義的程式鎖。
PluginProcressMain.getPluginHost().pluginDownloaded將會下載並載入外掛,我們留到下一節再介紹,這個過程。
tryLock和unlock的呼叫就是對程式的鎖定了。
ProcessLock裡面,是使用檔案鎖來完成上鎖的,這裡的程式鎖,正確的來說是檔案鎖。
這裡面建立出檔案字尾為.lock的檔案,作為檔案鎖,然後建立出FileOutputStream為輸出通道,
Java NIO中的FileChannel是一個連線到檔案的通道。可以通過檔案通道讀寫檔案。
FileChannel無法設定為非阻塞模式,它總是執行在阻塞模式下。
這裡的FileChannel是FileOutputStream中獲取的通道的。
這裡面需要釋放的時候,需要釋放Filelock,FileChannel, FileOutStream, File,四個物件形成的鎖。
當正常安裝以後,了通過獲取到PluginInInfo來判斷外掛是否成功安裝
然後再次下載中會通過onPluginNotExitsForActivtiy,來回撥提示。
如果activity是動態註冊的類,直接使用startActivity開啟
這裡面需要判斷外掛中有註冊到註冊的Activity類
這裡是通過HashMap來儲存類的列表
這裡其他外掛首先會在Entry的入口裡面在init的時候呼叫註冊的方法註冊,建立出一個ProxyRePluginVar的遠端外掛資訊。
其會建立出兩個startActivity的MethodInvoker反射的類,來用於使用跳轉方法。其會分發到不同的外掛的RePlugin的物件
Broadcast,Provider,Service,四大元件都是通過這種反射呼叫的方式,來提供其他外掛呼叫的。
回到PmInternalImpl,外掛損壞或者其他原因狀態異常,判讀會返回跳轉目標不存在
如果是大外掛,會使用onLoadLargePluginForActivity的方法啟動。
這裡真正的啟動佔坑的方式來做跳轉
我們看到loadPluginActivity當中,通過ActivityInfo 來儲存一個Activity的資訊,然後
這裡判斷程式和遠端分配坑位。
如果有分配,立刻進入監控狀態,並強制使用UI程式執行。
使用bindActivity來繫結Activity.
bindActivity當中,繼續呼叫到PluginContainter的alloc分配
最終會呼叫到allocLocked分配,裡面有四種規則
(1)嘗試找找到一個動態註冊過的。
(2)找一個新分配的
(3)重用,最老的一個
(4)擠掉最老的一個
然後通過坑位跳轉
我們在plugin-lib中的外掛需要依賴的庫中找到
其PluginActivity是替換的Activity
但是實際上demo中並沒有使用繼承PluginActivity的例子。都是使用佔坑邏輯來替換,並不一定要使用PluginActivity。使用PluginActivity是巢狀生命週期的方法給Repluin管理。
Replugin佔坑跳轉的判斷是我研究外掛化以來最複雜的,程式碼量也很大。
我建立了一個關於Android架構學習的群,裡面可以進一步進行元件化學習的交流。
群號是316556016,也可以掃碼進群。我在這裡期待你們的加入!!!