Android アーキテクチャのアイデアと MVVM フレームワークのカプセル化

Android プロジェクトのアーキテクチャについても決まり文句です. インターネット上には Android アーキテクチャに関する記事が多すぎますが、Google の検索キーワードを介して、ホームページで人気のある記事のほとんどは、MVC、MVP、および MVVM の概念の紹介です。アーキテクチャ. 概念に関する記事 Android アーキテクチャを理解していない学生にとっては、あまり役に立たないかもしれません。この記事は、実は著者の社内での技術共有に端を発しており、若干の修正を加えて記事として公開したものです。この記事の内容は、MVC、MVP から MVVM への進化を含みます
が、理解を深めるために各アーキテクチャをコードで示し、最後に
Jetpack が提供するコンポーネントに基づいて MVVM アーキテクチャをカプセル化します。記事の内容は比較的基本的なもので、あいまいな知識はほとんどないため、Android アーキテクチャを理解したい学生にとっては非常に役立ちます。

1. Android プロジェクト アーキテクチャの進化

まず第一に、アーキテクチャにはプラットフォームがないことを理解する必要があります。MVC、MVP、または MVVM が
Android プラットフォームに固有のものでなくても、Android プラットフォームの開始が遅れたとしても、Android プロジェクトのアーキテクチャは多かれ少なかれフロントエンド アーキテクチャの実装を指します。

フロントエンドまたは Android 側のプロジェクトの場合、コードは UI 部分、ビジネス ロジック部分、データ制御部分の 3 つの部分に分けることができます。これら 3 つの循環の開始点は、ユーザー入力、つまり、ユーザーが UI を操作してデータを取得するために対応するビジネス ロジックを呼び出し、最終的にデータを UI インターフェイスにフィードバックすることです。下図。

ここに画像の説明を挿入

これらの 3 つの部分に従って、コードを 3 つの層、つまり MVC アーキテクチャの初期層に分割できます。しかし、プロジェクトの業務がますます複雑になるにつれて、MVC アーキテクチャの欠点が明らかになり、既存の業務をサポートできなくなります。したがって、このコンテキストでは、MVC の欠点を補うために MVP アーキテクチャが派生しました。さらにその後、Google は Android アーキテクチャの問題を具体的に解決するために、いくつかの Jetpack コンポーネントを正式に発表しました。これは、MVVM のメイン プッシュから MVI アーキテクチャの現在のメイン プッシュまでです。しかし、アーキテクチャがどのように進化しても、上記の 3 層ロジックから切り離すことはできませんが、新しいアーキテクチャは、既存のアーキテクチャに基づいて古いアーキテクチャの欠点を補い、プロジェクト コードを保守しやすくします。 .

以下のコンテンツでは、MVC から MVVM への Android プロジェクト アーキテクチャの進化について主に説明します。

  1. MVC の概要

上記の 3 層ロジックに基づいて、最初の Android プロジェクトは MVC アーキテクチャを採用しました。MVC はModel-View-Controller
の略です。簡単に言うと、MVC とは、ユーザーが View を操作し、View が Controller を呼び出して Model レイヤーを操作し、Model レイヤーが View レイヤーにデータを返して表示するというものです。

  • モデル層 (モデル) は、データベースおよびネットワーク層との通信、およびアプリケーション データの取得と保存を担当します。
  • ビュー レイヤー (ビュー) は、モデル レイヤーのデータを視覚化し、ユーザーとのやり取りを処理します。
  • コントロール層 (Controller) は、モデル層とビュー層の間の接続を確立するために使用され、主要なビジネス ロジックの処理を担当し、ユーザーの操作に応じてモデル層のデータを更新します。

MVCの構造図を下図に示します。

ここに画像の説明を挿入

Android プロジェクトの MVC アーキテクチャでは、Activity が View レイヤーと Controller レイヤーとして同時に機能するため、Android の MVC は次のような構造になります。

ここに画像の説明を挿入

上の図に基づいて、APP ログイン プロセスを例として、Android で MVC アーキテクチャのコードを実装できます。

(1) モデル層コードの実装

モデル層は、ログイン要求を処理し、サーバーからログイン関連のデータを取得し、成功後にインターフェースを更新するようにビュー層に通知するために使用されます。コードは以下のように表示されます:

public class LoginModel {
    
    
    public void login(String username, String password, LoginListener listener) {
    
    
        RetrofitManager.getApiService()
                .login(username, password)
                .enqueue(new Callback<User>() {
    
    
                    @Override
                    public void onResponse(Call<User> call, Response<User> response) {
    
    
                        listener.onSuccess(response.data);
                    }

                    @Override
                    public void onFailed(Exception e) {
    
    
                        listener.onFailed(e.getMessage());
                    }
                });
        
    }
}

