筆記 - Android應用架構 (Android Dev Summit 2015)

TriB64 8年前發布 | 18K 次閱讀 安卓開發 Android開發 移動開發

來自: http://blog.zhaiyifan.cn/2016/01/29/android-app-architecture-2015/

視頻見: https://www.油Tube.com/watch?v=BlkJzgjzL0c&feature=em-subs_digest

印度哥們的發音每次都能讓我一陣沉醉。

</div>

盡快行動

  • 早期的設計抉擇對app的影響很大
  • 基本架構會讓你思考需要解決的問題變得更容易或困難。

哪種模式?

  • MVC
  • MVP
  • Reactive
  • Cairngorm
  • Flux
  • fdsafdsa
  • MVVM
  • CLEAN

這不是一個庫的調查

  • 很多很棒的libraries展示了思考app的不同方式
  • 趨勢迅速改變,但一些挑戰是永恒的
  • 久而久之 “熔巖流” 發生了

Lava Flow 熔巖流:解釋一下,大致就是說一些因為種種狀況寫得不太好的代碼,恩,比如產品/PM/某些開發老大等呵呵呵催進度的時候,被加入到實際生產環境,而其實際仍處于開發中的階段,導致后期需要維護原先未完成狀態的設計的兼容性,比如各種API/命名等。而在這種team里面,隨著熔巖流的不斷發生,人員的進進出出變動,系統內部各種設計的目的丟失,導致之后的工程師不僅不敢清理這些代碼,反而只能繼續增加它們的復雜性,把系統弄得越來越混亂。

如果你有一定工作經驗,相信對這種狀況一定不會陌生。:-D

為了用戶體驗(UX)的架構

用戶不會關心你系統的架構,他們只在意用戶體驗。

接著我們看到了一個IM app的例子,用戶發送了一個消息,然后就看到菊花轉啊轉,用戶并不知道發生了什么。

非持久化的提示

見下圖,view發送命令給view controller,然后controller請求網絡,并等待網絡response再更新view。

這樣就會讓用戶覺得體驗很差勁,如果我們試著再增加一個Model層(存儲),如下圖,當用戶點擊后,view controller并不會直接發送請求,而是先更改Model層狀態,Model會先把更新后的狀態回調給View Controller,更新View的UI,然后才去發送網絡請求,并等待response,然后再次更新UI(其實這里的Model就是Repository層了,對上面屏蔽了數據來源,UGC/FEED流的app中這種體驗很重要)。

這種設計下,我們就很容易可以造一種假feed(比如真實的是黑色,假的是灰色),等到網絡response回來了,再變成真feed(黑色)。

這就是 非持久化的提示

緩存

第二個例子,當我們從詳情頁退出回到列表頁,然后再次點擊列表項進入剛才的詳情頁,數據竟然不見了。2秒前我還在這里,為什么當我再次回來的時候數據就沒了呢。

因為數據不是持久化的!它只是一個短暫的存儲,數據來源是網絡。

于是我們就得到了上圖這樣的設計,這樣一來我們一進頁面就能看到數據,因為數據來自本地,Application Logic會在收到View Controller請求的時候同時請求Persistent Model和Network。

非持久化提示隊列

再來個神奇的場景,見下圖,當用戶輸入一條信息后,UI展示了假數據,但當他繼續輸入后,卻沒有更新新的假數據到界面,這其中發生了什么呢?

因為我們的架構是基于命令的!見下面的后臺處理示意圖:

當我們處理請求的時候,我們同時只能做2件事(想象一個一個max為2的線程池),當我們在一個線程抓取bitmap,另一個線程發送數據到服務器的時候,我們無法處理第三個命令(比如從磁盤讀取評論)。這是一種很糟糕的情況,調度的優先級有問題,但設計者很難料到這種情況的發生,因為并不知道每一個命令要花多久,你并不能總是估算出來。

但我們可以分開這些東西。

見上圖,我們把隊列分為網絡和本地隊列,這樣就不會出現剛才的情形了。就算網絡出了問題,用戶還是能看到本地的假反饋。

Activity狀態機

小結

  1. 為了離線設計(為了更好的UX不能責怪網絡)
  • UI是基于model的
  • App Logic負責同步model和服務器
  • 這兩者不互相依賴(在必要的時候使用events和callbacks通知狀態改變)

Application Logic - 應用邏輯

  1. 解耦
  • 如果有用的話,使用依賴注入(use it if it helps)
  • 了解副作用(這不是那么容易的事,尤其是當你在只在自己桌上測試的時候,當你坐在那兒的時候一切都是在最佳狀態)
  • 為了更好的性能,避免反射(筆者看過一些項目在命令中心大肆使用反射,這真的很讓我無語)

