筆記 - Android應用架構 (Android Dev Summit 2015)
來自: 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狀態機
小結
- 為了離線設計(為了更好的UX不能責怪網絡)
- UI是基于model的
- App Logic負責同步model和服務器
- 這兩者不互相依賴(在必要的時候使用events和callbacks通知狀態改變)
Application Logic - 應用邏輯
- 解耦
- 如果有用的話,使用依賴注入(use it if it helps)
- 了解副作用(這不是那么容易的事,尤其是當你在只在自己桌上測試的時候,當你坐在那兒的時候一切都是在最佳狀態)
- 為了更好的性能,避免反射(筆者看過一些項目在命令中心大肆使用反射,這真的很讓我無語)
很多依賴注入框架有一個很重的運行期組件或者編譯期組件。
比如Dagger2在編譯期做這些事,從而達到更好的性能。所以要仔細看看這些庫,權衡它們的優劣。
網絡
- 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>
這樣就靈活很多。
- 電池和數據
- 盡量做批量請求
- 如果有用的話,使用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
-
36min: 自定義view的使用
<p>筆者在這想到了非死book發過的那篇feed view優化的文章,QQ空間Android版也有類似的實踐。</p> <p> 許多人在應該使用Custom View的時候使用了Fragment,應該更多從 <em>內聚UI塊</em> 來看待這個問題。比如有一些事件需要這塊UI的某一個部分來響應,那它就更像viewgroup。而如果其中的一部分需要響應應用的其他元素,像是生命周期,做了一些事像是注冊事件,那它可能更適合作為一個fragment。但人們在考慮特定子段的時候總會更偏重于fragment。 </p>
</div> </li>
37:33min:怎么在app死掉的時候處理任務到磁盤的串行化和停止
在Demo里直接使用了演講者自己寫的Job Queue,開發者可以對它做簡單的串行化,比如使用 Tape 。
在這個應用里,你會使用sync adapter還是異步REST請求來和服務器溝通?在這個demo里,我們分開了本地和網絡任務。對本地任務,我們總是使用同步請求。而對網絡任務,我們使用Job Queue。
39min的時候有一個問題,提到了服務器數據問題,這里也有一個很重要的原則:不信任服務器,保持本地model的一致性。在Model保存前,總是去檢查是否正確(Null Pattern也是一個很好的實踐哦)。
</ol> </div>
本文由用戶 TriB64 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!