上記のコードは、LoginListener を介して UI を更新するようビュー レイヤーに通知します。LoginListener は次のようなインターフェイスです。

    public interface LoginListener {
    
    
    void onSuccess(User data);

    void onFailed(String msg);
}

(2) Controller/View コードの実装

Android の MVC アーキテクチャの Controller レイヤーと View レイヤーは両方とも Activity を担当するため、Activity は LoginListener を実装して
UI を更新する必要があります。そのコードは次のとおりです。

public class MainActivity extends AppCompactActivity implements LoginListener {
    
    

    private LoginModel loginModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        model = new LoginModel();
        findViewbyId(R.id.btn_fetch_data).setOnClickListener(view -> {
    
    
                    String username = findViewById(R.id.et_username).getText().toString();
                    String password = findViewById(R.id.et_password).getText().toString();
                    loginModel.login(username, password, this);
                }
        );
    }

    @Override
    public void onSuccess(User data) {
    
    
        // Update UI
    }

    @Override
    public void onFailed(String msg) {
    
    
        // Update UI
    }
}

上記のコードからわかるように、Android の MVC コードは階層化について明確ではなく、Controller レイヤーと View レイヤーが混同されています。同時に、Android のコードを書くときは、モデル層を意図的に抽出するのではなく、
モデル層のコードを Activity に入れて実装するのが一般的です。

public class MainActivity extends AppCompactActivity implements LoginListener, View.OnClickListener {
    
    

    private LoginModel loginModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        model = new LoginModel();

        findViewbyId(R.id.btn_fetch_data).setOnClickListener(view -> {
    
    
                    showLoading();
                    String username = findViewById(R.id.et_username).getText().toString();
                    String password = findViewById(R.id.et_password).getText().toString();
                    RetrofitManager.getApiService()
                            .login(username, password)
                            .enqueue(new Callback<User>() {
    
    
                                @Override
                                public void onResponse(Call<User> call, Response<User> response) {
    
    
                                    onSuccess(response.data);
                                }

                                @Override
                                public void onFailed(Exception e) {
    
    
                                    listener.onFailed(e.getMessage());
                                }
                            });
                }
        );
    }

    @Override
    public void onSuccess(User data) {
    
    
        dismissLoading();
        // Update UI
    }

    @Override
    public void onFailed(String msg) {
    
    
        dismissLoading();
        // Update UI
    }

    public void showLoading() {
    
    
        // ...
    }

    public void dismissLoading() {
    
    
        // ...
    }
}

このようなコードの階層化はより曖昧になり、コードの構造的階層もより混乱させます。一部のビジネスをより複雑にする一部のページのアクティビティ内のコードは、数千行に及ぶ場合があります。Android プロジェクトの MVC アーキテクチャには多くの問題があることがわかります
概要は主に以下の点です。

  • アクティビティ/フラグメントは、ビュー レイヤーとコントローラー レイヤーの作業を同時に引き受けます。これは、単一の責任に違反します。
  • モデル層とビュー層は結合され、相互に依存しています。
  • 開発中はレイヤリングに注意が払われておらず、モデル レイヤのコードも Activity/Fragment に詰め込まれているため、コード レイヤがさらにわかりにくくなっています。
  1. MVP の概要

上記の MVC アーキテクチャに存在する問題については、MVC に基づいて最適化および解決できます。
つまり、コントロール層のロジックがアクティビティから取り除かれ、モデル層とビュー層の間の結合がブロックされます.モデル層はビューと直接通信しませんが、モデルが制御層に通知するときにデータが変更され、コントロール レイヤーがビュー レイヤーに通知し、インターフェイスを更新する、これが MVP のアーキテクチャ上の考え方です。MVP はModel-View-Presenterの略です。MVPとは簡単に言うと、
MVCのControllerをPresenterに変更する、つまりロジック層のコードをActivityからPresenterに抜き出すことで、コードレベルがより明確になるということです。ビュー
層、およびコードはより分離されています。

  • モデル層 (モデル) は MVC のそれと一貫性があります. データベースとネットワーク層との通信も担当します. アプリケーションデータの取得と保存も担当します. 違いは, モデル層がビュー層と直接通信しなくなったことです.プレゼンター層と通信します。
  • ビュー レイヤー (ビュー) は、モデル レイヤーのデータを視覚化し、同時にプレゼンター レイヤーと対話する役割を果たします。MVC と比較すると、MVP のビュー レイヤーとモデル レイヤーは結合されていません。
  • コントロール レイヤー (プレゼンター) は、主にビジネス ロジックの処理、モデル レイヤーでのデータ変更の監視、およびビュー レイヤーの呼び出しによる UI の更新を担当します。

MVPの構造図を下図に示します。
ここに画像の説明を挿入

