Android 從 MVC 到 MVP 的演變

MVC 簡介

  • MVC 模式代表 Model-View-Controller(模型-視圖-控制器) 模式。這種模式用於應用程序的分層開發。
    在這裏插入圖片描述
  • Model(模型) - 模型代表一個存取數據的對象或 JAVA POJO。它也可以帶有邏輯,在數據變化時更新控制器。
  • View(視圖) - 視圖代表模型包含的數據的可視化。
  • Controller(控制器) - 控制器作用於模型和視圖上。它控制數據流向模型對象,並在數據變化時更新視圖。它使視圖與模型分離開。

下面是一個簡單的 mvc 實現的例子

  • MainActivity如下,就是簡單的一個ListView展示了個列表,適配器就不粘出來了
public class MainActivity extends BaseActivity {

    private ListView listView;
    private List<InfoBean> lists = new ArrayList<>();

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

    private void init() {
        listView = findViewById(R.id.listView);
    }

    private void getData() {
        lists.add(new InfoBean("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586336456530&di=d427f7650721a197f1dbe68169814608&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20200103%2Fcb1d64d373f54f21928a4813f41937e6.jpeg",
                "語文書", "從小就學習的語文書"));
        lists.add(new InfoBean("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586336510815&di=8f8dfc6f48f2864409205a1a5c8bda1b&imgtype=0&src=http%3A%2F%2Fimg14.360buyimg.com%2Fn1%2Fjfs%2Ft7012%2F299%2F121318139%2F28880%2F44c02f66%2F5972f6bfN3eee543b.jpg",
                "數學書", "從小就學習的數學書"));
        lists.add(new InfoBean("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586336548967&di=4bc1d03cd9a0e68d4b90740fcdd61bca&imgtype=0&src=http%3A%2F%2Fshopimg.kongfz.com.cn%2F20130326%2F118587%2F118587DE4vm0_b.jpg",
                "英語書", "從小就學習的英語書"));
        lists.add(new InfoBean("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=189950173,2182075783&fm=26&gp=0.jpg",
                "化學書", "從小就學習的化學書"));
        lists.add(new InfoBean("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2986308404,3738485014&fm=26&gp=0.jpg",
                "物理書", "從小就學習的物理書"));
        lists.add(new InfoBean("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586336997608&di=9ac209c043c6dba2300cf4df9cc1b554&imgtype=0&src=http%3A%2F%2Fimg.jk51.com%2Fimg_jk51%2F148284641.jpeg",
                "生物書", "從小就學習的生物書"));

        listView.setAdapter(new MyAdapter(this, lists));
    }

}

效果圖如下:
代碼效果圖

  • 以上是簡單的一個 mvc 實現的邏輯,view 和 data 綁定都放在了activity中,如果代碼邏輯比較複雜的頁面 將來維護activity頁面會非常麻煩,所以要想辦法簡化代碼,降低耦合度。

MVP 簡介和基本框架搭建

  • 簡稱:MVP 全稱:Model-View-Presenter ;MVP 是從經典的模式MVC演變而來,它們的基本思想有相通的地方:Controller/Presenter負責邏輯的處理,Model提供數據,View負責顯示。
    在這裏插入圖片描述
    接下來我們按照圖中所展示的來一點點改造上面的代碼。

  • 上面的圖所示 創建 view 接口

public interface IMainView {
	// 返回data
    void showData(List<InfoBean> infoBeans);
}
  • 創建 IModel接口和 model實現類
public interface IModel {
    // 通過回調注入的形式獲取數據
    void loadInfo(OnLoadListener loadListener);


    interface OnLoadListener {
        void onComplete(List<InfoBean> infoBeans);
    }
}

