[譯]使用MVI打造響應(yīng)式APP(五):輕而易舉地Debug

原文:REACTIVE APPS WITH MODEL-VIEW-INTENT - PART5 - DEBUGGING WITH EASE
作者:Hannes Dorfmann
譯者:卻把清梅嗅

前文我們探討了Model-View-Intent (MVI)架構(gòu)模式及其相關(guān)特性,在 第一篇文章 中岭辣,我們談到了 單項數(shù)據(jù)流的重要性應(yīng)用狀態(tài)應(yīng)該被業(yè)務(wù)邏輯驅(qū)動留夜。本文我們將展示這種架構(gòu)模式會怎樣回報開發(fā)者奕删,它可以讓開發(fā)者在開發(fā)過程中更輕而易舉進行debug拄氯。

遇到過這樣的情況嘛渡蜻?你得到了一個崩潰的報告满俗,但是你無法復(fù)現(xiàn)這個BUG菩鲜。聽起來似曾相識腻菇?我也是胳螟!在花了很多時間查看堆棧跟蹤和項目的源碼后昔馋,最終我選擇了放棄——關(guān)閉了這個issue,并提交了一個類似 無法復(fù)現(xiàn) 或者 某個Android生產(chǎn)商的某種特定的機型導(dǎo)致的特殊錯誤 的備注糖耸。

以我們的購物App舉例來說秘遏,在Home界面,用戶以某種方式進行下拉刷新嘉竟,但不知道為什么邦危,崩潰報告告訴我,當(dāng)用戶執(zhí)行下拉刷新獲取最新數(shù)據(jù)的操作時舍扰,應(yīng)用拋出了一個NullPointerException倦蚪。

因此,作為開發(fā)人員边苹,您啟動App并嘗試在Home界面進行下拉刷新陵且,但App并沒有崩潰, 它按照預(yù)期正常地運行。然后您開始仔細(xì)檢查自己的代碼个束,但是就是找不到哪里會導(dǎo)致NullPointerException的發(fā)生滩报。你打開了debug模式,一行一行逐步執(zhí)行該界面相關(guān)的代碼播急,但App仍然正常的運行—— 到底怎么樣才能讓它在下拉刷新時崩潰脓钾?

問題的根本在于你不能在App崩潰發(fā)生之前復(fù)現(xiàn)狀態(tài),如果遇到崩潰的用戶可以在崩潰報告中提供他App的狀態(tài)(在崩潰發(fā)生之前)以及堆棧跟蹤桩警,那不是很棒嗎可训?

通過 單向數(shù)據(jù)流Model-View-Intent ,這簡直輕而易舉捶枢。

用戶執(zhí)行所有Intent界面對Model進行渲染時握截,我們很方便地能夠?qū)⑺鼈冞M行打印,讓我們通過在HomePresenter中添加Log來為Home界面執(zhí)行這樣的操作(具體代碼請參考 第三節(jié)烂叔,該小節(jié)我們針對狀態(tài)折疊器進行了探討)谨胞。

在以下代碼片段中,我們使用Crashlytics(譯者注:一種崩潰報告工具)蒜鸡,使用其它的崩潰報告工具也是一樣的:

class HomePresenter extends MviBasePresenter<HomeView, HomeViewState> {

  private final HomeViewState initialState; // Show loading indicator

  public HomePresenter(HomeViewState initialState){
    this.initialState = initialState;
  }

  @Override protected void bindIntents() {

    Observable<PartialState> loadFirstPage = intent(HomeView::loadFirstPageIntent)
          .doOnNext(intent -> Crashlytics.log("Intent: load first page"))
          .flatmap(...); // 加載數(shù)據(jù)的業(yè)務(wù)邏輯

    Observable<PartialState> pullToRefresh = intent(HomeView::pullToRefreshIntent)
          .doOnNext(intent -> Crashlytics.log("Intent: pull-to-refresh"))
          .flatmap(...); // 加載數(shù)據(jù)的業(yè)務(wù)邏輯

    Observable<PartialState> nextPage = intent(HomeView::loadNextPageIntent)
          .doOnNext(intent -> Crashlytics.log("Intent: load next page"))
          .flatmap(...); // 加載數(shù)據(jù)的業(yè)務(wù)邏輯

    Observable<PartialState> allIntents = Observable.merge(loadFirstPage, pullToRefresh, nextPage);
    Observable<HomeViewState> stateObservable = allIntents
          .scan(initialState, this::viewStateReducer) // 對狀態(tài)進行折疊
          .doOnNext(newViewState -> Crashlytics.log( "State: "+gson.toJson(newViewState) ));

    subscribeViewState(stateObservable, HomeView::render); // 展示新的狀態(tài)
  }