上の図からわかるように、View は Presenter レイヤーと直接通信し、View レイヤーがユーザーの操作を受け取ると、Presenter
レイヤーを呼び出してビジネス ロジックを処理します。次に、Presenter レイヤーは Model を介してデータを取得し、Model レイヤーはデータ取得後に最新のデータを Presenter に返します。
プレゼンター レイヤーはビュー レイヤーへの参照も保持するため、データはView表示のためにレイヤーに渡されます。

以下でもログインを例として使用し、コードによる MVP アーキテクチャの実装を示します。

(1) モデル層コードの実装

MVP のモデル レイヤーは、MVC のモデル レイヤーと一致しています。コードは次のとおりです。

public class LoginModel {
    
    
    public void login(String username, String password, LoginListener listener) {
    
    
        RetrofitManager.getApiService()
                .login(username, password)
                .enqueue(new Callback<User>() {
    
    
                    @Override
                    public void onResponse(Call<User> call, Response<User> response) {
    
    
                        listener.onSuccess(response.data);
                    }

                    @Override
                    public void onFailed(Exception e) {
    
    
                        listener.onFailed(e.getMessage());
                    }
                });
    }
}

ログイン インターフェイスが結果を返した後、結果は LoginListener を介してコールバックされます。

public interface LoginListener {
    
    
    void onSuccess(User user);

    void onFailed(String errorInfo);
}

(2) プレゼンター層コードの実装

プレゼンターは UI を更新するためにビュー レイヤーに通知する必要があるため、ビューを保持する必要があり、ここでビュー インターフェイスを抽象化できます。次のように:

public interface ILoginView {
    
    
    void showLoading();

    void dismissLoading();

    void loginSuccess(User data);

    void loginFailer(String errorMsg);
}

さらに、プレゼンターはモデル層とも通信する必要があるため、プレゼンター層もモデル層を保持します. ユーザーがログイン操作をトリガーした後、プレゼンターのログイン ロジックが呼び出されます. プレゼンターは、モデル. ログインが成功すると、結果がビュー レイヤーにフィードバックされます. インターフェイスを更新します. コードは次のように実装されます。

public class LoginPresenter {
    
    

    // Model 层 
    private LoginModel model;

    // View 层 
    private ILoginView view;

    public LoginPresenter(LoginView view) {
    
    
        this.model = new LoginModel();
        this.view = view;
    }

    public void login(String username, String password) {
    
    
        view.showLoading();
        model.login(username, password, new LoginListener() {
    
    
            @Override
            public void onSuccess(User user) {
    
    
                view.loginSuccess(user);
                view.dismissLoading();
            }

            @Override
            public void onFailed(String msg) {
    
    
                view.loginFailer(msg);
                view.dismissLoading();
            }
        });
    }

    @Override
    public void onDestory() {
    
    
        // recycle instance.
    }
}

(3) ビューレイヤーコードの実装

ビュー レイヤーとしてのアクティビティは、上記の ILoginView インターフェイスを実装する必要があり、ビュー レイヤーはビジネス ロジックを処理するためにプレゼンターを保持する必要があります。ビュー層のコード実装は非常に単純になります。

public class MainActivity extends AppCompatActivity implements ILoginView {
    
    

    private LoginPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        presenter = new LoginPresenter(this);
        findViewById(R.id.button).setOnClickListener(v -> {
    
    
            String username = findViewById(R.id.et_username).getText().toString();
            String password = findViewById(R.id.et_password).getText().toString();
            presenter.login(username, password);
        });
    }

    @Override
    public void loginSuccess(User user) {
    
    
        // Update UI.
    }

    @Override
    public void loginFailer(String msg) {
    
    
        // Update UI.
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        presenter.onDestroy();
    }

    @Override
    public void showLoading() {
    
    

    }

    @Override
    public void dismissLoading() {
    
    
    }
}

MVP の本質は、依存関係逆転の原則を実現するためのインターフェース指向プログラミングです。MVP アーキテクチャは、階層化の点で MVC よりも明確であり、モデル層とビュー層を分離していることがわかります。ただし、MVP には次のような欠点もあります。

  • プロジェクトの複雑さを増すために、多数の IView および IPresenter インターフェイス実装クラスが導入されています。
  • View レイヤーと Presenter は互いに保持され、カップリングがあります。
  1. MVVM の紹介

