android实践的一些思考:实现一个MVP架构设计

    MVP是从MVC架构演变而来的,目的是为了使得程序开发高内聚低耦合,易于扩展,方便维护。 

    MVC中的M指的是model模型, V 指的是View视图,C指的是Controller。  MVC 大体的思想是,控制器控制着模型与视图的同步。当模型发生了变动,通知控制器然后控制器通知视图同步。比如我们浏览一个动态网页,以JSP为例: 用户请求路径--> 得到一个视图V,在视图上修改某个javaBean,即修改模型,此时它会交给servlet控制器C,然后控制器通过后台的业务逻辑代码完成模型M的同步。   修改完模型后,需要回到servlet控制器C中,判断是否需要将模型封装到视图解析器中,通过jstl标签编译成网页视图V返回给用户。 总之,它的特点就是单向通信。 V->C->M;  M->C->V。 

    MVP中,M指的是model模型,V指的是View视图,P指的是Presenter(中介者)。 它的面向对象的思想更强。 具体的理解我将结合自己的练习来进行学习理解。 

    还有一种16年左右红起来的MVVM的架构,现在主流的三大前端框架,angular,react,vue就是基于这个。 其中VM指的是ViewModel,视图模型。  它的最强大的一个地方是将同步的任务交给代码完成,删减了很多的冗余代码,提高开发效率。 貌似叫做响应式开发。 angular的双向绑定,确是一种非常强大理想的特性。

   我的想法出发点是这样的:

       第一,每个Activity中需要写的内容分为三部分, 一部分是布局文件xml,一部分是针对布局文件的业务逻辑代码,另一部分是二者的同步!   要完成这三个基本的逻辑功能,通常我们需要写很多的代码在activity中。 这样会带来一些问题,如,逻辑不清晰,维护困难,看到代码颓丧消极。  因为,稍微一个有些功能的界面,都至少是一百好几行代码。  当这些代码乘以写的页面数,改起来要老命了!

       第二,每一个同步的代码需要写好几步: 第一要确定每个视图id与所对应的成员变量名,那么这里就存在一个命名的问题(而,实际上这个命名除了起个标志位的作用,没其他任何作用,我们要还要尽量做到见名知意)。   第二,要写很多无聊的findById进行绑定。第三,为了完成单向取值,通常需要一个一个从视图组件调用相应的方法取得自己想要的属性。 冗余的代码量很多。  体验很差。 通过开源注入框架ButterKnife可以很大程度上解决这个问题。

      第三,虽说使用了注入框架,代码看起来清爽了很多。 可是为了完成视图与模型的同步,我们还是不得不定义相关的成员变量进行绑定。 有没有一种方法,可以完成类似于angular的双向绑定呢?  

基于MVP的抽象层设计:

  View顶层抽象:

package com.automannn.meimeijiong.activity.view.api.baseApi;

public interface IView {
}

Model 顶层抽象:

package com.automannn.meimeijiong.model.api.base;

public interface IModel {
}

Presenter 顶层抽象:

package com.automannn.meimeijiong.presenter.api;

import com.automannn.meimeijiong.model.api.base.IModel;
import com.automannn.meimeijiong.activity.view.api.baseApi.IView;


public abstract class BasePresenter<M extends IModel,V extends IView>{

   protected M currModel;
   protected V currView;

    public BasePresenter(V view){
        this.currView = view;
    }

    public abstract void init();

}

      在这里可以看到,每个中介者持有模型与视图的引用。 它的目的是为了更好的发挥他协调者的作用。 

View的第二层抽象:

package com.automannn.meimeijiong.activity.api;


import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.automannn.meimeijiong.presenter.api.BasePresenter;


public abstract class BaseActivity<T extends BasePresenter> extends AppCompatActivity {

    protected T currentPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayout());
        initView();
        currentPresenter = initPresent();
        onPrepare();
    }

    protected abstract void onPrepare();

    protected abstract T initPresent();

    protected abstract int getLayout();

    protected abstract void initView();
}

Presenter的第二层抽象:

   使用示例:

package com.automannn.meimeijiong.presenter;


import com.automannn.meimeijiong.model.ChatModel;
import com.automannn.meimeijiong.presenter.api.BasePresenter;

public class ChatPresenter extends BasePresenter<ChatModel,ChatActivity> {
    public ChatPresenter(ChatActivity view) {
        super(view);
        currModel = new ChatModel();
    }

