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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容