成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

微信Android 模塊化架構重構實踐(上)

開發 開發工具
微信Android誕生之初,用的是常見的分層結構設計。這種架構簡單、清晰并一直沿襲至今。這是微信架構的v1.x時代。

微信Android架構歷史

微信Android誕生之初,用的是常見的分層結構設計。這種架構簡單、清晰并一直沿襲至今。這是微信架構的v1.x時代。

圖1-架構演進

圖1-架構演進

到了微信架構的v2.x時代,隨著業務的快速發展,消息通知不及時和Android 2.3版本之前webview內存泄露問題開始突顯。由于代碼、內存、apk大小都在增長,對系統資源的占用越來越多,導致微信進程容易被系統回收。因此微信開始轉向多進程架構,獨立的通信進程保持長連接的穩定性,獨立的webview進程也阻隔了內存泄露導致的問題。

時間繼續推進,我們也遇到了65535問題和LinearAlloc問題。這時的微信已經具備了許多功能像朋友圈、搖一搖、附近的人等等,分離核心功能和其他業務模塊變得越發重要。為此,微信開啟了第三次架構改造(v3.x)。我們對各種產品功能進行解耦并拆分到相互獨立的p_xxx工程中,這是微信***次進行模塊化架構的重構。經過幾個月的努力,微信拆出了幾十個p工程,它們都通過基礎組件訪問網絡、存儲等服務,互相獨立并行。新的p工程架構支撐了微信更快速的業務發展,配合多分支開發模式的改進,能夠支持團隊多分支多team的并行開發。

圖2 - 架構圖

圖2 - 架構圖

為何再次重構微信

原本好好的架構出了什么問題?

從上個架構之后的兩年多時間里,微信Android基本沒有大的架構改動。配合gradle的編譯,以及git的多分支并行開發,微信的模塊工程數量不斷增多,支撐了游戲、支付等大功能,可以說這段時間里原有架構起到了很好的作用。

然而隨著代碼繼續膨脹,一些問題開始突顯出來。首先出問題的是基礎工程libnetscene和libplugin。基礎工程一直處于不斷膨脹的狀態,同時主工程也在不斷變大。同時基礎工程存在中心化問題,許多業務Storage類被附著在一個核心類上面,久而久之這個類已經沒法看了。此外當初為了平滑切換到gradle避免結構變化太大以及太多module,我們將所有工程都對接到一個module上。缺少了編譯上的隔離,模塊間的代碼邊界出現一些劣化。雖然緊接著開發了工具來限制模塊間的錯誤依賴,但這段時間里的影響已經產生。在上面各種問題之下,許多模塊已經稱不上“獨立”了。所以當我們重新審視代碼架構時,以前良好模塊化的架構設計已經逐漸變了樣。

 

圖3 - 架構逐漸的變化

圖3 - 架構逐漸的變化

“君有疾在腠理,不治將恐深”,在我們還在猶豫到底要不要重構的時候,硬件同學向我們提出了需求。希望將微信Android代碼移植到類似微信相冊這樣產品中。這樣就可以快速跟進微信業務***的支撐組件、協議、安全性、后臺服務等能力,而且代碼要盡可能精簡,可以選擇和定制模塊,可以移植模塊來實現原型嘗試。但就之前的情況來說,微信一時難以滿足。這下定了,還得重構。

于是我們回過頭仔細看之前的設計,找找問題究竟是怎么來的。

問題出在哪

先尋找代碼膨脹的原因。

翻開基礎工程的代碼,我們看到除了符合設計初衷的存儲、網絡等支持組件外,還有相當多的業務相關代碼。這些代碼是膨脹的來源。但代碼怎么來的,非要放這?一切不合理皆有背后的邏輯。在之前的架構中,我們大量適用Event事件總線作為模塊間通信的方式,也基本是唯一的方式。使用Event作為通信的媒介,自然要有定義它的地方,好讓模塊之間都能知道Event結構是怎樣的。這時候基礎工程好像就成了存放Event的唯一選擇——Event定義被放在基礎工程中;接著,遇到某個模塊A想使用模塊B的數據結構類,怎么辦?把類下沉到基礎工程;遇到模塊A想用模塊B的某個接口返回個數據,Event好像不太適合?那就把代碼下沉到基礎工程吧……

就這樣越來越多的代碼很“自然的”被下沉到基礎工程中。

我們再看看主工程,它膨脹的原因不一樣。分析一下基本能確定的是,首先作為主干業務一直還有需求在開發,膨脹在所難免,缺少適當的內部重構但暫時不是問題的核心。另一部分原因,則是因為模塊的生命周期設計好像已經不滿足使用需要。之前的模塊生命周期是從“Account初始化”到“Account已注銷”,所以可以看出在這時機之外肯定還有邏輯。放在以前這不是個大問題,剛啟動還不等“Account初始化”就要執行的邏輯哪有那么多。而現在不一樣,再簡單的邏輯堆積起來也會變復雜。此時,在模塊生命周期外的邏輯基本上只能放主工程。