    @Override
    public void init() {
        //todo,do something
    }

}

  从这里可以看到,V与P二者是双向持有对方的关联的。 这就难免导致一些逻辑上的问题(比如到底以presenter为主还是以activity为主,方法的调用的逻辑应该怎么布局?)。我是这样解决的:

   整个app入口在manifest文件,而我们直接交互的界面在activity中。  所以说activity可以近似看成人机接口。 一个activity发生变化应该有三种方式: 第一,activity本身具有自身的生命周期方法; 第二,activity作为视图的载体,具有反馈事件回调的功能;第三,通过looper机制,结合hander可以完成一些其它的功能。hander的方式由于出现了很多的开源框架,因此不推荐使用,但是理解它是很有必要的。   因此,在整个布局的时候,将只存在两部分,以生命周期线索组织起来的代码逻辑;和 以事件回调线索组织起来的代码逻辑。

   也就是说,每一个功能性方法的入口都在activity,(视图)中,但是功能的实现逻辑则分布在presenter,P中。 因为它是资源的持有者,即持有V,也持有M。

    以上讨论的是解决V与P的关系。  但是我们的根本目的是为了解决M与V的关系。  为了完成二者同步,我们可以很自然的想到基于视图适配器的ListView,ListAdapter。 因为它可以不用频繁切换场景,直接定义好视图绑定规则,即可完成很多的操作。 但是针对单页的适配(或者说普通的视图适配)并没有相关解决方案,但是通过类比适配器的源码,我们可以抽象出一个视图持有者,基于它来完成类似适配器的功能。

ViewHoler抽象:

package com.automannn.meimeijiong.adapter;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

public class ViewHolder {
        //代表视图 dto的根部局
        private View currView;

        public ViewHolder(View view){
            this.currView = view;
            currView.setTag(this);
        }

        public <T extends  View> T getView(int id){
            T t = (T) currView.findViewById(id);

            return t;
        }

        public View getRootView() {
            return currView;
        }


        /**
         * 设置文字
         */
        public ViewHolder setText(int id, CharSequence text) {
            View view = getView(id);
            if (view instanceof TextView) {
                ((TextView) view).setText(text);
            }
            return this;
        }

        /**
         * 设置图片
         */
        public ViewHolder setImageResource(int id, int drawableRes) {
            View view = getView(id);
            if (view instanceof ImageView) {
                ((ImageView) view).setImageResource(drawableRes);
            } else {
                view.setBackgroundResource(drawableRes);
            }
            return this;
        }

        public ViewHolder setImageResourceFromBitmap(int id, Bitmap bitmap) {
            View view = getView(id);
            if (view instanceof ImageView) {
                ((ImageView) view).setImageBitmap(bitmap);
            } else {
                Drawable drawable = new BitmapDrawable(bitmap);
                view.setBackground(drawable);
            }
            return this;
        }


        /**
         * 设置可见
         */
        public ViewHolder setVisibility(int id, int visible) {
            getView(id).setVisibility(visible);
            return this;
        }

        /**
         * 设置标签
         */
        public ViewHolder setTag(int id, Object obj) {
            getView(id).setTag(obj);
            return this;
        }

        public void invalidate(){
            this.currView.invalidate();
        }

}

   它的来源是,将当前Activity的整体充当一个View的根部局,然后建立一个引用,与之相关的所有的同步操作均由此类完成。通过该抽象我们可以完成类似双向绑定的功能,只不是不是响应式的,需要我们手动控制。 这样就会减少很多因视图同步所需要的写的冗余代码。

下面举个完整例子展示:

    

布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/user_nickname"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="昵称"/>
    <EditText
        android:id="@+id/user_realname"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="真实姓名"/>
    <EditText
        android:id="@+id/user_age"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"
        android:hint="年龄"/>
    <EditText
        android:id="@+id/user_sex"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="性别"/>
    <EditText
        android:id="@+id/user_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="邮箱"/>
    <Button
        android:id="@+id/user_submit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="提交"/>
</LinearLayout>

activity视图:

package automannn.com.testmvp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import automannn.com.testmvp.presenter.UserPresenter;
import automannn.com.testmvp.view.api.ISingleModelView;
import automannn.com.testmvp.view.api.IView;

