search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

全面插件化:RePlugin的使命

本文為作者在 GMTC 2017 大會上演講的總結。作者還將繼續產出剖析插件化技術細節的系列文章,也將在移動開發前線公眾號發布,敬請期待。

寫在前面

「RePlugin 即將開源,這將是我們獻給安卓世界最好的禮物。」當我們宣布這一消息時,心中的激動,無以言表。是的,三年的「厚積」,如今的「薄發」,看似平凡的話,實際上卻飽含了我們太多的激動、辛酸與淚。

那麼今天,我們就來詳細的和您聊一聊,這個從 2014 年中旬,正式在手機衛士上啟用,並即將開源的 360 RePlugin,究竟能為我們,更為您能帶來什麼。

RePlugin 是什麼

RePlugin 是一套完整的、穩定的、適合全面使用的,占坑類插件化方案。其主要優勢有:

  • 極其靈活:主程序無需升級(無需在 Manifest 中預埋組件),即可支持新增的四大組件,甚至全新的插件

  • 非常穩定:Hook 點僅有一處(Classloader)。其崩潰率做到僅為「萬分之一」,並完美兼容市面上近乎所有的 Android ROM

  • 特性豐富:支持近乎所有在「單品」開發時的特性,包括靜態 Receiver、Task-Affinity、自定義 Theme、進程坑位、AppCompat 等

  • 進程任意:可讓各組件跑在 UI、常駐,甚至是「任意坑位進程」

  • 易於集成:無論插件還是主程序,只需「數行」就能完成接入

  • 自由隔離:想隔離就隔離(如不穩定或佔資源的插件,易於釋放),不想隔離的模塊就混用(如各種基礎、UI 插件,都跑在 UI 進程內,性能優異)

  • 管理成熟:擁有成熟穩定的「插件管理方案」,支持插件安裝、升級、卸載、版本管理,甚至包括進程通訊、協議版本、安全校驗等

  • 數億支撐:有 360 手機衛士龐大的數億用戶做支撐,三年多的殘酷驗證,確保 App 用到的方案是最穩定、最適合使用的

截止 2017 年 6 月底,RePlugin 的:

  • 插件數已達 102 個(其中核心基礎插件 57 個)

  • 插件占應用比(指把代碼資源鋪開,插件占整個應用的比例)高達 83%

  • 所支撐的單個應用年發版次數高達 596 次(平均每個工作日發版 2-3 次)

此外,目前 360 公司幾乎所有的億級用戶量的 APP,以及多款主流第三方 APP,都採用了 RePlugin 方案。

總而言之,您們現在看到的 RePlugin,可以說是「久經沙場」、「歷經風雨」的方案,是在數億設備上,歷經三年多時間驗證的成熟方案,也是可以直接拿來為您們所用的,穩定與靈活兼得的方案。

(RePlugin 的核心優勢)

(RePlugin 與現有插件化框架的對比)

為什麼要做 RePlugin插件化的好處

在講述我們團隊為何要在 2013 年底設計一套屬於自己的插件化之前,我們先來簡單談談,有了插件化方案后,能為我們帶來多大的便利,它究竟解決了什麼問題。

對於用戶而言

一切按需:利用插件化方案,可以讓您的應用變得「小而精」。只有當用戶需要使用某個特定功能時,才可以下載並開啟,且可以隨時卸載插件。這不僅可以減小 APK 大小、節省流量,還可明顯的減少內存、內部存儲佔用,將更多空間讓給珍貴的相片、文檔等資料。

隨時體驗新版:不用去應用市場等到大包升級,用戶可以隨時體驗到新版的應用。現在紅極一時的插件化、動態化(RN 類)、熱更新技術,都或多或少的在圍繞此點而展開,可見其對用戶帶來的巨大價值。

對於開發者而言

發版靈活:不用等市場上線,等用戶主動升級,結果錯過寶貴的時機。插件化方案可讓您做到「隨時發版」,不受「發版窗口期」的限制。甚至可針對不同地域、不同用戶群、不同時段來更新,且可以快速驗證自己的構想。