此外的問題,模塊邊界破壞、基礎工程中心化,都是代碼持續劣化的幫兇。

總之在模塊化上我們忽視了一些重要的問題,必須重塑。

重塑模塊化

重塑模塊化,我們分解為三個目標:

  • 改變通信方式
  • 重新設計模塊
  • 約束代碼邊界

改變通信方式

前面講過,我們使用Event總線作為模塊間通信的媒介,這種設計很常見。然而當回顧整體代碼時能發現,Event并非所有通信需要的***形式。它的特點適合一對多的廣播場景,依賴關系弱。一旦遇到需要一組業務接口時,用Event寫起來那是十分痛苦的。也正因如此,這種情況下大家都跳過了Event的使用,直接將代碼下沉到了基礎工程,共享代碼,進而導致基礎工程的不斷膨脹。

所以選個合適的通信方式很有必要,我們希望兼顧考慮開發的便利性和協議的約束性。

Event不合適。協議通信如何?

我們理解的協議通信,是指跨平臺/序列化的通信方式,類似終端和服務器間的通信或restful這種。現在這種形式在終端內很常見了。協議通信具備一種很強力解耦能力,但也有不可忽視的代價。無論什么形式的通信,所有的協議定義需要讓通訊兩方都能獲知。通常為了方便會在某個公共區域存放所有協議的定義,這情況和Event引發的問題有點像。另外,協議如果變化了,兩端怎么同步就變得有點復雜,至少要配合一些框架來實現。在一個應用內,這樣會不會有點復雜?用起來好像也不那么方便?更何況它究竟解決多少問題呢。

所以我們想要簡單點。經過權衡,我們決定用模塊提供“SDK”的方式作為它與其他模塊進行通信的手段。

通常“SDK”提供的是什么,是接口 + 數據結構。這種方式好處明顯:實現簡單也能解決問題,IDE容易補全、調用接口方便,不用配合工具,協議變化直接反映在編譯上,維護接口也簡單了。

其實想想,用協議的方式在終端內作為通信手段,開發效率低,也容易出錯。因此可能會誕生各種框架和工具來提升這里損失的效率。到頭來,是不是大家都實現了一套類似RPC這樣的封裝。其實本地的通信,能用接口就挺好,不能用的時候,再用協議封裝也來得及。

確定了方案,實現起來就很簡單。我們的注冊方式和接口訪問都很簡單。用接口注冊,再用接口訪問,不暴露實現細節。如下圖。

圖4 - 注冊接口

圖4 - 注冊接口

圖5 - 訪問接口

圖5 - 訪問接口

接下來,怎么暴露接口更方便?

模塊暴露“SDK”的方式無非就是新建個“SDK”工程,剝離接口和數據結構到該工程里面,然后讓其他模塊引用編譯。但這樣有點麻煩,能不能再方便點?

當然有辦法。我們實現了另一種接口暴露的形式——“.api化”。

使用方式和思路都很簡單。對于java文件,將工程里想要暴露出去的接口類后綴名從“.java”改成“.api”,就可以了。

而且并不只是java文件,其他文件如果也想暴露,在文件名后增加".api”,也一樣可以。

圖6 - “.api化”

圖6 - “.api化”

當然,要讓工程支持這樣的方式,gradle文件肯定會有一點改變。

settings.gradle

build.gradle

圖 7 - settings.gradle & build.gradle

圖 7 - settings.gradle & build.gradle

就這樣,可以說暴露接口變得非常容易,不用擔心實現類也被人引用到。而它的實現原理也相當簡單:自動生成一個“SDK”工程,拷貝.api后綴文件到工程中就行了,后面其他工程依賴編譯的只是這個生成的工程。簡單好用。

還有個細節,如果想編輯.api后綴的java文件,為了能讓Android Studio繼續高亮該怎么辦?可以在File Type中把.api作為java文件類型。

圖8 - 設置File Types

圖8 - 設置File Types

重新設計模塊

要把模塊重新設計,還要做好幾件事。首先,消滅代碼經常下沉的“三不管區域”——基礎工程。這意味著原來的模塊要把之前下沉的代碼重新認領回去。

圖9 - 分層結構改造

圖9 - 分層結構改造

為了鞏固替代基礎工程的mmkernel層,不被濫用為新的代碼堆放處,順便還要解決中心化問題。就必須強化它的職責和設計。

mmkernel結構可以很通用的定義為CoreAccount/CoreNetwork/CoreStorage三個部分,分別提供了核心賬號狀態(初始化、注銷)、網絡狀態回調(鏈接建立)、存儲狀態生命周期(db創建、銷毀、用戶存儲路徑切換、sdcard掛起)。

