1. 程式人生 > >[Android]用架構師角度看外掛化(3)-Replugin 需要佔坑跳轉?

[Android]用架構師角度看外掛化(3)-Replugin 需要佔坑跳轉?

一.佔坑

什麼是佔坑?為什麼要佔坑?

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佔坑跳轉的判斷是我研究外掛化以來最複雜的,程式碼量也很大。