Android MVP Clean架构

todo‑mvp‑clean是Google的Android Architecture Blueprints(Android架构蓝图)中的一个用MVP架构和Clean架构来构架APP的demo,这篇文章主要是看完官方demo以后,谈谈对Android中使用MVP+Clean的理解

浅谈Android架构——MVP:https://www.jianshu.com/p/9050e3dbe513

对mvp-clean的理解

todo-mvp-clean架构模式是基于标准MVP架构和Clean Architecture的概念相结合来设计实现的。

df56fc6b-69dc-45e2-ba01-50d585b31625.png-69.9kB
df56fc6b-69dc-45e2-ba01-50d585b31625.png-69.9kB
  • Presentation Layer:其实就是MVP架构下的V层和P层
  • Domain Layer:抽象出来的业务逻辑,每个Use Case(用例)代表一个业务,通过Use Case实现P层和M层的通信
  • Data Layer:数据操作实现层,其实就是MVP架构下的M层

其实对比MVP架构,View层和Medle层是没有改变的,只是多了Domain Layer这层,是Presenter层和Data层之间通信的桥梁,并且对数据进行了业务处理;这一层是通过一个个Use Case组成的,代表每一个业务逻辑,更方便代码的复用和维护;数据操作的细节由Data层实现;从而进一步将Presenter层和Data层解耦。

对比MVP架构,我能理解的就是把P层的业务逻辑转移到了Domain Layer中,而且每一个业务逻辑都可以新建一个Use Case,细化了业务,而且提高了代码的重用性;

具体实现

Google的demo看着打一遍差不多就能理解意思了,但是项目用了Dagger注入框架和其他测试框架,看起来可能比较麻烦,就自己写了一个简单的,便于理解;栗子还是和上篇MVP框架一样,只是新增了一个Domain Layer层,所以只展示其创建过程和其他改变;功能:

  • 选择图片保存图片地址到数据库
  • 获取数据库中的图片展示
  • 删除数据库中的图片;
mulu.png

可以看出新增了几个类,首先是UseCaseUseCaseHandler,就是对业务逻辑的一个抽象和管理;UseCase抽象出请求参数、返回参数、数据返回回调接口

public abstract class UseCase<Q extends UseCase.RequestValues, P extends UseCase.ResponseValue> {

    //请求参数
    private Q mRequestValues;

    //返回监听
    private UseCaseCallback<P> mUseCaseCallback;

    //执行业务
    void run() {
        executeUseCase(mRequestValues);
    }
    
    //执行的具体方法
    protected abstract void executeUseCase(Q requestValues);

    /**
     * 请求参数
     */
    public interface RequestValues {

    }

    /**
     * 返回参数
     */
    public interface ResponseValue {

    }
    //返回回调
    public interface UseCaseCallback<R> {
        void onSuccess(R response);
        void onError();
    }
}

UseCaseHandler是对UseCase进行管理,使用到UseCaseSchedulerUseCaseThreadPoolScheduler这两个类来进行线程的调度,UseCaseScheduler抽象出方法,UseCaseThreadPoolScheduler实现方法,在线程池中执行异步任务;任务的结果回调用Handler post的方式来切换到主线程

UseCaseHandler进行分析,其实就两个部分:执行任务、返回结果,还有使用了线程调度来管理整个过程

处理结果

在主线程中处理结果和异常方法

 /**
     * 返回数据
     *
     * @param response
     * @param useCaseCallback
     * @param <V>
     */
    public <V extends UseCase.ResponseValue> void notifyResponse(final V response,
                                                                 final UseCase.UseCaseCallback<V> useCaseCallback) {
        mUseCaseScheduler.notifyResponse(response, useCaseCallback);
    }


    /**
     * 返回错误
     *
     * @param useCaseCallback
     * @param <V>
     */
    private <V extends UseCase.ResponseValue> void notifyError(final UseCase.UseCaseCallback<V> useCaseCallback) {
        mUseCaseScheduler.onError(useCaseCallback);
    }