  private HomeViewState viewStateReducer(HomeViewState previousState, PartialState changes){
    ...
  }
}

通過RxJava.doOnNext() 操作符胯努,我們可以很輕松將每個intent和每個intentresult——也就是即將渲染在view層上的狀態(tài)進行打印。

我們將view的狀態(tài)序列化為json字符串逢防,現(xiàn)在叶沛,我們的崩潰報告變成了這樣:

現(xiàn)在來看看這些日志,我們不僅能看到崩潰發(fā)生之前的最后一個狀態(tài)忘朝,而且還能看到用戶達到這個狀態(tài)所經(jīng)歷的完整歷史記錄——為了保證可讀性灰署,我將data字段內(nèi)的內(nèi)容替換為了[...]:

  • 1.用戶啟動了App,通過加載首頁數(shù)據(jù)的intent,這樣loadingFirstPage的值為true,使得加載指示器展示了出來溉箕,同時數(shù)據(jù)也被加載完畢(data[…])晦墙。

  • 2.接下來用戶滾動列表,并達到了列表的底部肴茄,這觸發(fā)了加載下一頁數(shù)據(jù)的intent晌畅,并開始加載更多的數(shù)據(jù)(分頁),這也導(dǎo)致了loadingNextPage狀態(tài)的改變独郎,它的值變成了true踩麦。

  • 3.一旦分頁數(shù)據(jù)被加載成功,loadingNextPage狀態(tài)改變成了false,用戶再次重復(fù)操作達到了列表的底部氓癌,并又一次出發(fā)了觸發(fā)了加載下一頁數(shù)據(jù)的intent谓谦。

  • 4.接下來用戶開始嘗試下拉刷新的intent,這導(dǎo)致loadingPullToRefresh狀態(tài)變更為了true,然后贪婉,App突然發(fā)生了崩潰—— 這之后就沒有更多日志了反粥。

這些信息如何幫助我們解決這個bug呢?顯然疲迂,我們知道用戶觸發(fā)了哪些操作才顿,因此我們完全可以手動復(fù)現(xiàn)這個崩潰。此外尤蒿,因為我們將App的狀態(tài)用json進行表現(xiàn)郑气,因此我們可以簡單地使用最后一個狀態(tài),反序列化json并將此狀態(tài)作為我們的初始狀態(tài)來修復(fù)該錯誤:

String json ="  {\"data\":[...],\"loadingFirstPage\":false,\"loadingNextPage\":false,\"loadingPullToRefresh\":false} ";
HomeViewState stateBeforeCrash = gson.fromJson(json, HomeViewState.class);
HomePresenter homePresenter = new HomePresenter(stateBeforeCrash);

接下來我們打開了Debug調(diào)試工具腰池,并嘗試觸發(fā)下拉刷新的intent,事實證明尾组,如果用戶向下滾動頁面2次,則沒有更多數(shù)據(jù)可用示弓,并且我們的App并沒有進行相應(yīng)的處理讳侨,因此后續(xù)的下拉刷新操作導(dǎo)致了崩潰。

結(jié)語

一個應(yīng)用狀態(tài)隨時隨地 可快照App可以使我們開發(fā)人員的生活更加輕松奏属。我們不僅能夠輕松的 復(fù)現(xiàn)崩潰跨跨,而且可以將狀態(tài)進行序列化來 編寫回歸測試,并且這幾乎沒有什么成本囱皿。

請記住勇婴,這些便利只有在App的狀態(tài)遵循 單項數(shù)據(jù)流不可變铆帽、純函數(shù) 的原則的情況下才能享受到(即被業(yè)務(wù)邏輯驅(qū)動)咆耿,Model-View-Intent讓我們偏向了這種思想流派,而這個架構(gòu)模式中有一個非常棒并且有效的額外的效果爹橱,那就是本文所提到的構(gòu)建了一個 可快照App

