Android mvp 架構的自述

最近發現再也無法忍受越來越臃腫的Activity代碼,越來越來混亂的Activity層的代碼,投入到了MVP的懷抱。目前來看MVP的架構還是很適合Android的,在這裏記錄一下一點心得,希望都給想用MVP的人一點幫助。

老的MVC架構

剛開始接觸Android的時候會覺得Android的整個代碼架構就是一個MVC。

  • M : 業務層和模型層,相當與javabean和我們的業務請求代碼
  • V : 視圖層,對應Android的layout.xml佈局文件
  • C : 控制層,對應於Activity中對於UI 的各種操作

看起來MVC架構很清晰,但是實際的開發中,請求的業務代碼往往被丟到了Activity裏面,大家都知道layout.xml的佈局文件只能提供默認的UI設置,所以開發中視圖層的變化也被丟到了Activity裏面,再加上Activity本身承擔着控制層的責任。所以Activity達成了MVC集合的成就,最終我們的Activity就變得越來越難看,從幾百行變成了幾千行。維護的成本也越來越高

新的MVP架構

  • M : 還是業務層和模型層
  • V : 視圖層的責任由Activity來擔當
  • P : 新成員Prensenter 用來代理 C(control) 控制層

MVP與MVC最大的不同,其實是Activity職責的變化,由原來的C (控制層) 變成了 V(視圖層),不再管控制層的問題,只管如何去顯示。控制層的角色就由我們的新人 Presenter來擔當,這種架構就解決了Activity過度耦合控制層和視圖層的問題。

一個demo

理念終歸要用代碼來實現,來看一個很典型的例子,打開一個有列表的Activity界面,請求數據然後刷新界面的過程。先來看看工程的架構 
這裏寫圖片描述 
我分了MVC 和 MVP2種寫法的目錄,MVP與MVC不同就在於多了2個結構presenter和view。 
這裏業務層大家就公用了一個biz,先來看看相同的biz 也就是業務層

RequestBiz

聲明瞭一個接口,帶有請求數據業務的方法

public interface RequestBiz {
    //請求數據業務
    void requestForData(OnRequestListener listener);
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

RequestBiziml

請求的實現類爲了模擬網絡請求,開啓了一個會sleep2秒的線程,然後裝填請求的數據,通過OnRequestListener 接口回調出去,與我們平時開發的方式一致。

public class RequestBiziml implements RequestBiz{

    @Override
    public void requestForData(final OnRequestListener listener) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    ArrayList<String> data = new ArrayList<String>();
                    for(int i = 1 ; i< 8 ; i++){
                        data.add("item"+i);
                    }
                    if(null != listener){
                        listener.onSuccess(data);
                    }
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

OnRequestListener

數據請求的回掉接口,聲明瞭成功和失敗的方法 。

public interface OnRequestListener {

    void onSuccess(List<String> data);
    void onFailed();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

到此業務層完成,開始實現功能了,先來看看傳統寫法

MVC的寫法

public class MVCActivity extends AppCompatActivity{

    private ListView mvcListView;
    private RequestBiz requestBiz;
    private ProgressBar pb;
    private Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvc);
        mvcListView = (ListView)findViewById(R.id.mvc_listview);
        pb = (ProgressBar) findViewById(R.id.mvc_loading);
        pb.setVisibility(View.VISIBLE);
        handler = new Handler(Looper.getMainLooper());
        requestBiz = new RequestBiziml();
        requestForData();
    }

    public void requestForData(){
        requestBiz.requestForData(new OnRequestListener() {
            @Override
            public void onSuccess(final List<String> data) {
             //由於請求開啓了新線程,所以用handler去更新界面
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        pb.setVisibility(View.GONE);
                        ArrayAdapter adapter = new ArrayAdapter(MVCActivity.this,
                        android.R.layout.simple_list_item_1,data);
                        mvcListView.setAdapter(adapter);
                        mvcListView.setOnItemClickListener(itemClickListener);
                    }
                });

            }

            @Override
            public void onFailed() {
                pb.setVisibility(View.GONE);
                Toast.makeText(MVCActivity.this,"加載失敗",Toast.LENGTH_SHORT).show();
            }
        });
    }

    AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            Toast.makeText(MVCActivity.this,"點擊了item"+(position+1),Toast.LENGTH_SHORT).show();
        }
    };
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

比較常見的Activity裏面的寫法:

  1. 界面的初始化
  2. 發起請求以及請求完成後的界面更新
  3. 點擊的監聽設置方法。