MVC と比較すると、MVP には間違いなく多くの利点がありますが、無敵というわけではありません.さらに、MVP は Google が公式に推奨するアーキテクチャではないため、プログラマーが Android プロジェクトのアーキテクチャを最適化するためのワイルドな方法と見なすことしかできません. 2015 年から、Google は公式に Android プロジェクト アーキテクチャのガイドを開始し、開発者がプロ​​ジェクト アーキテクチャを最適化するのに役立つ DataBinding コンポーネントと一連の Jetpack コンポーネントをリリースしました。Google が公式に提供する一連のガイダンス フレームワークが MVVM です。MVVM は **Model-View-ViewModel** の略です
このアーキテクチャは、MVP アーキテクチャの問題をある程度解決します。公式のガイド フレームワークは最近 MVVM から MVI に変更されましたが、MVVM は依然として Android プロジェクトの主流のフレームワークです。

  • モデル レイヤー (モデル) は、MVP のモデル レイヤーと一致しており、データベースおよびネットワーク レイヤーとの通信、データの取得と保存を担当します。MVP との違いは、モデル レイヤーがビジネス ロジック レイヤーにデータ変更をコールバックではなく、オブザーバー モードで通知することです。
  • View (ビュー) は、Model レイヤーのデータを視覚化し、同時に ViewModel レイヤーと対話する役割を果たします。
  • ビュー モデル (ViewModel) は、主にビジネス ロジックの処理を担当し、モデル レイヤーとビュー レイヤーを同時に操作します。MVP の Presenter と比較すると、ViewModel は View に依存しなくなり、デカップリングがより徹底されます。

MVVM アーキテクチャの構造図は次のとおりです。

ここに画像の説明を挿入

MVVM アーキテクチャの本質はデータ駆動型であり、その最大の特徴は一方向依存です。MVVM アーキテクチャは、オブザーバー モードを通じて ViewModel を View から切り離し、View が ViewModel に依存し、ViewModel が Model に依存するという一方向の依存関係を実現します。

次に、オブザーバー モードを使用して MVVM アーキテクチャ コードを実装する例として引き続きログインを取り上げます。

(1) オブザーバーモード

MVVM アーキテクチャは ViewModel を View から分離する必要があるためです。したがって、ここで観察者パターンを使用して達成することができます。以下は、Observer パターンを実装するコードです。

  • オブザーバー インターフェイスを実装する
public interface Observer<T> {
    
    
    // 成功的回调
    void onSuccess(T data);

    // 失败的回调
    void onFailed(String msg);
}
  • 抽象的な観察者
public abstract class Observable<T> {
    
    
    // 注册观察者
    void register(Observer observer);

    // 取消注册
    void unregister(Observer observer);

    // 数据改变(设置成功数据)
    protected void setValue(T data) {
    
    

    }

    // 失败,设置失败原因
    void onFailed(String msg);
}

ここで、オブザーバブル Observable の setValue メソッドが保護に設定されていることに注意する必要があります。これは、View レイヤーが Observable インスタンスを取得した場合、コードのセキュリティを確保するために setValue を呼び出してデータを設定できないことを意味します。

  • 観察された具体的な実装
public class LoginObservable implements Observable<User> {
    
    

    private final List<Observer<User>> list = new ArrayList<>();

    private User user;

    @Override
    public void register(Observer<User> observer) {
    
    
        list.add(observer);
    }

    @Override
    public void unregister(Observer<User> observer) {
    
    
        liset.remove(observer);
    }

    @Override
    public void setValue(User user) {
    
    
        this.user = user;
        for (Observer observer : list) {
    
    
            observer.onSuccess(user);
        }
    }

    @Override
    public void onFailed(String msg) {
    
    
        for (Observer observer : list) {
    
    
            observer.onFailed(msg);
        }
    }
}

オブザーバーの実装クラスでは、setValue メソッドが public に設定されています。これは、LoginObservable の場合、setValue を介してデータを設定できることを意味します。

(2) モデル層コードの実装

モデル層のコードは、次のように MVP/MVC の実装と基本的に一致しています。

public class LoginModel {
    
    
    public void login(String username, String password, LoginObservable observable) {
    
    
        RetrofitManager.getApiService()
                .login(username, password)
                .enqueue(new Callback<User>() {
    
    
                    @Override
                    public void onResponse(Call<User> call, Response<User> response) {
    
    
                        observable.setValue(response.data);
                    }

                    @Override
                    public void onFailed(Exception e) {
    
    
                        observable.onFailed(e.getMessage());
                    }
                });
    }
}

LoginModel の login メソッドは LoginObservable の型を受け取るため、ここで setValue を使用してデータを設定できることに注意してください。

(3) ViewModel レイヤーの実装

ViewModel レイヤーは Model レイヤーを保持する必要があり、ViewModel レイヤーは LoginObservable を保持し、View レイヤーが使用する getObservable メソッドを開きます。コードは以下のように表示されます:

public class LoginViewModel {
    
    

    private LoginObservable observable;

    private LoginModel loginModel;

    public LoginViewModel() {
    
    
        loginModel = new LoginModel();
        observable = new LoginObservable();
    }

    public void login(String username, String password) {
    
    
        loginModel.login(username, password, observable);
    }

    // 这里返回值是父类Observable,即View层得到的Observable无法调用setValue
    public Observable getObservable() {
    
    
        return observable;
    }
}

ここで、getObservable メソッドの戻り値は Observable であることに注意してください。つまり、View レイヤーは register メソッドを呼び出してデータの変更を監視することしかできませんが、setValue を介してデータを設定することはできません。