組織結構靈活:一旦發版變得足夠靈活,則組織結構上就可以由原來的「統一作戰」變成「百團作戰」,每個團隊都在開發「自己的插件單品」,制定自己的發版計劃模塊思維:可以讓團隊形成「模塊意識」。當然,插件間、插件與宿主間允許有適度的耦合,但不會是「毫無控制」的那種。這讓開發者們意識到,我們之間是「插件間的協定」,而非「同一屋檐下,隨便胡來」,迫使團隊以全新又合適的方式來開發應用。

Android 原生優勢:和動態化(RN 類)不同,您可以使用最熟悉的 Java/Kotlin 語言,及各種原生 API 來開發您的插件。這使得應用能和系統更「契合」,充分利用原生的各種優勢,且在性能上幾乎感受不到影響。

如上所述,無論是對用戶,還是對開發者而言,使用插件化框架都是大有益處的,理應做到「飛入尋常應用家」。

然而,在實際調查過程中,我們卻發現了一個和這些好處完全不匹配的奇怪現象。究竟是什麼呢?

既然插件化這麼好,為什麼……

雖然我事先已做好功課,然而在 GMTC 上的調查結果卻讓人大跌眼鏡——在聽講的 200 多位安卓開發者中,僅有不足 5% 的比例,使用了插件化方案。超過九成的開發者,目前上沒有將插件化應用在軟體開發之中。

實際上,這和我們在線下觀察到的結果基本吻合。結合之前的調查,我們發現,有三大挑戰制約了插件化在 Android 開發界的普及:

  • 不夠穩定:目前有很多比較靈活的插件化框架,雖然支持特性眾多,但因 Hook 點較多,所以不是非常穩定。因此很多大型項目不是很願意用它們來開發插件,擔心出現應用崩潰、插件無法正常使用等問題。

  • 不夠靈活:有一些相對穩定的插件化框架,又存在「不夠靈活、自由」的問題,一旦插件有較大改動,如新增 Activity、Service、進程等,就需要主程序發版,更不用說能做到「一年前的主程序,無需升級,可以用新插件和組件」。也因此,很多項目的「接入動機」也就大打折扣了。

  • 功能豐富項目專用:目前市面上的插件化方案,大多僅在功能豐富的大型項目中,才被考慮使用,且多用於邊緣功能,比如「紅包」、「天氣」、「搖一搖」等,他們認為只有「非核心」模塊,才會考慮做成插件。這也使得插件化的應用範圍非常狹窄。

然而,通過我們多年的實踐證明,以上三大挑戰,其實是可以被攻克的。這也是我們今天要為您介紹的,360 手機衛士首款 Android 開源項目——RePlugin。

既然這麼大膽,那麼,我們究竟是怎麼做到的呢?

我們是怎麼做的「不夠穩定」怎麼破?

前文提到,不夠穩定的主要原因是 Hook 了太多。那麼市面上比較靈活的插件化框架,究竟 Hook 了哪些呢?

注意:這裡所說的「Hook」是指通過 Java 反射手段,獲取並修改與系統 Server 等交互的 Internal API,來讓框架正常工作的行為,如上面所列部分。正常情況下的反射(例如反射類內部自己的欄位)不屬於 Hook。

看似靈活,然而下列三種情況,將很有可能導致插件甚至應用,徹底不能工作:

  • Android 升級:既然是內部 API,那麼 Android 自然不會認為是「不能修改的」,一旦系統升級時做了改動,輕則功能不正常,重則直接 Crash。例如有的插件化框架曾遇到在 Android 7.0 上出現異常,必須升級主程序才能解決的事故,歷歷在目。

  • ROM 修改:比 Android 升級更可怕的,是第三方 ROM 對內部 API 的「各種改」。這個適配難度是可想而知的。例如自定義 Resource、自定義 WiFiService 等造成的「插件化血案」,不一而足。

  • 使用不當:「常在河邊站,哪有不濕鞋」,Hook 的點多了,一旦對某一點的實現原理理解不透而出了錯。結果,前功盡棄不說,還可能出現更嚴重,且更難以察覺的崩潰事故,細思恐極。