C: 控制層(點擊事件,網絡請求), V : 視圖層(界面刷新) 。就這樣耦合到了Activity裏面。

MVP的寫法

由於Activity變成了view層不再去控制界面,但是具體的界面的改變api其實還是由Activity來提供的,所以在寫MVP之前需要思考,View層需要哪些方法。

  • 顯示loading
  • 隱藏loading
  • listview的初始化
  • 彈出Toast消息

MvpView

public interface MvpView {
     //顯示loading progress
     void showLoading();
     //隱藏loading progress
     void hideLoading();
     //ListView的初始化
     void setListItem();
     //Toast 消息
     void showMessage();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

通過上面的MVC的例子,我們可以總結出View層需要的接口。我們的Activty就是View層,所以直接用Activity來實現上面的方法。

public class MVPActivity extends AppCompatActivity implements MvpView{

    ListView mvpListView;
    MvpPresenter mvpPresenter;
    ProgressBar pb;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvp);
        mvpListView = (ListView)findViewById(R.id.mvp_listview);
        pb = (ProgressBar) findViewById(R.id.mvp_loading);
    }

    @Override
    public void showLoading() {
        pb.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading() {
        pb.setVisibility(View.GONE);
    }

    @Override
    public void setListItem(List<String> data) {
        ArrayAdapter adapter = new ArrayAdapter(MVPActivity.this,
        android.R.layout.simple_list_item_1,data);
        mvpListView.setAdapter(adapter);
    }


    @Override
    public void showMessage(String message) {
        Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

View視圖層就完成了。接下來開始寫presenter層, 同樣在寫presenter之前想想控制層需要哪些方法

  • 網絡請求
  • 點擊事件

MvpPresenter

public class MvpPresenter {

    private MvpView mvpView;
    RequestBiz requestBiz;
    private Handler mHandler;

    public MvpPresenter(MvpView mvpView) {
        this.mvpView = mvpView;
        requestBiz = new RequestBiziml();
        mHandler = new Handler(Looper.getMainLooper());
    }

    public void onResume(){
        mvpView.showLoading();
        requestBiz.requestForData(new OnRequestListener() {
            @Override
            public void onSuccess(final List<String> data) {
               //由於請求開啓了新線程,所以用handler去更新界面
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mvpView.hideLoading();
                        mvpView.setListItem(data);
                    }
                });

            }

            @Override
            public void onFailed() {
                mvpView.showMessage("請求失敗");
            }
        });
    }

    public void onItemClick(int position){
        mvpView.showMessage("點擊了item"+position);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

Presenter完成,現在就剩下一件事,Activity中使用Presenter

完整版MVPActivity

public class MVPActivity extends AppCompatActivity implements MvpView ,AdapterView.OnItemClickListener{

    ListView mvpListView;
    MvpPresenter mvpPresenter;
    ProgressBar pb;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvp);
        mvpListView = (ListView)findViewById(R.id.mvp_listview);
        mvpListView.setOnItemClickListener(this);
        pb = (ProgressBar) findViewById(R.id.mvp_loading);
        mvpPresenter = new MvpPresenter(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mvpPresenter.onResume();
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        mvpPresenter.onItemClick(position);
    }

    @Override
    public void showLoading() {
        pb.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading() {
        pb.setVisibility(View.GONE);
    }

    @Override
    public void setListItem(List<String> data) {
        ArrayAdapter adapter = new ArrayAdapter(MVPActivity.this,
        android.R.layout.simple_list_item_1,data);
        mvpListView.setAdapter(adapter);
    }


    @Override
    public void showMessage(String message) {
        Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

靜靜的享受MVP的感覺吧,就是這麼清爽,沒有亂七八糟的業務,沒有各種點擊處理邏輯,Activity只需要提供View層的方法就可以了。

一點心得

我們的實際發開中需求往往瞬息萬變,每次還要等美工出界面,這樣往往會浪費大量時間,但是在MVP的世界這些都不是事,視圖層與控制層完全分離,可以讓我們在界面還是很粗糙的情況下,先進行控制層的開發,甚至可以先讓View層先提供方法出來,這樣可以節省很多時間。後面的複雜需求變化,也就是構建接口的時候會變的複雜,但是在有這麼多優勢的情況下,這點複雜度完全可以接受 
還等什麼快來加入MVP吧!!!

代碼下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章