(4) ビューレイヤーコードの実装

ビュー レイヤーはビューモデルを保持し、ユーザーはログイン イベントをトリガーし、それをビュー レイヤーを介して処理するためにモデルに渡します。モデル レイヤーは、ログイン後にビューモデルのオブザーバーにデータを渡します。したがって、View レイヤーは ViewModel レイヤーのオブザーバーを取得し、データの変化を観察した後に UI を更新するだけで済みます。コードは以下のように表示されます:

public class MainActivity extends AppCompatActivity implements Observer<User> {
    
    

    private LoginViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewModel = new LoginViewModel();
        viewModel.getObservable().register(this);
        findViewById(R.id.button).setOnClickListener(v -> {
    
    
            String username = findViewById(R.id.et_username).getText().toString();
            String password = findViewById(R.id.et_password).getText().toString();
            viewModel.login(username, password);
        });
    }

    @Override
    public void onDestroy() {
    
    
        super.onDestory();
        viewModel.getObservable().unregister(this);
    }

    @Override
    public void onSuccess(User user) {
    
    
        // fetch data success,update UI.
    }

    @Override
    public void onFailed(String message) {
    
    
        // fetch data failed,update UI.
    }
}


オブザーバー モードにより、View->ViewModel->Model の一方向の依存関係が実現されていることがわかります。MVP と比較して、MVVM デカップリングはより純粋です。ただし、上記のオブザーバー モードは自分たちで実装したものであり、プロジェクトで直接使用するのは安全ではありません。開発者が MVVM アーキテクチャを実装するのに役立つ一連の Jetpack コンポーネントが Google から提供されていることは前述しました。次に、Jetpack によって実装された MVVM アーキテクチャを見てみましょう。

2. Jetpack コンポーネントを使用して MVVM アーキテクチャをカプセル化する

これをご覧になった方は、プロジェクトのアーキテクチャについてある程度理解されていると思います。ここで、アーキテクチャはアイデアであり、それを実現するために使用するツールとは何の関係もないことを強調する必要があります。前の章と同じように、MVVM を自分で作成したオブザーバー モードを介して実装しました。このアーキテクチャのアイデアに従う限り、MVVM はこのアーキテクチャに属します。Google が正式に発表したこれらのコンポーネントは、MVVM をより効率的に実現するのに役立ちます。

1. Jetpack MVVM の紹介

MVVM は、Google が公式に推奨するフレームワークです。この目的のために、Google は MVVM を実装するための一連のツールを提供しています。これらのツールは、LiveData、ViewModel、
DataBinding などの Jetpack コンポーネントに含まれています。以下は、Jetpack コンポーネントによって実装された公式の MVVM アーキテクチャ図です。

ここに画像の説明を挿入

この図は、Jetpack コンポーネントがここに組み込まれていることを除いて、前の章の MVVM 構造図と一致しています。図の矢印はすべて単方向であり、View レイヤーは ViewModel レイヤーを指していることがわかります。これは、View レイヤーが ViewModel レイヤーへの参照を保持しているが、ViewModel レイヤーが View レイヤーを保持していないことを示しています。ViewModel レイヤーは Repository レイヤーを保持しますが、Repository レイヤーは ViewModel を保持しません。この図と MVVM の対応は次のとおりです。

  • View (ビュー) は、図のアクティビティ/フラグメントに対応し、レイアウト ファイルとインターフェイス関連のものを含みます。
  • ビューモデル(ViewModel)は、図のJetpack ViewModelとLiveDataに対応し
    、UIに関するデータを保持するために使用され、画面回転後にデータが失われないことを保証できます。さらに、ビュー レイヤーを呼び出すためのインターフェイスと、リポジトリと通信するためのインターフェイスも提供します。
  • モデル レイヤー (モデル) は、図のリポジトリに対応し、ローカル データとサーバー データが含まれます。

2. MVVM フレームワーク コードのカプセル化

Jetpack MVVM を理解した後、通常はより効率的な開発のために基本的なパッケージ化を行います。たとえば、ネットワーク リクエストとデータベースを組み合わせてデータを取得する方法、表示ロジックのロードなど。この章の内容は、読者がJetpack ViewModel、LiveData、およびその他のコンポーネントについて
ある程度理解していることを前提としています。

2.1 ネットワーク層のカプセル化

サーバーから返されたデータをカプセル化するために、一般的な Response 基本クラスを抽出できます。次のように:

class BaseResponse<T>(
    var errorCode: Int = -1,
    var errorMsg: String? = null,
    var data: T? = null,
    var dataState: DataState? = null,
    var exception: Throwable? = null,
) {
    
    
    companion object {
    
    
        const val ERROR_CODE_SUCCESS = 0
    }
    
    val success: Boolean
        get() = errorCode == ERROR_CODE_SUCCESS
}

/**
 * 网络请求状态
 */