基於上述的情況,我們團隊在 2014 年初,研究全新占坑插件化框架(注意,此時 DroidPlugin 類方案還沒有出現)時,就定了個「小目標」:讓 Hook 越少越好。經過一次次的研究討論,最終確定只 Hook 一個點:ClassLoader,且要求「堅持到底」,所有改動都是基於此來展開。

對我們而言,這是里程碑式的決定,即便到現在來看也是如此。

唯一 Hook——ClassLoader

修改 ClassLoader 的點其實不難,如上圖所示逐步反射即可。然而需要注意的是,這個 ClassLoader 一定得繼承自 PathClassLoader,防止 Android 7.x 因使用 addDexPath 而有問題。

除此之外,此 ClassLoader 所在位置也非常穩定。目前來看,從 Android 2.1 至今都沒有發生過位置、名稱上的變化,可以長期使用。

關於這一點,我們之後會有一篇文章來詳述,敬請期待。

「不夠靈活」怎麼破?

前文提到,就目前市面上的插件化框架而言,若做的足夠穩定,則多少會失去一些靈活性。對於我們擁有這麼多模塊的產品而言,這同樣也是不可接受的。

為此,我們在「堅持一個 Hook 點」原則的前提下,通過不斷創新,最終解決了上述問題。

我們的核心思路,是 2015 年以後才開始「老生常談」的一個詞,那就是:坑位。

坑位方案思想

非坑位方案:標準的一一對應關係。例如,插件有個 XXXActivity,那麼主程序則要求必須也有個 XXXActivity(名字未必一樣)來對應。

一旦插件要添加一個新的 Activity,則對應的,主程序也必須得添加,否則就無法使用這個 Activity。

准坑位方案:有的(如 2013 年的我們)會通過 Fragment 來模擬 Activity,從而實現一定程度上的靈活。然而真的遇到大需求,如和其它應用 Activity 的交互等,就局限百出,很難稱得上是完整坑位思想。

坑位方案:可以做到「一對多」的關係。例如插件有個 XXXActivity,則運行時,主程序可以將自己的 N1ST1 對應到這個 XXXActivity 上。一旦該 Activity 退出,則 N1ST1 就「空閑」出來。而當 YYYActivity 進來后,又會重新佔用 N1ST1。這樣就可以做到一個坑位(如 N1ST1)對應多個實體。

此外,這個 YYYActivity 既可以是已有的,也可以是插件新增的。這樣無論插件如何升級,主程序都可以不用動,即可支持新的 Activity。

當然,我們當時 2014 年設計坑位思想時,也絕不僅僅針對 Activity,而是整個四大組件,甚至到了後期,連 Theme、進程、Task-Affinity 等都做到了「坑位化」,只不過實現方式各異而已。

因篇幅所限,這裡僅以常用的 Activity 來簡述。有關更詳細的內容,歡迎繼續關注我們的《RePlugin 深度剖析》系列文章。

Activity 坑位

目前市面上的完整坑位方案,Hook 的地段可以說是「各有千秋」,從 AMS、Instrumentation 到 ContextImpl 都有,並以此讓插件變得更靈活。

而我們的方案和他們有些不同:除了 ClassLoader 是 Hook 的,其餘一律不需要。那麼我們究竟是如何開啟一個插件的 Activity 呢?

簡單來說,我們有五個核心步驟:

  • 記錄:通過 PM.startActivity 方法來「記錄」到要打開的 Activity 的名字

  • 尋找坑:通過一系列流程來找到一個可用的坑位(如 N1ST1)並記錄

  • 開啟坑:通過系統的 startActivity 來直接打開這個坑位(注意,此處沒有做 Hook)

  • 攔截:當系統調到我們的 HostClassLoader(唯一 Hook 點)時,我們「攔了一道」,找到此坑位(N1ST1)對應的真正的 Activity(XXXActivity)

  • 返回:載入插件並獲取這個真正的 Activity 的 Class 對象並返回給系統

