Android插件化快速入門與實例解析(VirtualApk)

  集成一個第三方相冊功能,只需集成一個插件APK到項目中,無需集成額外代碼,而且支持隨時更新相冊功能,無需發佈版本更新,無需AndroidManifest中聲明四大組件,這就是插件化。android

  插件化可利用性很廣,但事實上大多數開發者,由於未知而放棄使用,因此本篇將深刻淺出帶你瞭解插件化原理,從基礎到實現,插件化再也不是你陌生的領域。git

本篇主要涉及到:github

  • 1、Activity/Service的啓動原理和流程。
  • 2、插件化實現原理。
  • 3、DiDi開源VirtualApk源碼解析(Activity/Service)。
  • VirtualApk優化反射帶來損耗的小技巧。

ps:若是你對此(1、二)已經十分了解,請自行略過。編輯器

1、 Activity/Service啓動流程

  Activity和Service的啓動流程十分複雜,一個startActivity的背後是無數的邏輯實現,這裏不深刻討論,但須要理解這個流程,由於插件化是在流程上動手腳,以達到繞過系統限制的目的。ide

  下方圖片是Activity啓動的簡化流程,能夠看到,從Instrumentation開始,到ActivityManagerServiceActivityThread結束,啓動一個Activity,流程並不簡單。工具

  在InstrumentationexecStartActivity開始啓動,到經過checkStartActivityResult校驗Activity是否在Manifest中聲明,從圖中能夠看出,流程仍是至關繁瑣的。
  
Activity啓動流程詳見圖片性能

Activity簡化啓動流程圖
Activity簡化啓動流程圖

  下方圖片是Service啓動的簡化流程,一樣能夠看到,ActivityManagerServiceActivityThread一樣起到了關鍵性的做用。插件化的關鍵,就在於InstrumentationActivityManagerServiceActivityThread優化

Service啓動流程詳見圖片插件

Service簡化啓動流程圖
Service簡化啓動流程圖

  爲了更好理解插件化,如下圖,是幾個關鍵類的對應關係與實際做用,有點S/C的味道。它們的通訊是經過IBinder,實現進程通訊的,能夠看出,啓動Activity和Service,ActivityThreadActivityManagerService是關鍵,而且上面咱們知道,Instrumentation是Activity的啓動入口,因此實現插件化的流程,即可以在這些關鍵類上開刀。3d

下圖在插件化實現中起到關鍵做用

關鍵類關係圖
關鍵類關係圖

提早說明

  好了,帶了一波基礎姿式的節奏,稍安勿躁,先這裏在補充幾個概念,若是你已經習得,能夠跳過:

  • Hook:攔截某個內部流程,在其中作某些修改,以實現本身的邏輯。

  • Instrumentation:每一個Activity都有一個Instrumentation對象,它是在Activity啓動是被賦予的Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(); 這即是startActivityForResult的啓動,同時返回啓動結果。

  • 佔坑:聲明一個不存在的Activity,如:
    <activity android:name=".A$1" android:launchMode="standard"/>
    ,這樣啓動.A$1這個Activity能夠欺騙系統檢測,而後再將插件Activity注入到.A$1這個坑位中。

2、插件化實現原理。

  插件化的實現就是在於加載、繞過系統限制、啓動和管理插件等過程。按照VirtualApk的實現,大體流程爲:

一、初始化Hook住InstrumentationActivityThread等。經過PackageParser(插件apk包信息)、AssetManager(資源文件Resources)、ClassLoader等加載一個Apk插件。

二、啓動插件Activity:提早在主APP中佔有坑位,經過替換Intent中的targetActivity,打開佔坑聲明的A$Activity,而後繞過AndroidManifest檢測,再攔截newActivity方法中恢復targetActivity。

三、啓動插件Service:經過啓動一個代理Service統一管理,攔截全部Service方法,修改成startService到代理Service,在代理Service的onStartCommond統一管理,建立/中止目標service 。

3、VirtualApk源碼解析

一、初始化

  初始化過程當中,VirtualApk 建立了PluginManager ,而且hook住了Instrumentation和SystemService,以下圖所示。

初始化
初始化

  如下圖所示,VrutalApk經過Instrumentation建立了一個VAInstrumentation對象,VAInstrumentation是一個繼承Instrumentation的類。

  將VAInstrumentation反射插入到ActivityThread中,這樣系統接下來關於Instrumentation的操做,就會回到VAInstrumentation中,被VrtualApk接管。

Hook Instrumentation 圖1
Hook Instrumentation 圖1

  這裏是如何拿到Instrumentation的?

  由於在ActivityThread內部有一個sCurrentActivityThread靜態變量。以下圖,經過反射sCurrentActivityThread咱們能夠獲取當前ActivityThread,而ActivityThread的公開方法getInstrumentation便可拿到Instrumentation對象。

Hook Instrumentation 圖2
Hook Instrumentation 圖2

  另外,上方圖1還有設置HandlerCallback的流程,其實就是攔截了ActivityThread中的mH這個Handler的Callback,從【 1、 Activity/Service啓動流程】流程圖能夠看到,mH的handleMessage處理不少Activity的啓動狀態。

  以下圖, 是Hook Service的流程,如圖中註釋所示,經過ActivityManagerNativegetDefault,拿到AndroidManagerService(詳見啓動流程圖),而VirtualApk經過自定義ActivityManagerProxy,從新生成了一個IActivityManager,而後注入回AndroidManagerService中,這樣接管了系統啓動、管理service等操做。

