欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从零搭建Android-MVP框架、基类封装以及简单使用

程序员文章站 2024-03-15 19:37:42
...

首先我们先了解一下MVP的原理以及流程:
从零搭建Android-MVP框架、基类封装以及简单使用
MVP分三层:View、Presenter、Model
view层不直接与model交互,而是通过presenter来与model交互,view负责数据展示,发起请求,而presenter则负责将view的请求转发给model,然后有model来处理相应的数据请求等操作。

MVP的优点:前后端分离,降低耦合度,逻辑分明,思路清晰等

MVP缺点:很明显的就是类的数量变多了

在Android中,对于Activity并没有明确的说它是属于View还是Controller的范畴,Activity既有View的性质,也具有Controller的性质,所以导致MVC在Android中很难明确分工使用,导致Activity很重。而且MVC中View会与Model直接交互,所以Activity与Model的耦合性很高,当后期维护时,稍有变动,可能Model、Activity、XML都会跟着改变,工作量很大,成本太高。

而MVP与MVC最大的不同之处是,MVP将M与V分隔开来,通过P交互,这样在Android中,就可以明确的把Activity当作View处理,虽然可能还有一点逻辑在其中,但是已经无伤大雅;View和Model不直接交互,当View有变动或者Model有变动时,不会相互影响,有太大变动,,耦合性低,对于后期维护来说,特别是项目越来越庞大时,可以很快的理清项目结构,找到需要修改的地方,大大的缩短了工作量。而且,因为View与Model分离的缘故,Model可以单独进行单元测试。

好了,上面说了那么多,我们还是来点实际的吧,下面是本人在项目中对MVP的处理方式,有不同见解的,欢迎大家提出。

首先对我们的基类进行封装,之后所有的子类都要继承自基类:

封装一个超级父类,传入一个泛型:CONTRACT

package com.org.huanjianmvp.Base;

/**
 * 整个架构的父类、超级父类
 * 泛型 CONTRACT 面向的是接口类
 * 子类必须实现方法接口getContract(),具体实现哪些看传过来的方法接口中有哪些方法
 * Created by Administrator on 2020/8/19.
 */

public abstract class SuperBase<CONTRACT> {

    /*【子类要实现的方法,具体实现由传过来的接口中的方法决定】*/
    public abstract CONTRACT getContract();

}

View层封装:Activity 或者 Fragment

package com.org.huanjianmvp.Base;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;

import com.org.huanjianmvp.BootApplication;
import com.org.huanjianmvp.Login;

import cn.pedant.SweetAlert.SweetAlertDialog;

/**
 * 只关注P层,不与V层进行交互
 * 继承自AppCompatActivity的基类
 * Created by Administrator on 2020/8/19.
 */

public abstract class BaseActivity<P extends BaseActivityPresenter, CONTRACT>
        extends AppCompatActivity implements View.OnClickListener{

    public P mPresenter;

    private Long startTime = System.currentTimeMillis();    //第一次点击时间
    private Long clickTime = System.currentTimeMillis();    //每一次点击都重置该时间,判断两次时间间隔
    private Intent intent ;                                 //超时跳转页面
    private SweetAlertDialog alertDialog;                   //超时提示框
    public BootApplication application;                     //程序页面管理

    public abstract CONTRACT getContract();     //方法接口,实现接口中的方法

    public abstract P createPresenter();        //实例化P层

    public abstract int getViewID();            //拿到布局视图

    public abstract void initViewUI();          //初始化组件

    public abstract void initListener();        //初始化监听

    public abstract void destroy();             //销毁时执行方法

    public abstract void initDatas();           //初始化数据

    //处理相应错误信息
    public abstract<ERROR extends Object> void responseError(ERROR error , Throwable throwable);

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(getViewID());                //引入布局文件
        this.mPresenter = createPresenter();        //实例化P层
        application = (BootApplication)getApplication();
        mPresenter.attach(this);              //绑定
        initViewUI();                               //初始化UI组件
        initListener();                             //初始化监听
        initDatas();                                //初始化数据
    }

    @Override
    protected void onDestroy() {
        destroy();                                  //销毁时触发
        mPresenter.detach();                        //解绑
        if (alertDialog!=null){
            alertDialog.dismiss();
        }
        super.onDestroy();
    }

}

Model层基类封装:

package com.org.huanjianmvp.Base;