圖10

圖10

再然后是生命周期問題,我們需要重新設計正確的生命周期。

之前講過,我們的模塊生命周期大體上只有“Account初始化”和“Account注銷”兩個階段。這已經不夠用了。

所以擴大模塊的生命周期,就給了模塊實現各種代碼需要的時機,才能避免大家往主工程塞代碼。

圖11

圖11

實現新的生命周期是一個正確的選擇,同時產生了解決另一個問題的機會——復雜的啟動流程。

要知道主工程的代碼一部分原因是啟動流程堆積造成的,邏輯多了代碼自然多。隨之而來的問題就是代碼多了,邏輯也就跟著復雜起來。微信的初始化邏輯是順序排列在一起并從上到下執行,某種情況下還會異步啟動。當程序啟動流程比較復雜時,這樣的代碼會產生“隱性依賴”的問題。“隱性依賴”顧名思義就是:原本并應該存在依賴的代碼,隨著版本的迭代逐漸產生了依賴,而且還不明顯。這樣的情況會讓情況惡化,大家只敢往里面堆代碼,但卻不敢“亂動”。

所以重新設計的模塊應該要徹底避免這些問題。

我們重新定義了模塊的生命周期,將模塊的生命周期延長到應用啟動和退出。而后,每個模塊都可以定義一個Plugin類,作為模塊的“支柱”或“起點”。作為解決初始化問題的手段,它具備幾個主要階段:dependency()、configure()、execute()

圖12 - plugin初始化的幾個階段

圖12 - plugin初始化的幾個階段

dependency()階段,用于設置需要依賴的其他Plugin,當然提供那個Plugin的別名接口類就可以了。

圖13 - 設置dependency

圖13 - 設置dependency

依賴階段我們會生成整個模塊的依賴樹。這與編譯時的依賴不同。通常的依賴關系是分為兩種的,一種是類型依賴也就是編譯期依賴,需要被依賴模塊提供具體類型才能編譯通過;另一種依賴則是運行期的邏輯依賴或數據一致性依賴,當一個模塊用這種方式依賴另一個模塊,就意味著,前者的執行要依賴后者執行已完成,通常是為了數據準備妥當或保證所需服務已被注冊。

顯性的運行期依賴把之前啟動邏輯的“隱性依賴”完全暴露在陽光之下,改啟動邏輯不用提心吊膽。

圖14 - 依賴關系樹狀圖

圖14 - 依賴關系樹狀圖

configure()階段,該階段是根據之前的依賴樹遍歷執行。通常用于初始化一些數據配置、注冊IService服務、向前面依賴的模塊注冊一些回調等等。

此外這個階段還有額外的作用是插入BootTask,用于后面execute()階段的執行。

圖15 - configure階段

圖15 - configure階段

execute()階段,為了改變啟動流程不清楚的情況,強調啟動邏輯之間的依賴關系,我們現在將每個要執行的啟動步驟封裝為BootTask。前面的configure階段時,我們可以將BootTask插入到通過dependency()得到的依賴樹。每個Plugin同時也是一個BootTask,也因此擁有execute()接口。最終得到了包含所有BootTask的啟動樹,將遍歷執行所有節點執行execute()。

 

圖16 - BootTask

圖16 - BootTask

獨立使用BootTask的方式并不十分常見,通常Plugin本身的execute已經夠用。不過在一些通用型組件初始化嘗試會需要用到,如某些給某個全局使用的預加載資源提前初始化的邏輯。

為何設計configure()和execute(),這可以理解為“收集任務”和“執行任務”的兩個階段。另外這樣的抽象還可以實現從外部調度execute的執行線程,將啟動邏輯和啟動異步代碼分開。順便解決了,原來異步啟動代碼混亂不堪的情況。

約束代碼邊界

從之前的經驗看,要想約束好代碼的邊界不被破壞,編譯上的隔離是唯一法寶。

除了工程和工程之間的分割,在工程的內部如果也能實現約束代碼就更好了,算是將問題扼殺在搖籃里。之前通常的做法是以module工程為單位的相互分離,但在工程內部并不限制代碼相互引用。所以為了規范代碼,常能看到用包名作為約定,區分內部功能職責,靠約定維持解耦。隨著時間推移,很快就能發現包名約定作為約束太弱了,在快速迭代的代碼上很難一直維持下去。不管怎么樣解決,總要通過一些手段審查代碼引用的對不對。感覺有點防不勝防。為此,我們實現了一種簡單易用、粒度更細的工程組織結構——pins工程結構

圖18 - code-check

圖18 - code-check

這樣的工程組織形式的兩個明顯好處:

  • 約束代碼粒度和小代碼邊界的利器