public class MainModel implements IModel {
	// 通過接口獲取數據
    @Override
    public void loadInfo(OnLoadListener loadListener) {
        List<InfoBean> lists = new ArrayList<>();
        lists.add(new InfoBean("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586336456530&di=d427f7650721a197f1dbe68169814608&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20200103%2Fcb1d64d373f54f21928a4813f41937e6.jpeg",
                "語文書", "從小就學習的語文書"));
        lists.add(new InfoBean("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586336510815&di=8f8dfc6f48f2864409205a1a5c8bda1b&imgtype=0&src=http%3A%2F%2Fimg14.360buyimg.com%2Fn1%2Fjfs%2Ft7012%2F299%2F121318139%2F28880%2F44c02f66%2F5972f6bfN3eee543b.jpg",
                "數學書", "從小就學習的數學書"));
        lists.add(new InfoBean("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586336548967&di=4bc1d03cd9a0e68d4b90740fcdd61bca&imgtype=0&src=http%3A%2F%2Fshopimg.kongfz.com.cn%2F20130326%2F118587%2F118587DE4vm0_b.jpg",
                "英語書", "從小就學習的英語書"));
        lists.add(new InfoBean("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=189950173,2182075783&fm=26&gp=0.jpg",
                "化學書", "從小就學習的化學書"));
        lists.add(new InfoBean("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2986308404,3738485014&fm=26&gp=0.jpg",
                "物理書", "從小就學習的物理書"));
        lists.add(new InfoBean("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1586336997608&di=9ac209c043c6dba2300cf4df9cc1b554&imgtype=0&src=http%3A%2F%2Fimg.jk51.com%2Fimg_jk51%2F148284641.jpeg",
                "生物書", "從小就學習的生物書"));
        loadListener.onComplete(lists);
    }
}
  • 創建 Presenter

public class MainPresenter {
	// 依賴 view
    private IMainView iMainView;
	// 依賴 model
    private IModel mainModel = new MainModel();

    public MainPresenter(IMainView iMainView) {
        this.iMainView = iMainView;
    }
	// 通過model 獲取數據 返回回調
    public void fetch() {
        mainModel.loadInfo(new IModel.OnLoadListener() {
            @Override
            public void onComplete(List<InfoBean> infoBeans) {
            	// 通過 view 返回數據
                iMainView.showData(infoBeans);
            }
        });
    }
}
  • view層代碼
// 實現 IMainView接口
public class MainActivity extends BaseActivity implements IMainView {
    private ListView listView;
    private MainPresenter presenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        // 初始化 Presenter
        presenter = new MainPresenter(this);
        // 獲取數據
        presenter.fetch();
    }

    private void init() {
        listView = findViewById(R.id.listView);
    }

    @Override
    public void showData(List<InfoBean> infoBeans) {
    	// 	設置回調數據結果
        listView.setAdapter(new MyAdapter(this, infoBeans));
    }
}

以上就是一個基本的 mvp 架構。但是基本的架構首先不滿足擴展,不能動態注入。並且當 Presenter有網絡請求的時候,Activity銷燬後不能及時回收,導致內存泄漏。接下來進行 mvp的重構。

MVP 重構(1)

  • 防止內存泄漏
  • 解決方案1是傳入P層的View採用若引用方式。
public class MainPresenter<T extends IMainView> {

    private WeakReference<T> iMainView;

    private IModel mainModel = new MainModel();

    public MainPresenter(T iMainView) {
        this.iMainView = new WeakReference<T>(iMainView);
    }

    public void fetch() {
        mainModel.loadInfo(new IModel.OnLoadListener() {
            @Override
            public void onComplete(List<InfoBean> infoBeans) {
                iMainView.get().showData(infoBeans);
            }
        });
    }
}
  • 採用管理生命週期的方式防止內存泄漏 presenter中去掉構造方法
public class MainPresenter<T extends IMainView> {

    private WeakReference<T> iMainView;

    private IModel mainModel = new MainModel();

    /**
     * 綁定view
     */
    public void attachView(T view) {
        this.iMainView = new WeakReference<T>(view);
    }

    /**
     * 解綁view
     */
    public void detachView() {
        iMainView.clear();
        iMainView = null;
    }