Hook Service 圖1
Hook Service 圖1

二、加載插件Apk

  加載插件APK是經過PluginManagerloadPlugin方法,以下圖所示,此處就是將apk拆開,解析,讀取,加載,組裝爲LoadedPlugin並保存,以方便後面管理與使用。

加載Apk插件
加載Apk插件

  此處對Apk進行了複雜的解析、加載、合併等操做,大體流程以下:

  • 解析apk的相關包信息、判斷是否加載過apk。
  • 建立一些插件工具類。
  • 經過AssetManager建立Resource對象,平臺用AssetManager建立出Resource,判斷是否和宿主Apk合併資源。
  • ClassLoader 根據插件APK路徑建立loader,判斷是否合併loader中的dex,合併nativeLIbraryDirectories。
  • 將so複製到mNativeLibDir路徑。
  • 保存Instrumentation、Activities、Services、Providers , 註冊Broadcast等。
  • 建立出Apk的Application,並call Application onCreate。

三、啓動插件Activity

  那麼是時候啓動插件Activity了。經過startActivity即可以啓動。從上面的流程咱們知道啓動是從Instrumentation.execStartActivity();開始的,而系統的Instrumentation已經被VAInstrumentation替換,其中VAInstrumentation重寫了幾個關鍵方法:

  • execStartActivity:入口。
  • newActivity:建立。
  • callActivityOnCreate:通知。
  • handleMessage:處理。

  沒錯,以下圖,在啓動Activity的入口處,VirtualApk攔截了請求,而後根據Intent的參數,去匹配plugin中的Activity坑位,以後替換Intent中的Activity,以此來達到欺騙系統的效果。

  可是,由於這個Activity的對象了實際上並不存在,最終咱們須要啓動的是實現了的targetActivity,因此須要攔截Instrumentation的第二個方法newActivity,由於在ActivityTreadperformLaunchActivity中,會調用InstrumentationnewActivity

啓動Activity
啓動Activity

  在newActivity中,以下圖,類沒有找到時(坑位類確定找不到啦),那麼就去獲取本來保存在Intent的目標Activity,而後調用建立VAInstrumentation時保存的Instrumentation(mBase)去建立Activity。

建立Activity
建立Activity

  Activity雖然建立好了,可是它對應的資源和context都還不對,因此咱們須要在Activity的OnCreate以前完成好Resource的注入。前面加載Apk時,這些資源都保存在Plugin中。因此咱們攔截callActivityOnCreate方法,以下圖,將Activity的Context、Application、Reource,都替換成Plugin中對應的對象。一個Activity就這樣繞過AndroidManifest啓動起來了。

屏幕快照 2017-07-15 上午12.36.21.png
屏幕快照 2017-07-15 上午12.36.21.png

四、啓動插件Service

  startService啓動Service時,還記得上面咱們經過ActivityManagerProxy生成IActivityManager嗎?它主要攔截了Service相關啓動和中止等方法,而後將其都轉化爲對應的startService方法,指向代理Service。由於startService方法的特性,他們最終都會在代理Service的onStartCommand中被統一處理。

  以下圖,是ActivityManagerProxy,其中invoke攔截了全部相關的服務請求,並作了轉化處理,下面以startService爲例。

ActivityManagerProxy
ActivityManagerProxy

  startService這裏,主要即是提取本來目標service信息,而後轉化爲代理Service,發送到代理Service,下方圖片啓動流程轉化流程

啓動流程
啓動流程

轉化流程
轉化流程

  以下圖,在代理service中,根據請求類型,代理service會經過classLoader加載來建立service,並操做其attach、onCreate、onStartCommand等,讓service工做起來。

啓動真正的目標service
啓動真正的目標service

自此Activity和Service都成功啓動了,是否是對插件化有了不同的瞭解?

五、AndroidStub

允許這裏插入這一塊,安利下Virtual中的AndroidStub模塊,以下圖

AndroidStub
AndroidStub

  由於都用反射很浪費性能,因此有了AndroidStub,它是用來欺騙編譯器的。正常狀況下你想操做ActivityThread就會出現以下圖狀況,由於它是一個@hide類,這時候除了反射獲得ActivityThread,你還須要再反射須要執着方法才能執行,這在必定程度會損耗一些性能。

  可是以下圖,VirtualApk經過AndroidStub,模擬源碼建立了如ActivityThread類,這裏你就能夠如圖正常使用ActivityThread了,而AndroidStub中的ActivityThread,其實只是定義了和原碼中一摸同樣的方法,並無其餘實現。

  由於CoreLibrary依賴AndroidStub使用的是provided ,由於provided依賴是不打包依賴包,而是運行時提供,因此成功欺騙了編輯器,以此提升了性能。很神奇吧?

ActivityThread
ActivityThread

CoreLibrary依賴AndroidStub
CoreLibrary依賴AndroidStub

  終於結束了,若是你看到了這裏,相信你是一個頗有耐心的同志!固然插件化仍是其餘實現方式,如Replugin,只Hook住了ClassLoader,流程更加複雜,若有什麼建議和疑問,歡迎留言討論。

VirtualApk:github.com/didi/Virtua…

我的github:github.com/CarGuo

終於結束了,點個贊不?
終於結束了,點個贊不?
相關文章
相關標籤/搜索