很多依賴注入框架有一個很重的運行期組件或者編譯期組件。

比如Dagger2在編譯期做這些事,從而達到更好的性能。所以要仔細看看這些庫,權衡它們的優劣。

網絡

  1. API設計
  • 為了你的用戶設計后臺(想到了一些后臺API為了自己內部的解耦讓app一個頁面請求十幾個接口)
  • 在服務器盡量處理更多
  • 傳遞metadata給客戶端

比如有一張很大很大的圖片:

{
  "user":{
 "name": "MarkZhai",
 "photoUrl": "https://blog.zhaiyifan.cn/..."
 }
}

</div>

如果只是傳這些數據給客戶端,那就意味著客戶端需要自己處理很多邏輯,比如圖片寬/高,processor等等:

{
  "user":{
 "name": "MarkZhai",
 "photoUrl": {
 "width" : 300,
 "height" : 500,
 "url" : "https://blog.zhaiyifan.cn/...",
 "palette" : {}
 }
 }
}

</div>

這樣就靈活很多。

  1. 電池和數據
  • 盡量做批量請求
  • 如果有用的話,使用JobScheduler

批量請求會更省電。不想讓你的app臭名昭著吧。

Act locally, sync globally!

為了更好的用戶體驗。全局只做同步,更多的用戶回饋性操作在本地進行。

Activities 和 Fragments

(又想到了知乎3.0是怎么回事)

  • Q: 我應該使用哪一個?
  • A: Fragments是activity的封裝部件

So…Fragment的英譯是碎片,所以更多的不要為了Fragment而Fragment,當Activity太大,承載太多不同UI的時候,再去嘗試拆分為獨立的一個個Fragment。筆者在這里想到了 單一職責原則

Fragments 和 Views

Q: 我應該使用哪一個?

  • Views只是螺母和螺釘,即最基本的組成部分
  • 而Activities和Fragments是有生命周期的結構
  • 使用兩者,保持職責清晰

內存

  • 避免在代碼熱路徑上分配對象(如:可以在外面申明的對象去放到循環里重復申明)
  • 對象池 和 復用

一切都是為了減少GC。

來看一個例子

Rect getBounds() vs. void getBounds(Rect in)

顯然第一個設計更干凈更符合語言習慣,但它會額外創建一個 Rect ;而第二個方法則會從外面帶Rect進來,從而不會做額外的內存分配。

難道我們都得用第二個么?不。我們只需要在一些很熱的路徑上去考慮這些問題,比如那些會運行很多很多次的布局,比如每秒需要進行60次的繪制。而像是你的那些事件處理,像是點擊等,你真的不需要去為此而那樣在內部扭曲系統。

性能

  • 如果丑陋的代碼可以幫助你的用戶,沒問題
    用戶不會去看你的代碼,只會看你的UI,如果你的UI很難看,那才是真正影響他們體驗的。
    而且編譯后的代碼不管怎么都會很難看(笑
  • 大部分的代碼都不是性能關鍵

Demo

Talk is cheap, show me the code .

Q and A

  1. 36min: 自定義view的使用

    <p>筆者在這想到了非死book發過的那篇feed view優化的文章,QQ空間Android版也有類似的實踐。</p>
    
    <p> 許多人在應該使用Custom View的時候使用了Fragment,應該更多從 <em>內聚UI塊</em> 來看待這個問題。比如有一些事件需要這塊UI的某一個部分來響應,那它就更像viewgroup。而如果其中的一部分需要響應應用的其他元素,像是生命周期,做了一些事像是注冊事件,那它可能更適合作為一個fragment。但人們在考慮特定子段的時候總會更偏重于fragment。 </p>
    
    

    </div> </li>

  2. 37:33min:怎么在app死掉的時候處理任務到磁盤的串行化和停止

    在Demo里直接使用了演講者自己寫的Job Queue,開發者可以對它做簡單的串行化,比如使用 Tape 。

  3. 在這個應用里,你會使用sync adapter還是異步REST請求來和服務器溝通?在這個demo里,我們分開了本地和網絡任務。對本地任務,我們總是使用同步請求。而對網絡任務,我們使用Job Queue。

  4. 39min的時候有一個問題,提到了服務器數據問題,這里也有一個很重要的原則:不信任服務器,保持本地model的一致性。在Model保存前,總是去檢查是否正確(Null Pattern也是一個很好的實踐哦)。

  5. </ol> </div>

 本文由用戶 TriB64 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!