enum class DataState {
    
    
    STATE_LOADING, // 开始请求
    STATE_SUCCESS, // 服务器请求成功
    STATE_EMPTY, // 服务器返回数据为null
    STATE_FAILED, // 接口请求成功但是服务器返回error
    STATE_ERROR, // 请求失败
    STATE_FINISH, // 请求结束
}

通常、ネットワーク要求に応じた異常事態に一元的に対応いたします。ここでは Observer でそれを行うことができます。LiveData の Observer をカプセル化し、Response と例外の統一処理を実現します。

abstract class ResponseObserver<T> : Observer<BaseResponse<T>> {
    
    
    
    final override fun onChanged(response: BaseResponse<T>?) {
    
    
        response?.let {
    
    
            when (response.dataState) {
    
    
                DataState.STATE_SUCCESS, DataState.STATE_EMPTY -> {
    
    
                    onSuccess(response.data)
                }
                DataState.STATE_FAILED -> {
    
    
                    onFailure(response.errorMsg, response.errorCode)
                }
                DataState.STATE_ERROR -> {
    
    
                    onException(response.exception)
                }
                else -> {
    
    
                }
            }
        }
    }
    
    private fun onException(exception: Throwable?) {
    
    
        ToastUtils.showToast(exception.toString())
    }
    
    /**
     * 请求成功
     *  @param  data 请求数据
     */
    abstract fun onSuccess(data: T?)
    
    /**
     * 请求失败
     *  @param  errorCode 错误码
     *  @param  errorMsg 错误信息
     */
    open fun onFailure(errorMsg: String?, errorCode: Int) {
    
    
        ToastUtils.showToast("Login Failed,errorCode:$errorCode,errorMsg:$errorMsg")
    }
}

RetrofitCreator をカプセル化して API サービスを作成します。

object RetrofitCreator {
    
    
    
    private val mOkClient = OkHttpClient.Builder()
        .callTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .connectTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .readTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .writeTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .retryOnConnectionFailure(true)
        .followRedirects(false)
        .addInterceptor(HttpHeaderInterceptor())
        .addInterceptor(LogInterceptor())
        .build()
    
    private fun getRetrofitBuilder(baseUrl: String): Retrofit.Builder {
    
    
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(mOkClient)
            .addConverterFactory(GsonConverterFactory.create())
    }
    
    /**
     * Create Api service
     *  @param  cls Api Service
     *  @param  baseUrl Base Url
     */
    fun <T> getApiService(cls: Class<T>, baseUrl: String): T {
    
    
        val retrofit = getRetrofitBuilder(
            baseUrl
        ).build()
        return retrofit.create(cls)
    }
}

2.2 モデル層のカプセル化

公式コードの Model レイヤーは Repository を介して実装されています. テンプレート コードを削減するために、BaseRepository をカプセル化してネットワーク リクエストを処理できます。

open class BaseRepository {
    
    
    
    // Loading 状态的 LiveData
    val loadingStateLiveData: MutableLiveData<LoadingState> by lazy {
    
    
        MutableLiveData<LoadingState>()
    }
    
    /**
     * 发起请求
     *  @param  block 真正执行的函数回调
     *  @param responseLiveData 观察请求结果的LiveData
     */
    suspend fun <T : Any> executeRequest(
        block: suspend () -> BaseResponse<T>,
        responseLiveData: ResponseMutableLiveData<T>,
        showLoading: Boolean = true,
        loadingMsg: String? = null,
    ) {
    
    
        var response = BaseResponse<T>()
        try {
    
    
            if (showLoading) {
    
    
                loadingStateLiveData.postValue(LoadingState(loadingMsg, DataState.STATE_LOADING))
            }
            response = block.invoke()
            if (response.errorCode == BaseResponse.ERROR_CODE_SUCCESS) {
    
    
                if (isEmptyData(response.data)) {
    
    
                    response.dataState = DataState.STATE_EMPTY
                } else {
    
    
                    response.dataState = DataState.STATE_SUCCESS
                }
            } else {
    
    
                response.dataState = DataState.STATE_FAILED
// throw ServerResponseException(response.errorCode, response.errorMsg)
            }
        } catch (e: Exception) {
    
    
            response.dataState = DataState.STATE_ERROR
            response.exception = e
        } finally {
    
    
            responseLiveData.postValue(response)
            if (showLoading) {
    
    
                loadingStateLiveData.postValue(LoadingState(loadingMsg, DataState.STATE_FINISH))
            }
        }
    }
    
    private fun <T> isEmptyData(data: T?): Boolean {
    
    
        return data == null || data is List<*> && (data as List<*>).isEmpty()
    }
}

2.3 ViewModel レイヤーのカプセル化

カプセル化がない場合、ViewModel レイヤーにもテンプレート コードが含まれます。ここでは BaseViewModel で共通コードを完成させます。BaseViewModel はリポジトリを保持する必要があります。