    public void fetch() {
        mainModel.loadInfo(new IModel.OnLoadListener() {
            @Override
            public void onComplete(List<InfoBean> infoBeans) {
                iMainView.get().showData(infoBeans);
            }
        });
    }
}

上面的代碼不能每次使用的時候都需要寫這麼多的代碼,怎麼簡化呢接下來繼續抽離。

MVP 重構(2)

抽離到base。

  • BaseActiity
// T 傳入 Presenter  V 傳入View
public abstract class BaseActivity<T extends BasePresenter, V> extends AppCompatActivity {

    protected T presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        presenter = createPresenter();
        if (presenter!=null){
        	// 	綁定View
            presenter.attachView((V) this);
        }
    }
	// 子類創建 presenter
    protected abstract T createPresenter();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解綁view
        presenter.detachView();
    }
}
  • BasePresenter
/**
 * @param <V> 傳入View
 */
public class BasePresenter<V> {

    private WeakReference<V> referenceView;

    V getView() {
        if (referenceView != null) {
            return referenceView.get();
        }
        return null;
    }

    public void attachView(V view) {
        this.referenceView = new WeakReference<V>(view);
    }

    public void detachView() {
        if (referenceView != null) {
            referenceView.clear();
            referenceView = null;
        }
    }
}

  • 具體的Presenter
public class MainPresenter<T extends IMainView> extends BasePresenter<T> {

    private IModel mainModel = new MainModel();

    public void fetch() {
        mainModel.loadInfo(new IModel.OnLoadListener() {
            @Override
            public void onComplete(List<InfoBean> infoBeans) {
                getView().showData(infoBeans);
            }
        });
    }

}

  • MainActivity 使用

public class MainActivity extends BaseActivity<MainPresenter<IMainView>,IMainView> implements IMainView {

    private ListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        presenter.fetch();
    }

    @Override
    public MainPresenter<IMainView> createPresenter() {
        return new MainPresenter<>();
    }

    private void init() {
        listView = findViewById(R.id.listView);
    }

    @Override
    public void showData(List<InfoBean> infoBeans) {
        listView.setAdapter(new MyAdapter(this, infoBeans));
    }
}


以上就是 MVP 的基本框架了。有個性需求可以自己擴展。比如傳入多個 Presenter等
以上最終代碼下載請點擊

對網絡,圖片等的封裝

  • 新建一個 commonLib 的module。

  • 新建一個 IHttpProcessor 接口,定義需要的訪問類型接口。

/**
 * 各種訪問類型接口
 */
public interface IHttpProcessor {

    /**
     * 網絡操作
     */
    void post(String url, Map<String,Object> params, ICallback callback);

    void delete(String url, Map<String,Object> params, ICallback callback);

}
  • 新建 Helper 做初始化和調用工作
/**
 * Http 請求初始化類
 */
public class HttpHelper {

    private static IHttpProcessor processor;

    private HttpHelper() {
    }

    public static HttpHelper getInstance() {
        return HttpHelperSingleTon.httpHelper;
    }

    private static class HttpHelperSingleTon {
        private static HttpHelper httpHelper = new HttpHelper();
    }

    public void init(IHttpProcessor processor) {
        HttpHelper.processor = processor;
    }
	// 調用 post 操作
    public void post(String url, Map<String, Object> params, ICallback callback) {
        // 請求代理給 具體的 processor 執行具體訪問
        // appendParams 是將 post可以轉爲 get請求  訪問get請求的時候也可以使用此post方法
        String finalUrl = appendParams(url, params);
        processor.post(finalUrl, params, callback);
    }
	// 調用 delete 操作
    public void delete(String url, Map<String, Object> params, ICallback callback) {
        processor.delete(url, params, callback);
    }