其中,PM.startActivity 可以由插件 / 宿主直接調用。若在插件內部,則可以直接通過 startActivity 方法來打開,更為方便。

當然,這只是核心思路,而每一步我們都會做各種邏輯處理,尤其是「尋找坑」一節,這也是我們的核心之一。

Activity 分層坑位

找坑是有非常多的注意點:

從「分層」上看,則需要支持:各種 LaunchMode、所有透明 / 非透明的 Theme、TaskAffinity 坑位、進程坑位等,甚至 AppCompat 的情況也要考慮。

從「管理」上看,又需要考慮到坑位回收釋放,坑位分配,甚至坑位不足時的處理策略等,不一而足。

篇幅所限,以後會寫詳細介紹,敬請期待。

向完美前進——動態編譯方案

通過剛才的敘述,像 PM.startActivity 等確實可通過一些方法,來讓插件「無成本使用」。但是,像 Provider 的調用(本質是 IContentProvider),Service 的 stopSelf(是 final 的),以及因涉及坑位分配,而不得不需複寫相應方法的 Activity 等。這裡面存在兩個矛盾點:

  • 若不 Hook,則必須要插件開發者「自行處理」,稍顯繁瑣,不夠完美;

  • 一旦 Hook(如嘗試 Hook AMS、IContentProvider 等),又破壞了我們堅持的「1 Hook 原則」,進而擔心未來出現兼容性問題

利弊之間如何取捨,令人頭疼。

針對這個問題,我們的核心理念是:「絕不在 Hook 及穩定性上做任何妥協」,轉而創新性的做一套「動態編譯方案」,力圖從「編譯期」來解決這個難題。

大體而言,就是把一些我們認為需要開發者修改的類和方法,藉助神奇的 JavaAssist 來做自動化修改,這樣可節省開發者的改動成本,達到想要的效果。

一圖以蔽之:

而做到了這一點以後,你會發現,下面的「夢想」就變成了現實:

例如,有個名叫「360 桌面」的應用,它想把自己變成「插件」跑起來。那麼,有了「動態編譯方案」,結合「插件類庫」和框架的支持,最終的效果是——只需改幾行 Gradle,就能直接生成一個 APK。這個 APK:

既可以作為插件直接跑在主程序中

又可以作為單品直接安裝到設備中

是的,就是這麼的神奇!

就這些了?

當然遠不止這些。我在 GMTC 上演示了一段視頻,將龐大又複雜的 360 桌面變成插件,運行在 360 手機衛士中。這讓在場嘉賓倍感驚嘆。

試想,一個桌面(Luncher)插件涉及到的功能是「方方面面」的,小到 TaskDesription 和 SO 的使用,大到四大組件、Task-Affinity 坑位、靜態 Receiver 和進程坑位的處理,都需一一兼顧。所以,要做到這一點,絕不僅僅是前面所說的那幾點就能搞定的。

當然,「讓 360 桌面變成插件」,還不是最有意義的。真正讓 RePlugin 變得更有意義的,就是拿我們的 360 手機衛士來「開刀」,讓數百個——甚至說,近乎一切——的模塊,都成為 RePlugin 的插件,並完美的運行起來。

「功能豐富項目專用」怎麼破?

前文提到,之所以「功能豐富項目專用」,主要和目前市面上的插件化方案的定位有關,以至於開發者認為:「插件 = 免安裝」、「基礎放在主程序里更放心」、「插件開發成本高」等。

