1. 程式人生 > >360手機衛士插件化RePlugin今日開源

360手機衛士插件化RePlugin今日開源

sdn 部分 manifest 隔離 效果 所有 成本高 文章 公司

寫在前面

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

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

GitHub地址:https://github.com/Qihoo360/RePlugin。歡迎您為RePlugin項目加Star、發送Pull Request,提Issue。我們會竭盡所能回答您們的疑惑。

RePlugin是什麽

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

  • 極其靈活:主程序無需升級(無需在Manifest中預埋組件),即可支持新增的四大組件,甚至全新的插件;
  • 非常穩定:Hook點僅有一處(Classloader)。其崩潰率做到僅為“萬分之一”,並完美兼容市面上近乎所有的Android ROM;
  • 特性豐富:支持近乎所有在“單品”開發時的特性,包括靜態Receiver、Task-Affinity、自定義Theme、進程坑位、AppCompat等;
  • 進程任意:可讓各組件跑在UI、常駐,甚至是“任意坑位進程”;
  • 易於集成:無論插件還是主程序,只需“數行”就能完成接入;
  • 自由隔離:想隔離就隔離(如不穩定或占資源的插件,易於釋放),不想隔離的模塊就混用(如各種基礎、UI插件,都跑在UI進程內,性能優異);
  • **管理成熟:擁有成熟穩定的“插件管理方案”,支持插件安裝、升級、卸載、版本管理,甚至包括進程通訊、協議版本、安全校驗等
  • 數億支撐:有360手機衛士龐大用戶量做支撐。
    截止2017年6月底,RePlugin的:
  • 插件數已達102個(其中,核心基礎插件57個)。
  • 插件占應用比(指把代碼資源鋪開,插件占整個應用的比例)高達83%。
  • 年發版次數高達596次(平均每個工作日發版2-3次)。
    此外,目前360公司幾乎所有的億級用戶量的APP,以及多款主流第三方APP,都采用了RePlugin方案。

技術分享
RePlugin的核心優勢

技術分享
RePlugin與現有插件化框架的對比

為什麽要做RePlugin

插件化的好處

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

對於用戶而言

  • 一切按需:利用插件化方案,可以讓您的應用變得“小而精”。只有當用戶需要使用某個特定功能時,才可以下載並開啟,且可以隨時卸載插件。這不僅可以減小APK大小、節省流量,還可明顯的減少內存、內部存儲占用,將更多空間讓給珍貴的相片、文檔等資料;
  • 隨時體驗新版:不用去應用市場等到大包升級,用戶可以隨時體驗到新版的應用。現在紅極一時的插件化、動態化(RN類)、熱更新技術,都或多或少的在圍繞此點而展開,可見其對用戶帶來的巨大價值。

對於開發者而言

  • 發版靈活:不用等市場上線,等用戶主動升級,結果錯過寶貴的時機。插件化方案可讓您做到“隨時發版”,不受“發版窗口期”的限制。甚至可針對不同地域、不同用戶群、不同時段來更新,且可以快速驗證自己的構想;
  • 組織結構靈活:一旦發版變得足夠靈活,則組織結構上就可以由原來的“統一作戰”變成“百團作戰”,每個團隊都在開發“自己的插件單品”,制定自己的發版計劃;
  • 模塊思維:可以讓團隊形成“模塊意識”。當然,插件間、插件與宿主間允許有適度的耦合,但不會是“毫無控制”的那種。這讓開發者們意識到,我們之間是“插件間的協定”,而非“同一屋檐下,隨便胡來”,迫使團隊以全新又合適的方式來開發應用;
  • Android原生優勢:和動態化(RN類)不同,您可以使用最熟悉的Java/Kotlin語言,及各種原生API來開發您的插件。這使得應用能和系統更“契合”,充分利用原生的各種優勢,且在性能上幾乎感受不到影響。

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

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

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

雖然我事先已做好功課,然而在一次技術大會上的調查結果卻讓人大跌眼鏡——在參會的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:

  • 既可以作為插件直接跑在主程序中;
  • 又可以作為單品直接安裝到設備中。

是的,就是這麽的神奇!

就這些了?

當然遠不止這些。我在曾演示過一段視頻,將龐大又復雜的360桌面變成插件,運行在360手機衛士中。

技術分享

試想,一個桌面插件涉及到的功能是“方方面面”的,小到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作者,高中大牛)在一次采訪時說過一段話:

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

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

當然,在“全面插件化”甚至“全民插件化”的道路上,我們還有太多的路要走,而如此龐大又復雜的RePlugin,是十多位研發人員共同努力的成果,且得到了部門領導,和公司技術委員會的大力支持。我相信,RePlugin的開源,是一場新的開始

360手機衛士插件化RePlugin今日開源