Android應用架構演變


引言

總結了多年的移動開發經驗,特別是在Android端的積累,以前認為從移動端APP談架構,其實有點舉大旗,因為大部份項目都在做業務理,且往往不是很大,並沒有多復雜的數據處理或高並發(只針對個人而言);並且長期認為架構這個詞,用在Web端或者大型系統里比較好一點,好的架構好,意味系統更穩健、高效率, 更大體量。總之,有種大材小用的感覺,然而,隨着Android應用開發規模的擴大,客戶端業務邏輯也越來越復雜,已然不是簡單的數據展示了,APP也需要進行架構設計,拆分視圖和數據,解除模塊之間的耦合,提高模塊內部的聚合度。

APP 程序的結構

對於開發人員來講,項目層面決定了如何搭建整個項目及划分模塊,常用的Android程序結構:

  • UI層
    數據展示與管理
    用戶交互
    繪制
    Adapter
  • 業務邏輯層
    持久化數據(內存中,相當於全局數據)
    數據加式(數據層的數據有時候需要進行加工成UI層需要的數據)
    數據變化的通知機制
  • 數據層
    數據訪問(DB,文件,網絡等)
    緩存(圖片,文件等)
    配置文件(shared perference)

從程序結構看,架構在APP中無處不在,只是我們不太關注,最簡單的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)和控制器(Controller)。

維基百科:
MVC模式最早由Trygve Reenskaug在1978年提出,是施樂帕羅奧多研究中心(XeroxPARC)在20世紀80年代為程序語言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<weather>() {
            @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架構由三部分組成: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的簡稱,它由三個部分組成,也就是 Model、View 和 ViewModel,其中視圖模型(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沒有絕對好壞,在軟件編程過程中,也沒必要非此即彼,脫離實際項目比較這些模式優劣毫無意義,各種模式都有優點和缺點,沒有好壞之分。越高級的架構實現起來越復雜,需要更多的學習成本更多的人力,所以說技術選型關鍵是在你自己項目的特點,團隊的水平,資源的配備,開發時間的限制,這些才是重點!但是不少團隊本末倒置,把mvvm往自己的項目硬套。最重要的是讓軟件高內聚、低耦合、可維護、可擴展。

可以理解為移動端的架構思維“MVX”,即是說按這個規則的分工,我們不用費盡心思考慮狹義架構的分層問題了,就沿用Model-View-X來就可以(當然還可以自己加一些輔助的模塊層)。

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com