/**
 * 拿到P层,把结果给P层
 * 将由子类传泛型 CONTRACT 到父类中
 * 子类传入到父类中的泛型 CONTRACT 一般为方法接口,具体子类要实现的方法由传入的方法接口决定
 * Created by Administrator on 2020/8/19.
 */

public abstract class BaseActivityModel<P extends BaseActivityPresenter, CONTRACT> extends SuperBase<CONTRACT> {

    public P presenter;

    public BaseActivityModel(P mPresenter){
        this.presenter = mPresenter;    //拿到P层
    }

}

Presenter层基类封装:

package com.org.huanjianmvp.Base;

import java.lang.ref.WeakReference;

/**
 * 拿到V层和M层,View必须是继承BaseActivity
 * Presenter关联M和V,M层和V层不能有交互,通过中间层P来进行交互
 * Created by Administrator on 2020/8/19.
 */

public abstract class BaseActivityPresenter<V extends BaseActivity , M extends BaseActivityModel, CONTRACT> extends SuperBase<CONTRACT>  {

    public WeakReference<V> weakView;  //弱化引用
    public M mModel;

    public abstract M createModel();        //创建一个model,具体model由子类传入决定

    //实例化model,建立与M得关系
    public BaseActivityPresenter(){
        mModel = createModel();
    }


    //建立与V的关系,绑定
    public void attach(V view){
        weakView = new WeakReference<>(view);
    }

    //解绑
    public void detach(){
        if (weakView.get()!=null){
            weakView.get().finish();
        }
        if (weakView != null){
            weakView.clear();
            weakView=null;
        }
    }
}

到这里基类的封装基本完成,还有个契约类由是基类的子类接口来实现,规定了子类的行为

我这里具体举个简单的登录例子:

登录的契约类接口:接口中规范了相应的行为:

package com.org.huanjianmvp.Contrast;

import android.content.Context;

import java.util.Map;

import cn.pedant.SweetAlert.SweetAlertDialog;

/**
 * 契约接口,规定行为,管理M层、V层、P层的抽象方法
 * 降低耦合度
 * Created by Administrator on 2020/8/19.
 */

public interface LoginContrast {

    /**【 Model 层的方法接口】**/
    interface Model{
        //登录验证
        void validateLogin(String userName , String passWord) throws Exception;

        //处理退出请求
        void exitAction(SweetAlertDialog alert);

        //版本号获取
        void versionAction(Context context);

        //权限申请处理
        void permissionAction(Context context);

        //token刷新处理
        void tokenAction();
    }

    /**【 View 层和 Presenter 层的方法接口】**/
    interface ViewAndPresenter{

        //登录请求
        void requestLogin(String userName , String passWord);

        //响应请求结果
        void responseResult(String msg);

        //请求退出
        void requestExit(SweetAlertDialog alert);

        //响应请求退出
        void responseExit(Boolean isExit);

        //请求版本号
        void requestVersion(Context context);

        //响应版本号
        void responseVersion(Map<String,String> map);

        //权限申请
        void requestPermission(Context context);

        //请求刷新token
        void requestToken();
    }

}

登录布局文件:我这里是直接拉项目中的代码过来,建议做个简单的按钮和输入框即可

<?xml version="1.0" encoding="utf-8"?>
<!--    登录页面布局  -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:background="@drawable/blue_button_background"
    tools:context="com.org.huanjianmvp.Login">

    <com.beardedhen.androidbootstrap.BootstrapButton
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="新环检软件"
        android:enabled="false"
        app:bootstrapBrand="info"
        app:bootstrapSize="xl"
        app:buttonMode="regular"
        app:roundedCorners="false"
        app:showOutline="false" />

    <ImageView
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:src="@drawable/login_icon"/>

    <com.rengwuxian.materialedittext.MaterialEditText
        android:id="@+id/EditTextUserName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入登录名"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        app:met_floatingLabel="normal"
        app:met_floatingLabelText="请在此输入登录名:"
        app:met_helperText="登录名:"
        app:met_clearButton="true"
        app:met_primaryColor="@color/bootstrap_brand_primary" />


    <com.rengwuxian.materialedittext.MaterialEditText
        android:id="@+id/EditTextPassWord"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入登录密码"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        app:met_floatingLabel="normal"
        app:met_floatingLabelText="请在此输入登录密码:"
        app:met_helperText="登录密码:"
        app:met_clearButton="true"
        app:met_primaryColor="@color/bootstrap_brand_primary" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <com.beardedhen.androidbootstrap.BootstrapButton
            android:id="@+id/exit"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:text="退出"
            app:bootstrapBrand="warning"
            app:bootstrapSize="xl"
            app:buttonMode="regular"
            app:roundedCorners="true"
            app:showOutline="true" />


        <com.beardedhen.androidbootstrap.BootstrapButton
            android:id="@+id/setting"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:text="设置"
            app:bootstrapBrand="info"
            app:bootstrapSize="xl"
            app:buttonMode="regular"
            app:roundedCorners="true"
            app:showOutline="true" />

        <com.beardedhen.androidbootstrap.BootstrapButton
            android:id="@+id/login"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:text="登录"
            app:bootstrapBrand="success"
            app:bootstrapSize="xl"
            app:buttonMode="regular"
            app:roundedCorners="true"
            app:showOutline="true" />

    </LinearLayout>

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="bottom">
        <TextView
            android:id="@+id/appVersionText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_gravity="center_horizontal"
            android:gravity="center"
            android:text="版本号:"
            android:textColor="@color/bootstrap_gray_dark"
            android:textSize="15sp" />

    </RelativeLayout>

