1. 程式人生 > >還在用 MVP?快來試試 MVVM框架吧!

還在用 MVP?快來試試 MVVM框架吧!

優勢

穩定

  • 減少記憶體洩漏:新手很容易線上程切換的地方寫出導致記憶體洩漏的程式碼,但如果把執行緒切換交給框架來做,出錯的概率就大大降低。
  • 減少 crash:根據我的開發經歷,大部分 crash 都是空指標導致的。一般執行緒回撥裡最容易出現問題,當UI銷燬後,子執行緒依舊去操作UI,容易導致 crash。 本框架有完善的生命週期,UI銷燬後,框架對子執行緒做了強制的停止操作,大大減少 crash 的概率。

輕量

提示:這兩個依賴庫在 Android Studio 新建的專案裡幾乎都包含,也就是幾乎 0 依賴。

接入成本低

  • 侵入性低:不需要修改任何現有程式碼
  • 無縫嵌入:可間接當做 View 使用,無論之前使用 MVP 還是 MVC,往裡面加一個 View 根本不影響你的結構。

簡單

  • 對原生開發友好:你幾乎不需要學習框架 api 就可以開始使用。
  • 熟悉 react 和 flutter 的非常容易上手

解耦

MVVM 的強大之處在於 UI 和 邏輯 分離,處理邏輯時不需要關心 UI,寫 UI 時不需要管資料從哪獲取。

要更新時,你直接對資料進行修改,就會自動觸發重新渲染。 並不需要擔心效能問題,因為預設情況下,原來的 View 並不會被拋棄掉,僅僅會觸發一次 update 操作。

public class StatefulUserWidget extends StatefulWidget<View, UserWidget> {
    private UserBean user = UserDataSource.getInstance().getUser();

    public StatefulUserWidget(Context context, Lifecycle lifecycle) {
        super(context, lifecycle);
    }

    @Override
    protected State<UserWidget> createState(Context context) {
        return StateUtils.create(new UserWidget(context, lifecycle, user));
    }

    @Override
    public void initWidget(UserWidget widget) {
        widget.setOnClickListener(v -> setState(() -> {
            user = UserDataSource.getInstance().getUser();
        }));
        update();
    }

    @Override
    public void update() {
        super.update();
        widget.setUser(user);
    }
}

在 initWidget 方法中對 widget 設定了一個點選事件,點選後重新獲取資料,自動觸發 UI 的更新。 其實就是呼叫了 setState 方法來觸發更新,類似於 react 和 flutter,更新資料的操作需要放到該方法中,否則不會觸發更新。

高複用

本框架的設計思想類似於 flutter 的 "Everything's a Widget",即把所有的東西都視為控制元件。 各個控制元件之間保持獨立,容器控制元件可以組合一個或多個控制元件,每個控制元件都有獨立的生命週期。 因此,控制元件的複用性大大提高。

便捷的生命週期

得益於谷歌新引進的 lifecycle,讓每個 widget 都可以擁有完整的生命週期,甚至資料也可以擁有生命週期。

非同步支援 (同步發請求)

對於客戶端程式設計來說,最麻煩的是各種非同步呼叫和狀態同步。 多執行緒程式設計很難,稍有不慎,輕則記憶體洩漏,重則直接蹦潰。 本框架內部做了處理非同步請求,並在 onDestroy 時,自動取消子執行緒的操作,防止記憶體洩漏 或者 非同步導致的空指標問題。

本庫提供瞭如下方法支援資料修改,各位開發者可自行選擇合適的方法。

  • setState:同步執行資料修改操作(適用於非耗時的資料修改操作,無執行緒切換效能消耗)
  • setStateAsync:非同步執行的資料修改操作,並在UI銷燬時自動停止非同步執行緒
  • setStateAsyncWithCache:類似於 setStateAsync ,對快取提供支援。

有了它,你可以同步的方式去髮網絡請求。 合併多個請求的資料變得異常輕鬆(比如 先請求a,在請求b,合併結果變成c)。

快取支援

生活 快取很難。 一千個應用有一千種快取。 我見過網上很多快取方案非常粗糙,大部分是直接在網路層通過攔截器來做。 因為這樣不用侵入到業務程式碼。 但是,這樣做的弊端也很大,不夠靈活。 雖然像 okhttp 這樣的網路庫提供了對快取的支援,比如可以設定只使用快取,或者只使用網路,但這依然不夠靈活。

如果想精準控制快取,那就不得不自己在程式碼裡為每一個請求都加上快取的邏輯。 你會發現這就導致相同的快取邏輯寫了無數遍,這簡直是噩夢。

不過因為本庫有非同步支援,所以處理快取也變得簡單多了。 至於你想怎麼使用快取,交給你自己判斷吧,我們提供了一個策略介面,你只需要實現它即可。