可快照 的應(yīng)用有什么缺陷呢?顯然我們正在將App的狀態(tài)序列化(比如通過Gson).這增加了一些額外的計算資源的負(fù)荷愧驱,平均來算的話慰技,狀態(tài)第一次被Gson序列化大約需要30毫秒,因為Gson必須使用反射來掃描類组砚,以確定必須序列化的字段吻商。

Nexus 4上,狀態(tài)的連續(xù)序列化平均需要大約6毫秒糟红。由于序列化在.doOnNext()中運行艾帐,雖然這通常在后臺線程上運行,但的確是這樣:我的App用戶必須比其它應(yīng)用的用戶多等待6毫秒盆偿,才能在屏幕上看到新的狀態(tài)柒爸。

我的觀點是,這對于用戶來說也許并不明顯事扭,但是對狀態(tài)進行 快照 的一個問題是捎稚,在崩潰時,崩潰報告工具從用戶設(shè)備上傳到其服務(wù)器的數(shù)據(jù)量要大得多—— 如果用戶通過wifi連接求橄,這無關(guān)痛癢今野,但如果用戶處于移動網(wǎng)絡(luò)下則可能會有一定的爭議。

最后罐农,將狀態(tài)附加在崩潰報告中時条霜,您可能會泄漏用戶的一些敏感的數(shù)據(jù)。針對這個問題涵亏,一個方案是不序列化敏感數(shù)據(jù)宰睡,但這可能導(dǎo)致連接到崩潰報告的狀態(tài)不完整(因此這些報告可能幾乎無用),另外一個方案則是將敏感數(shù)據(jù)進行加密——但這可能需要一些額外的CPU占用溯乒。

總結(jié)一下:我個人認(rèn)為這樣 可快照App有很多優(yōu)點夹厌,但是,你可能需要做出一些權(quán)衡裆悄。也許您開始為內(nèi)部版本或beta版本啟用App快照矛纹,以衡量它其產(chǎn)生的作用。


系列目錄

《使用MVI打造響應(yīng)式APP》原文

《使用MVI打造響應(yīng)式APP》譯文

《使用MVI打造響應(yīng)式APP》實戰(zhàn)


關(guān)于我

Hello光稼,我是卻把清梅嗅或南,如果您覺得文章對您有價值,歡迎 ??艾君,也歡迎關(guān)注我的博客或者Github采够。

如果您覺得文章還差了那么點東西,也請通過關(guān)注督促我寫出更好的文章——萬一哪天我進步了呢冰垄?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蹬癌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌逝薪,老刑警劉巖隅要,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異董济,居然都是意外死亡步清,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門虏肾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來廓啊,“玉大人,你說我怎么就攤上這事封豪∏绰郑” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵撑毛,是天一觀的道長书聚。 經(jīng)常有香客問我,道長藻雌,這世上最難降的妖魔是什么雌续? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮胯杭,結(jié)果婚禮上驯杜,老公的妹妹穿的比我還像新娘。我一直安慰自己做个,他們只是感情好鸽心,可當(dāng)我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著居暖,像睡著了一般顽频。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上太闺,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天糯景,我揣著相機與錄音,去河邊找鬼省骂。 笑死蟀淮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的钞澳。 我是一名探鬼主播怠惶,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼轧粟!你這毒婦竟也來了策治?” 一聲冷哼從身側(cè)響起脓魏,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎览妖,沒想到半個月后轧拄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體揽祥,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡讽膏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了拄丰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片府树。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖料按,靈堂內(nèi)的尸體忽然破棺而出奄侠,到底是詐尸還是另有隱情,我是刑警寧澤载矿,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布垄潮,位于F島的核電站,受9級特大地震影響闷盔,放射性物質(zhì)發(fā)生泄漏弯洗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一逢勾、第九天 我趴在偏房一處隱蔽的房頂上張望牡整。 院中可真熱鬧,春花似錦溺拱、人聲如沸逃贝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沐扳。三九已至,卻和暖如春句占,著一層夾襖步出監(jiān)牢的瞬間沪摄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工辖众, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留卓起,地道東北人。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓凹炸,卻偏偏與公主長得像戏阅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子啤它,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內(nèi)容