执行业务方法

传入请求参数和回调参数,并给UseCase设置回调UiCallbackWrapper,然后在线程池中执行具体的业务方法

public <T extends UseCase.RequestValues, R extends UseCase.ResponseValue> void execute(
            final UseCase<T, R> useCase, T values, UseCase.UseCaseCallback<R> callback) {
        useCase.setRequestValues(values);
        useCase.setUseCaseCallback(new UiCallbackWrapper(callback, this));
        mUseCaseScheduler.execute(new Runnable() {
            @Override
            public void run() {
                useCase.run();
            }
        });
    }

UiCallbackWrapper

继承UseCase.UseCaseCallback,目的是执行完业务后,UseCase执行回调就会调用UseCaseHandler的两个在主线程中处理结果的方法

    private static final class UiCallbackWrapper<V extends UseCase.ResponseValue> implements UseCase.UseCaseCallback<V> {

        private final UseCase.UseCaseCallback<V> mCallback;
        private final UseCaseHandler mUseCaseHandler;

        public UiCallbackWrapper(UseCase.UseCaseCallback<V> callback, UseCaseHandler useCaseHandler) {
            mCallback = callback;
            mUseCaseHandler = useCaseHandler;
        }

        @Override
        public void onSuccess(V response) {
            mUseCaseHandler.notifyResponse(response, mCallback);
        }

        @Override
        public void onError() {
            mUseCaseHandler.notifyError(mCallback);
        }
    }

主要的东西就在这个类里面,其实就是使用了命令模式,对UseCase进行了统一的管理,维护了一个UseCaseThreadPoolScheduler 对象来执行异步任务并在UI线程返回结果

具体使用

创建UseCase

每个业务就应该新建一个UseCase,按照Clean的原则,UseCase是业务逻辑层,是由纯Java来实现的,不应该有Android库的依赖;保存图片地址到数据库为例,新建一个AddPicture用例继承UseCase

public class AddPicture extends UseCase<AddPicture.RequestValues, AddPicture.ResponseValue> 
实现请求参数接口

用一个内部类实现请求参数接口,保存图片,图片是一个File对象

public static final class RequestValues implements UseCase.RequestValues {
        File pictureFile;

        public RequestValues(File pictureFile) {
            this.pictureFile = pictureFile;
        }

        public File getPictureFile() {
            return pictureFile;
        }
    }
实现返回参数接口

返回数据是一个定义的Picture对象,用于更新界面;

public static final class ResponseValue implements UseCase.ResponseValue {
        Picture mPicture;

        public ResponseValue(Picture picture) {
            mPicture = picture;
        }

        public Picture getPicture() {
            return mPicture;
        }
    }
实现执行方法

执行方法其实就是在数据库中插入一条数据

@Override
    protected void executeUseCase(RequestValues requestValues) {
        Picture picture = new Picture(requestValues.getPictureFile());
        mLocalDataSource.savePic(picture);
        getUseCaseCallback().onSuccess(new ResponseValue(picture));
    }

在Persenter中实现数据获取

P层直接调用mUseCaseHandler执行业务方法,获取到数据给View更新界面

 @Override
    public void addPic(File file) {
        AddPicture.RequestValues requestValues=new AddPicture.RequestValues(file);
        mUseCaseHandler.execute(mAddPicture, requestValues, new UseCase.UseCaseCallback<AddPicture.ResponseValue>() {
            @Override
            public void onSuccess(AddPicture.ResponseValue response) {
                mPicturesActivity.addPic(response.getPicture());
            }

            @Override
            public void onError() {

            }
        });
    }

以选择图片,保存到数据库这个功能来看,调用系统相册选择图片这一步属于数据的获取,但更是界面操作,之前MVP,我封装了图片选择工具,把图片选择写在了P层,现在P层不写业务逻辑,UseCase是纯Java的业务逻辑,所以其实应该是写在View层;