然而,仔細分析深層原因后發現,其實最為核心的原因,是插件化和相關框架(包括熱更新方案等)的「定位」不同。我知道的有:

  • 組件化:以 Atlas/ACDD 為代表,官方的定義是「在運行環境中按需地去完成各個 bundle 的安裝,載入類和資源」,以解決大團隊協作時的各種難題,提供了熱修復能力。其依賴「編譯期」較多但很穩定。而大組件的添加,則仍需要主程序發版才能解決,畢竟如官方所述,「組件化 ≠ 插件化」

  • 動態插件化:以 Dynamic-Load-Apk 為代表,其目的是解決發版、升級時的問題。大多採用「非占坑」思路,添加新組件時,還是會要求升級主程序。此類方案較多,且是插件化的「鼻祖」了,值得我們尊敬與學習

  • 免安裝應用:以 DroidPlugin 為代表,官方的定義是「可以在無需安裝、修改的情況下運行 APK 文件」。其場景比較類似於「應用分身」此外,此項目是由我們 360 公司的手機助手團隊研發,在 2015 年中旬發布。它的出現又一次轟動了插件化界,並掀起了「插件化研究」的思潮。

  • 熱修復:以 Tinker/Robust 為代表,目的是以最小的代價來快速打各種 Patch,讓應用能夠持續更新。其核心優勢是「無需重啟進程就能打補丁」。同樣,新添加的大塊功能,則還是需要升級主程序的,畢竟這不是「熱修復」的主要目標。

那麼,我們是這三種目標之一嗎?

顯然,都不是。那麼,我們的目標究竟是什麼?

答案:全面插件化

我們的目標只有一個:全面插件化。

全面插件化:以 RePlugin 為代表。其目的是「儘可能多的讓模塊變成插件」,並在很穩定的前提下,儘可能像開發「單品」那樣靈活,並享受插件化方案帶來的各種好處。

也就是說,無論是 UI、核心業務、合作插件、後台服務,還是基礎功能,都可以變成插件,並在 RePlugin 框架內穩定又靈活的運行起來。甚至,不僅大項目能用,小項目——甚至只是個計算器——都可以使用 RePlugin 來提升自己的靈活性,並最終實現「插件滿天下」的神奇效果。

而這一點,則是我們,和目前市面上大多數插件化框架的主要差異。

目前衛士插件的現狀

目前手機衛士已有的插件,可以分為以下幾類,供各 App 開發者參考:

  • UI 插件:如首頁(是的,你沒看錯)、體檢、信息流等

  • 業務插件:如清理、騷擾攔截、懸浮窗等

  • 合作插件:如程序鎖、免費 WiFi、安全桌面等

  • 後台插件:如 Push、服務管理、Protobuf 等

  • 基礎插件:如安全 WebView、分享、定位等

而這樣的插件,我們有 102 個。可以想見,一旦這些插件不能用,那麼手機衛士瞬間變空殼。三年已過,回頭想想,值得回味。

一起創造更高的價值

說到這兒,讓我想起了 Lody(VirtualApp 作者,高中大牛)在接受 InfoQ 採訪,即將結束時所說的那段話:

「插件化技術的成熟程度雖然在最近幾年呈上升趨勢,但是總體而言仍然處於初、中級階段。App 沙盒技術的出現就是插件化發展的創新和第一階段的產物。在未來,我相信很多插件化技術會被更多的應用,如果插件化穩定到了一定的程度,甚至可以顛覆 App 開發的方式。」

這,其實也是 RePlugin 的終極價值,那就是——讓插件化能「飛入尋常應用家」,做到穩定、靈活、自由,大小項目兼用。

當然,在「全面插件化」甚至「全民插件化」的道路上,我們還有太多的路要走,而如此龐大又複雜的 RePlugin,也絕不可能是一兩個人在戰鬥,而是十多位研發人員共同努力的成果,且得到了部門領導和公司技術委員會的大力支持。我相信,RePlugin 的開源,是一場新的開始。這不僅僅是一個決定,而是一份信念,讓全社區的有志之士能一起參與進來,共同為乃至世界 App 提供一套「全面插件化」的完美方案而努力。

那麼,就讓我們一起,為「全面插件化」的夢想而「窒息」吧!

全面插件化時代,為您來臨!

7 月初正式開源,GitHub 項目地址:



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