粒度極小,一個pin工程也許只有一個源文件,只要它能表達一個獨立職責。對于任何一個模塊,從內部約束自己的功能結構,是對整體代碼邊界約束的極大補充。以前面插的結構為例,一個gallery業務可能提供了幾種不同的產品功能,以及支撐能力。那么將其相互獨立的代碼進行區分,避免混雜,就會顯得十分必要。清晰的結構,意味著后期維護成本的降低和開發效率的提高,留下了靈活性。

  • 避免的超量module的創建,輕量

pins工程某種程度上能減少一些粒度太小的module工程,也一定程度的緩解太多module工程時的gradle編譯性能問題。

至此,我們基本完成了重塑模塊化的設計目標,解決掉很多之前沒有考慮的問題。算是模塊化的加強版。另外設計是一方面,拆分解耦原來代碼以及遷移還是另一回事,這個過程也是十分艱難和枯燥這里就不細講了。接下來想辦法看看重構的效果。

看看效果

重新設計的模塊化加上代碼的重構。我們終于能滿足之前硬件同學的需要。同時一并解決許多拖欠的問題。

在編譯上,整體編譯速度會因為module增多而下降一些。但拆分module之后,卻能顯著加快單工程增量編譯的速度。和之前相比,一行代碼的增量編譯耗時能減少60%。

除了滿足需求外,架構設計的效果并不好量化,不過我們嘗試用一個demo來說明。

WeChat nano

基于前面介紹過的輕量的微信內核mmkernel層,再配合一個不包含界面的基礎聊天模塊和Auth模塊,可以在短時間里開發出一個及精簡版本的微信——WeChat nano。

圖19 - WeChat nano

圖19 - WeChat nano

模擬這個console的界面是單獨開發的,時間的大頭都花在這上面。

它的效果不錯:

  • 可以讓安裝包大小縮減到3.5M,大概是完整版本的10%
  • 能大幅減小內存占用,約占用完整版本的25% (注:只計算應用相關有不同的部分PSS)

大概就是這樣。

下篇:http://zhuanlan.51cto.com/art/201708/547813.htm

原文鏈接:https://www.qcloud.com/community/article/441423  作者:carlguo

【本文是51CTO專欄作者“騰訊云技術社區”的原創稿件,轉載請通過51CTO聯系原作者獲取授權】

戳這里,看該作者更多好文

 

責任編輯:武曉燕 來源: 51CTO專欄
相關推薦

2017-08-11 16:10:36

微信Android實踐

2017-05-18 11:43:41

Android模塊化軟件

2013-08-20 16:45:22

重構Web App模塊化

2015-07-02 13:21:44

模塊化數據中心

2016-12-14 14:50:26

CSS預處理語言模塊化實踐

2010-02-03 09:01:01

Java動態模塊化

2019-08-28 16:18:39

JavaScriptJS前端

2021-10-11 09:51:37

模塊化UPS架構

2017-02-13 18:46:38

Android模塊化組件化

2016-11-08 20:31:19

同方服務器模塊化

2025-05-12 08:45:00

模塊化FastAPI路由分發

2017-06-09 10:06:54

微信小程序架構分析

2022-01-10 08:43:25

CanonicalSnap應用Linux

2023-06-28 08:12:49

Python代碼重構

2021-12-24 07:10:36

架構分層模塊化

2017-07-11 11:02:03

APP模塊化架構

2020-09-17 10:30:21

前端模塊化組件

2013-08-20 15:31:18

前端模塊化

2017-05-18 10:23:55

模塊化開發RequireJsJavascript

2015-10-10 11:29:45

Java模塊化系統初探
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 一级片在线观看视频 | 玖玖玖在线 | 欧美一二三 | 欧美激情 一区 | 欧美国产精品 | 欧美日韩久久 | 国产美女久久 | 精品网站999www | 日日操视频 | 羞羞的视频免费在线观看 | 午夜激情影院 | 91色网站| 免费成人在线网站 | 久久99国产精品 | 国产综合久久 | www.97国产| 久久精品免费一区二区三 | 日韩欧美一区二区三区免费观看 | 亚洲理论在线观看电影 | 欧美亚洲第一区 | 成年人在线视频 | 国产有码| 欧美日韩久久精品 | 国产精品黄色 | 最新国产在线 | 成人亚洲性情网站www在线观看 | 麻豆hd| 国产色网站| 亚洲欧美精品在线观看 | 亚洲国产精品视频 | 中文字幕在线第一页 | 国产成人精品一区二区三 | 精品一区二区电影 | 日本天堂视频在线观看 | 永久精品 | 综合久久亚洲 | 国产一区二区免费在线 | 欧美视频成人 | 一区二区蜜桃 | 一区二区三区免费 | 中文字幕在线观看一区 |