</LinearLayout>

从零搭建Android-MVP框架、基类封装以及简单使用
登录的View层,也就是我们的LoginActivity:

package com.org.huanjianmvp;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;

import com.beardedhen.androidbootstrap.BootstrapButton;
import com.org.huanjianmvp.Activity.ListDatas;
import com.org.huanjianmvp.Activity.Setting;
import com.org.huanjianmvp.Base.BaseActivity;
import com.org.huanjianmvp.Contrast.LoginContrast;
import com.org.huanjianmvp.Presenter.LoginActivityPresenter;
import com.org.huanjianmvp.Utils.AlertDialogUtils;
import com.rengwuxian.materialedittext.MaterialEditText;
import java.util.Map;

import cn.pedant.SweetAlert.SweetAlertDialog;

/**
 * 仅负责展示处理,不涉及逻辑以及数据处理
 *
 * **/
public class Login extends BaseActivity<LoginActivityPresenter,LoginContrast.ViewAndPresenter> {

    private String username , password;
    private MaterialEditText userName , passWord;
    private BootstrapButton btnLogin , btnExit , btnSetting;
    private AlertDialogUtils dialogUtils;
    private SweetAlertDialog alert;
    private SharedPreferences preferences;
    private SharedPreferences.Editor editor;
    private TextView appVersionText;

    /**【实现契约接口中的方法】**/
    @Override
    public LoginContrast.ViewAndPresenter getContract() {
        return new LoginContrast.ViewAndPresenter() {
            /**【发起登录请求】**/
            @Override
            public void requestLogin(String userName, String passWord) {
                mPresenter.getContract().requestLogin(userName,passWord);
            }
            /**【响应请求结果】**/
            @Override
            public void responseResult(String msg) {
                if (msg != null){
                    if (msg.equals("登录成功")){
                        Intent intent = new Intent(Login.this, ListDatas.class);
                        startActivity(intent);
                        Login.this.finish();
                    }else{
                        dialogUtils.AlertTitle(msg,"warning");
                    }
                }
            }

            @Override
            public void requestExit(SweetAlertDialog alert) {
                mPresenter.getContract().requestExit(alert);
            }

            @Override
            public void responseExit(Boolean isExit) {
                if (isExit){
                    Login.this.finish();
                }
            }

            @Override
            public void requestVersion(Context context) {
                mPresenter.getContract().requestVersion(context);
            }

            @Override
            public void responseVersion(Map<String,String> map) {
                //dialogUtils.AlertTitle(map.get("deviceID"),"success");
                appVersionText.setText(map.get("versionName"));
            }

            @Override
            public void requestPermission(Context context) {
                mPresenter.getContract().requestPermission(Login.this);
            }

            @Override
            public void requestToken() {
                mPresenter.getContract().requestToken();
            }
        };
    }

    /**【创建实例化一个Presenter】**/
    @Override
    public LoginActivityPresenter createPresenter() {
        return new LoginActivityPresenter();
    }

    /**【拿到、引用布局文件】**/
    @Override
    public int getViewID() {
        return R.layout.activity_login;
    }