abstract class BaseViewModel<T : BaseRepository> : ViewModel() {
    
    
    val repository: T by lazy {
    
    
        createRepository()
    }
    
    val loadingDataState: LiveData<LoadingState> by lazy {
    
    
        repository.loadingStateLiveData
    }
    
    /**
     * 创建Repository
     */
    @Suppress("UNCHECKED_CAST")
    open fun createRepository(): T {
    
    
        val baseRepository = findActualGenericsClass<T>(BaseRepository::class.java)
            ?: throw NullPointerException("Can not find a BaseRepository Generics in ${
      
      javaClass.simpleName}")
        return baseRepository.newInstance()
    }
}

2.4 ビュー レイヤーのカプセル化

ViewModel は BaseActivity/BaseFragment に保持され、LoadingState に応じて Loading の表示と非表示が行われます。

abstract class BaseActivity<VM : BaseViewModel<*>, VB : ViewBinding> : AppCompatActivity() {
    
    
    
    protected val viewModel by lazy {
    
    
        createViewModel()
    }
    
    protected val binding by lazy {
    
    
        createViewBinding()
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        viewModel.loadingDataState.observe(this) {
    
    
            when (it.state) {
    
    
                DataState.STATE_LOADING ->
                    showLoading(it.msg)
                else ->
                    dismissLoading()
            }
        }
        onActivityCreated(savedInstanceState)
    }
    
    /**
     * Activity content view created.
     *  @param  savedInstanceState savedInstanceState
     */
    abstract fun onActivityCreated(savedInstanceState: Bundle?)
    
    /**
     * 显示Loading
     */
    open fun showLoading(msg: String? = null) {
    
    
        ToastUtils.showToast("showLoading")
    }
    
    /**
     * 隐藏Loading
     */
    open fun dismissLoading() {
    
    
        ToastUtils.showToast("hideLoading")
    }
    
    /**
     * Create ViewBinding
     */
    @Suppress("UNCHECKED_CAST")
    open fun createViewBinding(): VB {
    
    
        val actualGenericsClass = findActualGenericsClass<VB>(ViewBinding::class.java)
            ?: throw NullPointerException("Can not find a ViewBinding Generics in ${
      
      javaClass.simpleName}")
        try {
    
    
            val inflate = actualGenericsClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
            return inflate.invoke(null, layoutInflater) as VB
        } catch (e: NoSuchMethodException) {
    
    
            e.printStackTrace()
        } catch (e: IllegalAccessException) {
    
    
            e.printStackTrace()
        } catch (e: InvocationTargetException) {
    
    
            e.printStackTrace()
        }
    }
    
    /**
     * Create ViewModel
     *  @return  ViewModel
     */
    @Suppress("UNCHECKED_CAST")
    open fun createViewModel(): VM {
    
    
        val actualGenericsClass = findActualGenericsClass<VM>(BaseViewModel::class.java)
            ?: throw NullPointerException("Can not find a ViewModel Generics in ${
      
      javaClass.simpleName}")
        if (Modifier.isAbstract(actualGenericsClass.modifiers)) {
    
    
            throw IllegalStateException("$actualGenericsClass is an abstract class,abstract ViewModel class can not create a instance!")
        }
        // 判断如果是 BaseAndroidViewModel,则使用 AppViewModelFactory 来生成
        if (BaseAndroidViewModel::class.java.isAssignableFrom(actualGenericsClass)) {
    
    
            return ViewModelProvider(this, AppViewModelFactory(application))[actualGenericsClass]
        }
        return ViewModelProvider(this)[actualGenericsClass]
    }
}

ViewModel を作成するプロセスは、サブクラスのジェネリック型に従って BaseActivity で自動的に生成されます。これはリフレクションを使用して行われます。パフォーマンスに影響すると思われる場合は、サブクラス内の関数を書き換えてcreateViewModelViewModel を自分で生成できます

さらに、アプリケーションで ViewModel を使用する必要がある場合は、BaseAndroidViewModel を継承でき、その実装は AndroidViewModel を参照します。

abstract class BaseAndroidViewModel<T : BaseRepository>(@field:SuppressLint("StaticFieldLeak") var application: Application) : BaseViewModel<T>()

ここで BaseAndroidViewModel を作成するには、AppViewModelFactory を使用する必要があります。

class AppViewModelFactory(private val application: Application) : ViewModelProvider.NewInstanceFactory() {
    
    
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
    
    
        return if (BaseAndroidViewModel::class.java.isAssignableFrom(modelClass)) {
    
    
            try {
    
    
                modelClass.getConstructor(Application::class.java).newInstance(application)
            } catch (e: NoSuchMethodException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            } catch (e: IllegalAccessException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            } catch (e: InstantiationException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            } catch (e: InvocationTargetException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            }
        } else super.create(modelClass)
    }
}