    private static String appendParams(String url, Map<String, Object> params) {
        if (params == null || params.isEmpty()) {
            return url;
        }
        StringBuilder urlBuilder = new StringBuilder(url);
        if (urlBuilder.indexOf("?") <= 0) {
            urlBuilder.append("?");
        } else {
            if (!urlBuilder.toString().endsWith("?")) {
                urlBuilder.append("&");
            }
        }
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            urlBuilder.append("&" + entry.getKey())
                    .append("=")
                    .append(encode(entry.getValue().toString()));
        }
        return urlBuilder.toString();
    }

    private static String encode(String str) {
        try {
            return URLEncoder.encode(str, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }
}
  • 新建 ICalback
public interface ICallback {
    void onSuccess(String result);

    void onError(String result);
}
  • 新建解析 callBack數據的具體callback
/**
 * 回調接口的json版本的實現類
 * 用於把網絡返回的json字符串轉讓換成對象(Result就是用戶接收數據的類型)
 */
public abstract class HttpCallback<Result> implements ICallback {
    @Override
    public void onSuccess(String result) {//result就是網絡回來的數據
        //result把轉換成用戶需要的對象
        Gson gson=new Gson();
        //需要得到用戶輸入的對象對應的字節碼是什麼樣的
        //得到用戶接收數據的對象對應的class
        Class<?> clz=analysisClassInfo(this);
        Result objResult=(Result)gson.fromJson(result,clz);
        //回調給調用層
        this.onSuccess(objResult);
    }

    public abstract void onSuccess(Result result);
    /**
     * 獲取反省<>中的類型
     * @param object
     * @return
     */
    private Class<?> analysisClassInfo(Object object) {
        //getGenericSuperclass可以得到包含原始類型,參數化類型,數組,類型變量,基本數據
        Type genType=object.getClass().getGenericSuperclass();
        //獲取參數化類型
        Type[] params=((ParameterizedType)genType).getActualTypeArguments();
        return (Class<?>)params[0];
    }

    @Override
    public void onError(String result) {
    }
}
  • 新建具體的網絡訪問 如 okHttp volley 等
public class OkHttpProcessor implements IHttpProcessor {
    private OkHttpClient mOkHttpClient;
    private Handler myHandler;

    public OkHttpProcessor() {
        mOkHttpClient = new OkHttpClient();
        myHandler = new Handler();
    }

    private RequestBody appendBody(Map<String, Object> params) {
        FormBody.Builder body = new FormBody.Builder();
        if (params == null || params.isEmpty()) {
            return body.build();
        }
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            body.add(entry.getKey(), entry.getValue().toString());
        }
        return body.build();
    }

    @Override
    public void post(String url, Map<String, Object> params, final ICallback callback) {
        RequestBody requestBody = appendBody(params);
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        mOkHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callback.onError(e.toString());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String result = response.body().string();
                if (response.isSuccessful()) {
                    myHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            callback.onSuccess(result);
                        }
                    });
                }

            }
        });
    }

    @Override
    public void delete(String url, Map<String, Object> params, ICallback callback) {

    }

}

  • 使用:app添加對 commonLib 的依賴在 app 中的 applicaiton 中初始化操作
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 傳入具體的實現
        HttpHelper.getInstance().init(new OkHttpProcessor());
//        HttpHelper.getInstance().init(new VolleyProcessor(this));
    }
}
  • 在 mvp的 model層調用訪問
public class MainModel implements IModel {


    @Override
    public void loadInfo(OnLoadListener loadListener) {
        //測試隔離層代碼
        String url="url";
        HashMap<String,Object> params=new HashMap<>();
        params.put("param","");
        HttpHelper.getInstance().post(url, params, new HttpCallback<TestBean>() {
            @Override
            public void onSuccess(TestBean testBean) {
                Log.e("onSuccess",testBean.toString());
            }

            @Override
            public void onError(String result) {

            }
        });
    }
}

使用這種方式的好處是當想切換新的框架時,不需要改業務邏輯的代碼,只需要在 application 初始化的時候切換一下新增的 processor 實現就可以了。同樣圖片框架,數據庫,都可以使用這種方式來切換。
源碼已經上傳有需要的可以下載自己完善 跳轉連接

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