    /**【初始化UI组件】**/
    @Override
    public void initViewUI() {
        application.addActivity(Login.this);
        appVersionText = findViewById(R.id.appVersionText);
        userName = findViewById(R.id.EditTextUserName);
        passWord = findViewById(R.id.EditTextPassWord);
        btnLogin = findViewById(R.id.login);
        btnExit = findViewById(R.id.exit);
        btnSetting = findViewById(R.id.setting);
        dialogUtils = new AlertDialogUtils(this);
        alert = dialogUtils.getAlertDialog("warning");
        alert.setCancelable(false);
        preferences = getSharedPreferences("userName",0);
        editor = preferences.edit();
    }

    /**【初始化监听事件】**/
    @Override
    public void initListener() {
        btnLogin.setOnClickListener(this);
        btnExit.setOnClickListener(this);
        btnSetting.setOnClickListener(this);
    }

    /**【销毁时执行方法】**/
    @Override
    public void destroy() {
        if (dialogUtils != null){
            dialogUtils.dismissDialog();
        }
        if (alert != null){
            alert.dismiss();
        }
        dialogUtils = null;
        alert = null;
    }

    /**【初始化数据】**/
    @Override
    public void initDatas() {
        userName.setText(preferences.getString("userName",""));
        getContract().requestVersion(Login.this);
        getContract().requestPermission(Login.this);
    }

    /**【报错处理】**/
    @Override
    public void responseError(Object o, Throwable throwable) {
        dialogUtils.AlertTitle(throwable.getMessage(),"error");
    }

    /**【点击执行事件】**/
    @Override
    public void onClick(View view) {
        username = userName.getText().toString().trim();
        password = passWord.getText().toString().trim();
        switch (view.getId()){
            case R.id.login:
                editor.putString("userName",username);
                editor.commit();
                /**【交由契约类处理,契约类交给P层,P层交给M层】**/
                getContract().requestLogin(username,password);
                break;
            case R.id.exit:
                getContract().requestToken();
                getContract().requestExit(alert);
                break;
            case R.id.setting:
                Intent intent = new Intent(Login.this, Setting.class);
                startActivity(intent);
                break;
        }
    }



    /**【重写返回键】**/
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
            getContract().requestExit(alert);
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
}

Presenter层:负责转交给model层来处理

package com.org.huanjianmvp.Presenter;

import android.content.Context;

import com.org.huanjianmvp.Base.BaseActivityPresenter;
import com.org.huanjianmvp.Contrast.LoginContrast;
import com.org.huanjianmvp.Login;
import com.org.huanjianmvp.Model.LoginActivityModel;

import java.util.Map;

import cn.pedant.SweetAlert.SweetAlertDialog;

/**
 * Created by Administrator on 2020/8/19.
 */

public class LoginActivityPresenter extends BaseActivityPresenter<Login,LoginActivityModel,LoginContrast.ViewAndPresenter> {


    @Override
    public LoginContrast.ViewAndPresenter getContract() {
        return new LoginContrast.ViewAndPresenter() {
            @Override
            public void requestLogin(String userName, String passWord) {
                try {
                    mModel.getContract().validateLogin(userName,passWord);
                } catch (Exception e) {
                    weakView.get().responseError(userName,e);
                    e.printStackTrace();
                }
            }

            @Override
            public void responseResult(String msg) {
                weakView.get().getContract().responseResult(msg);
            }

            @Override
            public void requestExit(SweetAlertDialog alert) {
                mModel.getContract().exitAction(alert);
            }

            @Override
            public void responseExit(Boolean isExit) {
                weakView.get().getContract().responseExit(isExit);
            }

            @Override
            public void requestVersion(Context context) {
                mModel.getContract().versionAction(context);
            }

            @Override
            public void responseVersion(Map<String,String> map) {
                weakView.get().getContract().responseVersion(map);
            }

            @Override
            public void requestPermission(Context context) {
                mModel.getContract().permissionAction(context);
            }

            @Override
            public void requestToken() {
                try {
                    mModel.getContract().tokenAction();
                }catch (Exception e){
                    weakView.get().responseError(e,e);
                }
            }

        };
    }

    @Override
    public LoginActivityModel createModel() {
        return new LoginActivityModel(this);
    }
}

Model层:具体的逻辑操作在这里执行

package com.org.huanjianmvp.Model;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;