頁面狀態管理

無資料頁面、 錯誤頁面、 載入中頁面、 下拉重新整理、 載入更多 在應用中很常見。

實現起來卻不方便了,常見的做法是 BaseActivity BaseFragment,但我表示不希望看見它們,曾今我覺得 base 是很好的邏輯抽象和封裝,後來發現自從有了 base,遷移和複用幾乎變成了 0。 base 使得它們緊緊的耦合在一起。 如果你不明白我在說什麼,我給你舉個例子:

我想從專案 A 中抽出一個頁面和邏輯差不多的 Activity,以便於在專案 B 中使用,這個時候最常見的就是 複製 XxxActivity.java 到 B 專案,然後後面你懂的。

但本庫對這幾種頁面狀態提供了高度的封裝,你不必再依賴於 Base。 不僅僅是 activity,甚至一個 button,你都可以讓他擁有如上的這幾種狀態。

請求過濾

不知道你是否煩惱過,產品跟你說,使用者可能狂按按鈕,讓你加個判斷,減少不必要的請求。 聽起來需求很簡單,防止重複點選就行,但可達鴨眉頭一皺,發現事情並不簡單。 一個按鈕防重複點選也就幾行程式碼,但幾十個幾百個按鈕呢? 你說可以抽出一個 BaseButton? 那點選的如果是個 text 或 fab 這樣的控制元件呢? 確實 base 可以解決很多重複程式碼,但相應的你要把對應的控制元件全部換成 base,工作量也很大。

本庫貼心的為大家提供了請求過濾器,預設就過濾重複的請求,雖然不是在 UI 上過濾,但同一個 task 的請求是不會重複執行的,這點可以放心。 如果你有其他過濾需求,還可以自定義實現一個過濾器。

重試支援

請求失敗重試也是很常見的需求,但實現並不簡單,基本有2種做法:

  • 如果在程式碼層面做,就需要在請求失敗的回撥裡重新發起一次,還要記錄次數,很是麻煩。
  • 如果在網路層做,你就得對網路層進行一次封裝,提供一個方法設定重試次數。 然而,這種方法弊端很大,不能和業務很好的聯絡。 因為網路層並不知道什麼時候應該重試,網路請求失敗就重試? 還是返回內容裡面標識不成功就重試呢?

本庫同樣提供了重試支援,因為有了非同步支援,重試對框架來說,就是一個迴圈,然而這個迴圈框架都幫你寫好了,你只需告訴框架重試次數和什麼時候應該重試就可以了。

動態屬性設定能力

動態換膚或樣式修改也是一個很常見的需求,然而為了實現這樣的需求,往往需要開發者在程式碼裡提前寫好根據配置修改的UI的程式碼。

本庫同樣提供了支援,你可以通過一個 json 來對 wiget 進行屬性修改。 所以換個面板或改個樣式都是分分鐘的事啦。

單頁面應用(測試)

搞前端的應該很清楚這是什麼,就是所有渲染都是在一個頁面上展示,頁面跳轉都是通過前端路由來控制。 對應到客戶端,就是所有 UI 都在一個 activity 中展示。

這樣做有什麼意義? 安卓外掛化最大的問題是四大元件需要提前在 manifest 中註冊,雖然目前有一些開源專案通過底層 hook 方式解決了這個問題,但是以後的安卓版本就不清楚會不會把這個限制了。 而且目前的外掛化都需要對資源進行合併,這就使得成功率下降。

如果是單頁面應用,動態下發位元組碼執行也變得有可能。 而且這樣成功率理論上接近 100%。 打算有時間嘗試一下。

我的意思就是像前端那樣具有隨時更新的能力,不知道會不會被封殺,逃。。。

快速開始

引入庫

【可選】 新增 java8 支援

android {
...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
...
}

新增 maven 倉庫

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

新增依賴

def support_version = '28.0.0'
def lifecycle_version = '1.1.1'

implementation 'com.github.ittianyu:relight:0.1.0'
implementation "com.android.support:appcompat-v7:$support_version"
implementation "com.android.support:design:$support_version"

// Support library depends on this lightweight import
implementation "android.arch.lifecycle:runtime:$lifecycle_version"

如果開啟了 java8

// alternately - if using Java8, use the following instead of compiler
implementation "android.arch.lifecycle:common-java8:$lifecycle_version"

混淆

使用了 xml 支援,必須加入混淆,未使用的可以不加。

-keep class * extends com.ittianyu.relight.widget.Widget {*;}

小結;

後面會寫一篇mvvp框架實戰,以及它的具體使用。也歡迎大家加入Android進階交流群;964557053。進群可免費領取一份最新技術大綱和Android進階資料。請備註csdn