Androidアプリケーションアーキテクチャの進化


引用する
長年のモバイル開発経験を総括し、特にAndroid端末での蓄積は、以前はモバイル端末のアプリからアーキテクチャについて話していたが、実際には大きな旗を掲げていると考えられていた.また、アーキテクチャという言葉は、Webエンドや大規模なシステムで使われたほうがいいと考えられています.良いアーキテクチャは、システムがより安定し、効率的で、より大きなボリュームを意味しています.要するに、大材小用な感じがしますが、Androidアプリケーションの開発規模の拡大に伴い、クライアントビジネスロジックもますます複雑になり、もはや簡単なデータ展示ではありません.APPもアーキテクチャ設計を行い、ビューとデータを分割し、モジュール間の結合を解除し、モジュール内部の集約度を高める必要があります.
APPプログラムの構成
開発者にとって、プロジェクトレベルでは、プロジェクト全体と分割モジュールをどのように構築するか、よく使われるAndroidプログラム構造を決定しました.
  • UI層データ表示管理ユーザとの対話描画Adapter
  • ビジネスロジック層永続化データ(メモリ中、グローバルデータに相当)データ加算式(データ層のデータはUI層に加工する必要がある場合がある)データ変化の通知メカニズム
  • .
  • データ層データアクセス(DB、ファイル、ネットワーク等)キャッシュ(ピクチャ、ファイル等)プロファイル(shared perference)
  • プログラム構造から見ると、アーキテクチャはアプリにはどこにでもあるが、私たちはあまり注目していない.最も簡単なDemoは実はアーキテクチャ(通常MVC)に関連している.Androidの誕生から現在に至るまで、モバイル端末のアーキテクチャは何度も変更され、最初のMVCからMVP、冷たいドアのFlutter(RNからモバイル端末に導入された)からGoogleのAAC/MVVMに変更された.アーキテクチャの思想はずっと変わっているようだが、万変はそこから離れない.以下、MVC、MVP、MVVMといういくつかの主流のアーキテクチャ設計を順に紹介する.ここでは、これらのアーキテクチャのコードにどのような違いがあるかを深く分析することはできない.ただ、それらの設計構想を分析し、プロジェクトの中で適用するアーキテクチャを簡単に選択する.
    MVC
    非常に古典的なアーキテクチャで、どのプラットフォームでも、このようなアーキテクチャがあり、使いやすく、お得です.AndroidはXMLファイルを用いてページレイアウトを実現し、Javaを通じてActivityでビジネスロジックを開発する.この開発モデルは実際にMVCの考え方を採用し、ビューとコントローラを分離している.MVCモード(Model–view–controller)はソフトウェアエンジニアリングにおけるソフトウェアアーキテクチャモードであり、ソフトウェアシステムをモデル(Model)、ビュー(View)、コントローラの3つの基本部分に分ける.
    ウィキペディア:MVCモデルは、Trygve Reenskaugが1978年に提案したもので、シュラパロオド研究センター(XeroxPARC)が1980年代にプログラム言語Smalltalkのために発明したソフトウェアアーキテクチャである.MVCモードの目的は、後続のプログラムの修正と拡張を簡略化し、プログラムの一部の再利用を可能にする動的なプログラム設計を実現することである.それ以外に,このモードは複雑さの簡略化によりプログラム構造をより直感的にする.ソフトウェアシステムは,自身の基本部分を分離するとともに,各基本部分にあるべき機能を付与する.プロフェッショナルは、独自の専門知識でグループ化できます.
  • コントローラ(Controller)-要求を転送し、要求を処理します.
  • ビュー(View)-インタフェースデザイナーがグラフィックインタフェースの設計を行います.
  • モデル(Model)-プログラマがプログラムを記述する機能(実装アルゴリズムなど)、データベース専門家がデータ管理とデータベース設計を行う(具体的な機能を実現できる).

  • Androidプログラミングでは、Viewはxmlレイアウトファイル、Modelはエンティティモデル(ネットワーク、データベース、I/O)、ControllerはActivityビジネスロジック、データ処理、UI処理に対応しています.次の図に示します.
    //Model
    public interface WeatherModel {
        void getWeather(String cityNumber, OnWeatherListener listener);
    }
    ................
    
    public class WeatherModelImpl implements WeatherModel {
        @Override
        public void getWeather(String cityNumber, final OnWeatherListener listener) {
            /*     */
            VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html,
            Weather.class, new Response.Listener() {
                @Override
                public void onResponse(Weather weather) {
                    if (weather != null) {
                        listener.onSuccess(weather);
                    } else {
                        listener.onError();
                    }
                }
            }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    listener.onError();
                }
            });
        }
    }
    
    //Controllor(View) 
    public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {
        private WeatherModel weatherModel;
        private EditText cityNOInput;
        private TextView city;
        ...
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            weatherModel = new WeatherModelImpl();
            initView();
        }
    
        //   View
        private void initView() {
            cityNOInput = findView(R.id.et_city_no);
            city = findView(R.id.tv_city);
            ...
            findView(R.id.btn_go).setOnClickListener(this);
        }
    
        //    
        public void displayResult(Weather weather) {
            WeatherInfo weatherInfo = weather.getWeatherinfo();
            city.setText(weatherInfo.getCity());
            ...
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_go:
                    weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
                    break;
            }
        }
    
        @Override
        public void onSuccess(Weather weather) {
            displayResult(weather);
        }
    
        @Override
        public void onError() {
            Toast.makeText(this,         , Toast.LENGTH_SHORT).show();
        }
    
        private T findView(int id) {
            return (T) findViewById(id);
        }
    }

    例分析:
  • Activityのコントロールは、ビジネスとデータに関心を持ってこそ、自分がどのように展示されているかを知ることができます.
  • すべての論理はactivityにあります.

  • したがって,実際の開発過程では,純粋にViewとしての各XMLファイルの機能が弱く,Activityは基本的にViewとControllerの合体であり,ビューの表示を担当するとともに制御ロジックを加え,担う機能が多く,コード量が大きい.すべてのより適切な現在の従来の開発はView-modelモードであるべきであり,大部分はActivityの協調によって行われている.
    MVP
    MVPはMVCから移行し、MVPアーキテクチャは3つの部分から構成されている:Viewは表示を担当し、Presenterは論理処理を担当し、Modelはデータを提供する.Androidの開発はMVCからMVPに移行し、最も主要な変化はActivityの業務ロジックを担当するコードをPresenterに移動することであり、ActivityはMVPのViewとしてのみ機能し、インタフェースの初期化とインタフェースコントロールとPresenterの関連付けを担当する.
    例えば、MVCでは、ビジネスロジックが少し複雑なページでは、Activityのコードが千を超えるのは容易であるが、Activityは標準的なMVCモードのControllerではなく、アプリケーションのレイアウトをロードし、ユーザーインタフェースを初期化し、ユーザーからの操作要求を受け入れ、処理し、応答することが主な職責である.インタフェースとその論理の複雑さが高まるにつれて、Activityクラスの職責が増加し、膨大に肥大化し、分割を考えてしまうのは当然です.このように分割した後,Presenterは大量の論理操作を引き受け,Activityの肥大化を回避した.アーキテクチャ全体を次の図に示します.
    //Model 
    /**
     *       
     */
    public interface IUserBiz {
        public void login(String username, String password, OnLoginListener loginListener);
    }
    
    /**
     *       
     */
    public interface OnLoginListener {
        void loginSuccess(User user);
    
        void loginFailed();
    }
    
    /**
     *   Model   
     */
    public class UserBiz implements IUserBiz {
        @Override
        public void login(final String username, final String password, final OnLoginListener loginListener) {
            //         
            new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //      
                    if ("zhy".equals(username) && "123".equals(password)) {
                        User user = new User();
                        user.setUsername(username);
                        user.setPassword(password);
                        loginListener.loginSuccess(user);
                    } else {
                        loginListener.loginFailed();
                    }
                }
            }.start();
        }
    }
    
    //View
    public interface IUserLoginView {
        String getUserName();
    
        String getPassword();
    
        void clearUserName();
    
        void clearPassword();
    
        void showLoading();
    
        void hideLoading();
    
        void toMainActivity(User user);
    
        void showFailedError();
    }
    
    //  Activity        :
    public class UserLoginActivity extends ActionBarActivity implements IUserLoginView {
        private EditText mEtUsername, mEtPassword;
        private Button mBtnLogin, mBtnClear;
        private ProgressBar mPbLoading;
        private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_user_login);
            initViews();
        }
    
        private void initViews() {
            mEtUsername = (EditText) findViewById(R.id.id_et_username);
            mEtPassword = (EditText) findViewById(R.id.id_et_password);
            mBtnClear = (Button) findViewById(R.id.id_btn_clear);
            mBtnLogin = (Button) findViewById(R.id.id_btn_login);
            mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading);
            mBtnLogin.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mUserLoginPresenter.login();
                }
            });
            mBtnClear.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mUserLoginPresenter.clear();
                }
            });
        }
    
        @Override
        public String getUserName() {
            return mEtUsername.getText().toString();
        }
    
        @Override
        public String getPassword() {
            return mEtPassword.getText().toString();
        }
    
        @Override
        public void clearUserName() {
            mEtUsername.setText("");
        }
    
        @Override
        public void clearPassword() {
            mEtPassword.setText("");
        }
    
        @Override
        public void showLoading() {
            mPbLoading.setVisibility(View.VISIBLE);
        }
    
        @Override
        public void hideLoading() {
            mPbLoading.setVisibility(View.GONE);
        }
    
        @Override
        public void toMainActivity(User user) {
            Toast.makeText(this, user.getUsername() +
                    " login success , to MainActivity", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void showFailedError() {
            Toast.makeText(this,
                    "login failed", Toast.LENGTH_SHORT).show();
        }
    }
    
    //Presenter
    public class UserLoginPresenter {
        private IUserBiz userBiz;
        private IUserLoginView userLoginView;
        private Handler mHandler = new Handler();
    
        //Presenter      View Model    
        public UserLoginPresenter(IUserLoginView userLoginView) {
            this.userLoginView = userLoginView;
            this.userBiz = new UserBiz();
        }
    
        public void login() {
            userLoginView.showLoading();
            userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener() {
                @Override
                public void loginSuccess(final User user) {
                    //   UI    
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            userLoginView.toMainActivity(user);
                            userLoginView.hideLoading();
                        }
                    });
                }
    
                @Override
                public void loginFailed() {
                    //   UI    
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            userLoginView.showFailedError();
                            userLoginView.hideLoading();
                        }
                    });
                }
            });
        }
    
        public void clear() {
            userLoginView.clearUserName();
            userLoginView.clearPassword();
        }
    }

    説明:
  • Modelはコールバック方式でPresenterにデータを転送する.
  • View(Activity)は、ユーザの操作に応答し、Presenterが暴露した方法でデータを要求する責任を負う.
  • Presenterは、データを取得した後、View(Activity)露出法によりインタフェース制御を実現し、Modelによりデータを取得し、Modelはネットワーク、データベース、I/Oなどを含む.

  • MVPの採用の明らかな利点は、従来の開発モデルにおけるViewとModelの結合を回避し、コードの拡張性、コンポーネントの多重化能力、チームワークの効率、ユニットテストの利便性を向上させることである.しかし、いくつかの欠点があります.例えば、
  • ModelからPresenterへのデータ転送プロセスはコールバックを通過する必要がある.
  • View(Activity)はPresenterの参照を持つ必要があり、同時にPresenterもView(Activity)の参照を持つ必要があり、制御の複雑さを増した.
  • MVCではActivityのコードが肥大化し,MVPのPresenterに移行し,同様にPresenterのビジネスロジックが複雑な場合のコード肥大化をもたらした.

  • したがって、MVCからMVPは簡単に言えば、インタフェースを追加して結合を低減することである.
    MVVM
    MVVMアーキテクチャモデルはマイクロソフトが2005年に誕生したもので、MVVMはModel-View-ViewModelの略称で、3つの部分から構成されています.つまり、Model、View、View Modelです.ビューモデル(ViewModel)は実はPMモードの展示モデルで、MVVMではビューモデルと呼ばれています.実際の効果から見ると、ViewModelはViewのデータモデルとPresenterの結合であり、具体的な構造は下図の通りである.
    説明:
  • Model(モデル層)は、ネットワークおよびローカルデータベースを介してビューレイヤに必要なデータを取得する.
  • View(ビューレイヤ)はXMLファイルを用いてインタフェースの説明を行う.
  • ViewModel(ビュー-モデルレイヤ)は、ViewとModel間の通信を担当し、ビューとデータを分離します.

  • ViewとModelの間でAndroid Data Binding技術を通じて、ビューとデータの双方向バインドを実現します.ViewModelはModelの参照を持ち、Modelの方法でデータを要求する.データを取得すると、Callback(コールバック)方式でViewModelに戻り、ViewModelとViewの双方向バインドによりインタフェースがリアルタイムで更新される.また,インタフェース入力のデータが変化すると,双方向バインディング技術により,ViewModelにおけるデータがリアルタイムで更新され,データ収集の効率が向上する.
    MVVMアーキテクチャはPresenterをViewModelに改名し、基本的にMVPモードと完全に一致している.唯一の違いは、双方向バインド(data-binding)Viewの変動を採用し、自動的にViewModelに反映することである.逆に、MVVMを完全に採用するにはDataBindingなどの基礎構築を熟練しなければならないため、MVVM導入プロジェクトに困難をもたらしている.
    まとめ
    実は、MVC、MVPとMVVMは絶対的な良し悪しがなくて、ソフトウェアのプログラミングの過程の中で、それ以外に彼を必要としないで、実際のプロジェクトから離れてこれらのモードの優劣を比較して意味がなくて、各種のモードはすべて長所と短所があって、良し悪しの区別がありません.高度なアーキテクチャが実現すればするほど複雑になり、より多くの学習コストとより多くの人力が必要になるため、技術選択の鍵はあなた自身のプロジェクトの特徴、チームのレベル、資源の配置、開発時間の制限であり、これらこそ重点である.しかし、多くのチームは本末転倒し、mvmを自分のプロジェクトに強引にカバーしている.最も重要なのは、ソフトウェアを高集約、低結合、メンテナンス、拡張性にすることです.
    モバイル側のアーキテクチャ思考「MVX」と理解できるのは、このルールに従った分業であり、狭義のアーキテクチャの階層化の問題を考慮するのに苦労することなく、Model-View-Xに沿って行けばよい(もちろん、自分で補助的なモジュール層を加えることもできる).