import com.org.huanjianmvp.Base.BaseActivityModel;
import com.org.huanjianmvp.Contrast.LoginContrast;
import com.org.huanjianmvp.Domain.token;
import com.org.huanjianmvp.Internet.ObserverManager;
import com.org.huanjianmvp.Internet.RetrofitManager;
import com.org.huanjianmvp.Presenter.LoginActivityPresenter;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import cn.pedant.SweetAlert.SweetAlertDialog;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

/**
 * 在这里实现数据处理
 * Created by Administrator on 2020/8/19.
 */

public class LoginActivityModel extends BaseActivityModel<LoginActivityPresenter,LoginContrast.Model> {

    public LoginActivityModel(LoginActivityPresenter mPresenter) {
        super(mPresenter);
    }

    @Override
    public LoginContrast.Model getContract() {
        return new LoginContrast.Model() {

            /**【登录验证】**/
            @Override
            public void validateLogin(String userName, String passWord) throws Exception {
                if (userName.equals("admin")){
                    presenter.getContract().responseResult("登录成功");
                }else{
                    presenter.getContract().responseResult("登录失败");
                }
            }

            /**【请求退出程序】**/
            @Override
            public void exitAction(final SweetAlertDialog alert) {
                if (alert != null){
                    alert.setTitle("是否退出当前软件?");
                    alert.setConfirmText("确定");
                    alert.setCancelText("取消");
                    alert.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
                        @Override
                        public void onClick(SweetAlertDialog sweetAlertDialog) {
                            presenter.getContract().responseExit(true);
                        }
                    });
                    alert.setCancelClickListener(new SweetAlertDialog.OnSweetClickListener() {
                        @Override
                        public void onClick(SweetAlertDialog sweetAlertDialog) {
                            alert.dismiss();
                        }
                    });
                    alert.show();
                }
            }

            /**【获取程序版本号、手机识别标识】**/
            @Override
            public void versionAction(Context context) {
                Map<String,String> map = new HashMap<>();
                try {
                    /**【获取程序版本】**/
                    PackageManager manager = context.getPackageManager();
                    PackageInfo info = manager.getPackageInfo(context.getPackageName(),0);
                    String versionName = info.versionName;
                    if (versionName != null){
                        versionName = "版本号:20.08.28.20(v" + versionName + ")";
                    }else {
                        versionName = "版本号:20.08.28.20(v3.3.3)";
                    }
                    map.put("versionName",versionName);

                    /**【获取设备识别标识】**/
                    TelephonyManager telephony = (TelephonyManager) context.getSystemService(context.TELEPHONY_SERVICE);
                    @SuppressLint("MissingPermission") String deviceID = telephony.getDeviceId();
                    if (TextUtils.isEmpty(deviceID)){
                        deviceID = Settings.System.getString(context.getContentResolver(),Settings.Secure.ANDROID_ID);
                    }
                    map.put("deviceID",deviceID);

                    presenter.getContract().responseVersion(map);
                } catch (PackageManager.NameNotFoundException e) {
                    e.printStackTrace();
                }

            }

            /**【权限申请】**/
            @Override
            public void permissionAction(Context context) {
                String [] permissions = new String[] {
                    Manifest.permission.READ_PHONE_STATE,
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.CAMERA,
                    Manifest.permission.RECORD_AUDIO,
                };
                ActivityCompat.requestPermissions((Activity) context,permissions,100);
            }

            /**【刷新token请求】**/
            @Override
            public void tokenAction() {
                Observable<token> observable = RetrofitManager.getRetrofitManager().getApiService()
                        .requestToken("refresh_token","web_client","web_secret","7f30f196-d063-4678-afd8-a31644620d03");

                observable.debounce(5000, TimeUnit.MILLISECONDS)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new ObserverManager<token>() {
                            @Override
                            public void onSuccess(token token) {
                                token.tokenShow();
                            }

                            @Override
                            public void onFail(Throwable throwable) {
                                Log.e("错误信息",throwable.toString());
                            }

                            @Override
                            public void onFinish() {
                                Log.e("请求信息","请求完成!");
                            }

                            @Override
                            public void onDisposable(Disposable disposable) {

                            }
                        });
            }
        };
    }
}

以上就是基类的封装以及MVP框架的基本使用。

注意上面的例子使用的是面向接口的方法来实现的,也就是由契约类来规定相应的行为。

最后附上相应的操作截图:
从零搭建Android-MVP框架、基类封装以及简单使用
从零搭建Android-MVP框架、基类封装以及简单使用
如有错误的地方欢迎指导。