执行过程分析

首先在View层选择图片以后,调动Persenter方法

 PicturePickUtil.pick(PicturesActivity.this, new OnPickListener() {
                    @Override
                    public void pickPicture(File file) {
                        mPresenter.addPic(file);
                    }
                });

在Presenter里面,根据File构建出请求参数,创建AddPicture对象,执行mUseCaseHandler.execute()方法;


//创建用例
mAddPicture=new AddPicture(mLocalDataSource);

@Override
    public void addPic(File file) {
        AddPicture.RequestValues requestValues=new AddPicture.RequestValues(file);
        mUseCaseHandler.execute(mAddPicture, requestValues, new UseCase.UseCaseCallback<AddPicture.ResponseValue>() {
            @Override
            public void onSuccess(AddPicture.ResponseValue response) {
                mPicturesActivity.addPic(response.getPicture());
            }

            @Override
            public void onError() {

            }
        });
    }

mUseCaseHandler.execute()的方法就是给mAddPicture设置了请求参数和返回回调,并异步执行mAddPicture.run()方法

public <T extends UseCase.RequestValues, R extends UseCase.ResponseValue> void execute(
            final UseCase<T, R> useCase, T values, UseCase.UseCaseCallback<R> callback) {
        useCase.setRequestValues(values);
        useCase.setUseCaseCallback(new UiCallbackWrapper(callback, this));
        mUseCaseScheduler.execute(new Runnable() {
            @Override
            public void run() {
                useCase.run();
            }
        });
    }

mAddPicture.run()方法执行了mAddPicture.executeUseCase()方法,也就是保存数据,并且生成返回数据执行getUseCaseCallback().onSuccess()回调方法;

@Override
    protected void executeUseCase(RequestValues requestValues) {
        Picture picture = new Picture(requestValues.getPictureFile());
        mLocalDataSource.savePic(picture);
        getUseCaseCallback().onSuccess(new ResponseValue(picture));
    }

getUseCaseCallback().onSuccess()回调方法就是UseCaseHandler中的内部类UiCallbackWrapper中的方法

  @Override
        public void onSuccess(V response) {
            mUseCaseHandler.notifyResponse(response, mCallback);
        }

也就是执行了mUseCaseHandler.notifyResponse(response, mCallback)方法

 public <V extends UseCase.ResponseValue> void notifyResponse(final V response,
                                                                 final UseCase.UseCaseCallback<V> useCaseCallback) {
        mUseCaseScheduler.notifyResponse(response, useCaseCallback);
    }

也就是执行了mUseCaseScheduler.notifyResponse方法

@Override
    public <V extends UseCase.ResponseValue> void notifyResponse(final V response, final UseCase.UseCaseCallback<V> useCaseCallback) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                useCaseCallback.onSuccess(response);
            }
        });
    }

到了这里,就是在主线程中执行了返回数据的方法,就是在Presenter里面new的这个UseCaseCallback返回了数据

mUseCaseHandler.execute(mGetPictures, new GetPictures.RequestValues(), new UseCase.UseCaseCallback<GetPictures.ResponseValue>() {
            @Override
            public void onSuccess(GetPictures.ResponseValue response) {
                mPicturesActivity.showPic(response.getPictures());
            }

            @Override
            public void onError() {

            }
        });

最后交给mPicturesActivity进行更新界面,整个流程完成

 //添加图片
    @Override
    public void addPic(Picture picture) {
        mPictureAdapter.insertData(0, picture);
    }

个人感觉这个架构还是很有用的,做业务逻辑比较多的APP还是很适合的,反正就是低耦合,每个业务都是独立的,复用性很高;如果配合上dagger依赖注入框架就更能感受到低耦合了;缺点就是每个小的逻辑都可以创建一个UseCase会创建更多的类

项目地址:https://github.com/tyhjh/AndroidMvp

推荐阅读更多精彩内容