LoginViewModel が BaseAndroidViewModel を実装する場合、ViewModelProvider を使用して LoginViewModel を作成する場合
、Jetpack ViewModel ライブラリの AndroidViewModelFactory の代わりに AppViewModelFactory パラメータを渡す必要があります。

ViewModelProvider(this, AppViewModelFactory(application))[LoginViewModel::class.java]

3. MVVMカプセル化後の適用例

上記のカプセル化が完了したら、ログイン ロジックを実装する方法を見てみましょう。

3.1 リポジトリの実装

class LoginRepository : BaseRepository() {
    
    
    /**
     * Login
     *  @param  username 用户名
     *  @param  password 密码
     */
    suspend fun login(username: String, password: String, responseLiveData: ResponseMutableLiveData<LoginResponse>, showLoading: Boolean = true) {
    
    
        executeRequest({
    
    
            RetrofitManager.apiService.login(username, password)
        }, responseLiveData, showLoading)
    }
}

RetrofitManager は、管理 API サービスを作成するために使用されます。

object RetrofitManager {
    
    
    private const val BASE_URL = "https://www.wanandroid.com/"
    
    val apiService: ApiService by lazy {
    
    
        RetrofitCreator.createApiService(
            ApiService::class.java, BASE_URL
        )
    }
}

3.2 ViewModel インスタンス

class LoginViewModel : BaseViewModel<LoginRepository>() {
    
    
    // 提供给Model层设置数据
    private val _loginLiveData: ResponseMutableLiveData<LoginResponse> = ResponseMutableLiveData()
    
    // 提供给View层观察数据
    val loginLiveData: ResponseLiveData<LoginResponse> = _loginLiveData
    
    /**
     * Login
     *  @param  username 用户名
     *  @param  password 密码
     */
    fun login(username: String, password: String) {
    
    
        viewModelScope.launch {
    
    
            repository.login(username, password, _loginLiveData)
        }
    }
}

3.3 アクティビティ/フラグメント インスタンス

class LoginActivity : BaseActivity<LoginViewModel, ActivityBizAMainBinding>() {
    
    
    
    override fun onActivityCreated(savedInstanceState: Bundle?) {
    
    
        binding.tvLogin.setOnClickListener {
    
    
            viewModel.login("110120", "123456")
        }
        
        viewModel.loginLiveData.observe(this, object : ResponseObserver<LoginResponse>() {
    
    
            override fun onSuccess(data: LoginResponse?) {
    
    
                // Update UI
            }
        })
    }
}

ここで注意する必要があるのは、開発中に View レイヤーの LiveData を介して setValue/postValue を使用すると、間違った UI 更新の問題が発生するのを防ぐために、Activity で不変の LiveData を使用する必要があることです
そのため、次のように LiveData にいくつかの変更が加えられました。

abstract class ResponseLiveData<T> : LiveData<BaseResponse<T>> {
    
    
    /**
     * Creates a MutableLiveData initialized with the given `value`.
     *
     *  @param  value initial value
     */
    constructor(value: BaseResponse<T>?) : super(value)
    
    /**
     * Creates a MutableLiveData with no value assigned to it.
     */
    constructor() : super()
    
    /**
     * Adds the given observer to the observers list within the lifespan of the given owner.
     * The events are dispatched on the main thread. If LiveData already has data set, it
     * will be delivered to the observer.
     */
    fun observe(owner: LifecycleOwner, observer: ResponseObserver<T>) {
    
    
        super.observe(owner, observer)
    }
}

ResponseLiveData は LiveData を継承するため、ResponseLiveData は不変です。さらに、変更可能な
LiveData である ResponseMutableLiveData が定義されています。コードは次のとおりです。

class ResponseMutableLiveData<T> : ResponseLiveData<T> {
    
    
    
    /**
     * Creates a MutableLiveData initialized with the given `value`.
     *
     *  @param  value initial value
     */
    constructor(value: BaseResponse<T>?) : super(value)
    
    /**
     * Creates a MutableLiveData with no value assigned to it.
     */
    constructor() : super()
    
    public override fun postValue(value: BaseResponse<T>?) {
    
    
        super.postValue(value)
    }
    
    public override fun setValue(value: BaseResponse<T>?) {
    
    
        super.setValue(value)
    }
}

ここまでで、Jetpack MVVM のカプセル化と使用は終了しました。

要約する

この記事では、MVC、MVP から MVVM への Android プロジェクト アーキテクチャの進化について詳しく説明し、3 つのアーキテクチャの詳細な実装例を示します。
同時に、現在の主流の Jetpack MVVM アーキテクチャをカプセル化します。もちろん、アーキテクチャに対する理解は全員が同じであるとは限らないため、すべての記事に読者の理解との矛盾が生じることは避けられません。

おすすめ

転載: blog.csdn.net/qq_20521573/article/details/127038987