public class MainActivity extends BaseActivity<UserPresenter> implements ISingleModelView {
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onPrepare() {
        currentPresenter.init();
    }

    @Override
    protected UserPresenter initPresent() {
        return new UserPresenter(this);
    }

    @Override
    protected int getLayout() {
        return R.layout.activity_main;
    }

    @Override
    protected void initView() {
        button =findViewById(R.id.user_submit);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                currentPresenter.submit();
            }
        });
    }

    @Override
    public View getRootView() {
        return getWindow().getDecorView();
    }

    public void showToast(String msg){
        Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
    }
}

model:

package automannn.com.testmvp.model;

import android.view.View;
import android.widget.Adapter;
import android.widget.TextView;

import automannn.com.testmvp.R;
import automannn.com.testmvp.adapter.ViewHolder;
import automannn.com.testmvp.entity.User;
import automannn.com.testmvp.model.api.ISingleModel;


public class UserModel implements ISingleModel<User> {
    private User data;

    private ModelViewHolder modelViewHolder;

    @Override
    public User getdata() {
        return data;
    }

    @Override
    public Adapter getAdapter() {
        return null;
    }

    @Override
    public void init() {

    }

    @Override
    public void setCurrentData(User o) {
        data =o;
    }

    @Override
    public void myNotifyDataSetChanged() {
        modelViewHolder.getViewHolder().invalidate();
    }

    @Override
    public void initModelViewHolder(View rootView) {
        modelViewHolder = new ModelViewHolder(rootView);
    }

    @Override
    public void M2VbindModelViewHolder() {
        ViewHolder viewHolder = modelViewHolder.getViewHolder();
        viewHolder.setText(R.id.user_nickname,data.getNickName());
        viewHolder.setText(R.id.user_realname,data.getRealName());
        viewHolder.setText(R.id.user_sex,data.getSex());
        viewHolder.setText(R.id.user_age,data.getAge()==null?"":data.getAge()+"");
        viewHolder.setText(R.id.user_email,data.getEmail());
    }

    @Override
    public void V2MbindModelViewHolder() {
        ViewHolder viewHolder = modelViewHolder.getViewHolder();
        data.setNickName(((TextView)viewHolder.getView(R.id.user_nickname)).getText().toString());
        data.setRealName(((TextView)viewHolder.getView(R.id.user_realname)).getText().toString());
        data.setSex(((TextView)viewHolder.getView(R.id.user_sex)).getText().toString());
        data.setAge(Integer.valueOf(((TextView)viewHolder.getView(R.id.user_age)).getText().toString()));
        data.setEmail(((TextView)viewHolder.getView(R.id.user_email)).getText().toString());
    }

}

presenter:

package automannn.com.testmvp.presenter;

import automannn.com.testmvp.MainActivity;
import automannn.com.testmvp.entity.User;
import automannn.com.testmvp.model.UserModel;

public class UserPresenter extends BasePresenter<UserModel,MainActivity> {
    public UserPresenter(MainActivity view) {
        super(view);
        currModel= new UserModel();
    }
    
    //生命周期的核心业务逻辑在此处书完成
    @Override
    public void init() {
        //设置数据源(注意,该数据源有可能来自任意的位置)
        currModel.setCurrentData(new User());
        //初始化
        currModel.initModelViewHolder(currView.getRootView());
    }
    public void submit() {
        currModel.V2MbindModelViewHolder();
        currView.showToast(currModel.getdata().toString());
        //新建一个模型,模拟用于同步视图
        currModel.setCurrentData(new User());
        currModel.M2VbindModelViewHolder();
    }
}

 ------------------------------------------------

     在单页的状态下,model充当了视图模型的作用,可以类似完成双向绑定的效果,所有的操作可以面向数据对象,而不是面向视图组件。  在一定程度上减少了相当的冗余代码。 同时,对于后期的修改升级与维护,逻辑还算清晰。   

      对于具有集合数据的页面,大体上差不多。 只是相应的数据源会发生一些改变。 集合数据的页面最终还是会以跳转到单页页面为结果,此时单页页面的数据源就是从intent中接收,而非重新实例化。  

      demo在github的位置:这里  (在androidManifest.xml中将入口改成User对应的activity就可以了。)

      为了防止由于时间的流逝而使得自己曾经的思考付诸流水,特此记录!

猜你喜欢

转载自blog.csdn.net/qq_36285943/article/details/84113123