android中使用VIPER-clean架构

一、简介

VIPER 是视图 (View),交互器 (Interactor),主持人 (Presenter),实体 (Entity) 以及路由 (Routing) 的首字母缩写,各个部分职责明确,遵循单一职责原则,在iOS开发中应用较早。
在这里插入图片描述

VIPER-clean是由VIPER改进而来的方案,是基于The Clean Architecture的思想,可以参考和阅读Uncle Bob的《整洁架构之道》。随着项目需求的增加,可以使用更多的usecase(用例)来处理逻辑,不会显著增加单个类的代码量,使得代码看起来整洁,易于维护。
在这里插入图片描述

二、经验

如果一个页面,承载了太多的逻辑,比如: 音视频页面、派对房等,属于功能超多的页面,可以使用这个架构。如果是简单的页面,比如: 登录、注册或者列表展示的页面,使用常规的mvc架构,就足够了,代码也不会乱到哪里去。对于小团队来说,可以灵活点。就像Uncle Bob说的,“不能嫁给框架”。

三、基类

  1. BaseView
public interface BaseView<T extends BasePresenter> {
    void setPresenter(T presenter);
}
  1. BaseInteractor
public interface BaseInteractor {
}
  1. BasePresenter
public interface BasePresenter {
}
  1. BaseRouter
public interface BaseRouter {
}
  1. ThreadUtils
public class ThreadUtils {
    public static void runOnUI(Runnable runnable){
        if (runnable==null){
            return;
        }
        if (Looper.myLooper()!=Looper.getMainLooper()){
            new Handler(Looper.getMainLooper()).post(runnable);
        }else{
            runnable.run();
        }
    }
}
  1. UICallBackWrapper
public class UICallBackWrapper<P> implements BaseUseCase.UseCaseCallback<P> {

    private final BaseUseCase.UseCaseCallback<P> mCallback;

    public UICallBackWrapper(BaseUseCase.UseCaseCallback<P> mCallback) {
        this.mCallback = mCallback;
    }

    @Override
    public void onSuccess(final P response) {
        ThreadUtils.runOnUI(new Runnable() {
            @Override
            public void run() {
                mCallback.onSuccess(response);
            }
        });
    }

    @Override
    public void onError(final String error) {
        ThreadUtils.runOnUI(new Runnable() {
            @Override
            public void run() {
                mCallback.onError(error);
            }
        });
    }
}
  1. BaseUseCase
public abstract class BaseUseCase<Q,P> {
    private Q mRequestValues;
    private UseCaseCallback<P> mUseCaseCallback;

    /**
     * 若包含多个请求数据,则需要封装传入
     * @param requestValues
     * @return
     */
    @CallSuper
    public BaseUseCase<Q,P> setRequestValues(Q requestValues){
        mRequestValues=requestValues;
        return this;
    }

    public Q getRequestValues(){
        return mRequestValues;
    }

    protected UseCaseCallback<P> getUseCaseCallback(){
        return mUseCaseCallback;
    }

    /**
     * 数据处理结束后的回调
     * @param useCaseCallback
     * @return
     */
    public BaseUseCase<Q,P> setUseCaseCallback(UseCaseCallback<P> useCaseCallback){
        mUseCaseCallback=new UICallBackWrapper<>(useCaseCallback);
        return this;
    }

    /**
     * 可在任意线程执行,回调时返回主线程
     */
    public void run(){
        executeUseCase(mRequestValues);
    }

    /**
     * 处理数据
     * @param requestValues
     */
    protected abstract void executeUseCase(Q requestValues);

    public interface RequestValues{}
    public interface ResponseValues{}
    public interface UseCaseCallback<P>{
        void onSuccess(P response);
        void onError(String error);
    }
}

四、练习

做一个简单的项目,左边是页面功能,右边是项目代码结构,如下图:
在这里插入图片描述

  1. activity_main.xml
<?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:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/ll_demand_1"
        android:layout_margin="20dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:textColor="@android:color/black"
            android:text="需求1: 1 + 1 =  "/>

        <TextView
            android:id="@+id/tv_result_1"
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:textSize="20dp"
            android:textColor="@android:color/black"
            android:text="__"/>

        <Button
            android:id="@+id/btn_1"
            android:onClick="clickBtn1"
            android:layout_marginLeft="30dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始计算"/>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/ll_demand_2"
        android:layout_margin="20dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:textColor="@android:color/black"
            android:text="需求2: 3 * 3 =  "/>

        <TextView
            android:id="@+id/tv_result_2"
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:textSize="20dp"
            android:textColor="@android:color/black"
            android:text="__"/>

        <Button
            android:id="@+id/btn_2"
            android:onClick="clickBtn2"
            android:layout_marginLeft="30dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始计算"/>
    </LinearLayout>

    <LinearLayout
        android:layout_margin="20dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:textColor="@android:color/black"
            android:text="需求3:100内 =  "/>

        <TextView
            android:id="@+id/tv_result_3"
            android:layout_weight="1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:textSize="20dp"
            android:textColor="@android:color/black"
            android:text="__"/>

        <Button
            android:id="@+id/btn_3"
            android:onClick="clickBtn3"
            android:layout_marginLeft="30dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始随机"/>
    </LinearLayout>

    <Button
        android:id="@+id/btn_detail"
        android:onClick="clickBtnDetail"
        android:layout_marginLeft="30dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="跳转到详情"/>

</LinearLayout>
  1. MainActivity
public class MainActivity extends AppCompatActivity implements MContact.View {

    private TextView tvResult1;
    private TextView tvResult2;
    private TextView tvResult3;
    private MContact.Presenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        setPresenter(new MPresenter(new MInteractor(),this,new MRouter()));
    }

    private void initView(){
        tvResult1 =findViewById(R.id.tv_result_1);
        tvResult2 =findViewById(R.id.tv_result_2);
        tvResult3=findViewById(R.id.tv_result_3);
    }

    public void clickBtn1(View v){
        mPresenter.calculateFirst();
    }

    public void clickBtn2(View v){
        mPresenter.calculateSecond();
    }

    public void clickBtn3(View v){
        mPresenter.exeRandom();
    }

    public void clickBtnDetail(View v){
        mPresenter.openDetail();
    }

    @Override
    public Context getContext() {
        return this;
    }

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

    @Override
    public void setFirstView(int result) {
        tvResult1.setText(String.valueOf(result));
    }

    @Override
    public void setSecondView(int result) {
        tvResult2.setText(String.valueOf(result));
    }

    @Override
    public void setThirdView(int result) {
        tvResult3.setText(String.valueOf(result));
    }

    @Override
    public void setPresenter(MContact.Presenter presenter) {
        this.mPresenter=presenter;
    }
}
  1. MContact
public class MContact {

    interface Presenter extends BasePresenter{
        /** 计算第一个*/
        void calculateFirst();
        /**计算第二个*/
        void calculateSecond();
        /**随机*/
        void exeRandom();
        /** 跳转到详情*/
        void openDetail();
    }

    interface View extends BaseView<Presenter>{
        Context getContext();
        void showToast(String msg);
        void setFirstView(int result);
        void setSecondView(int result);
        void setThirdView(int result);
    }

    interface Interactor extends BaseInteractor{
        FirstUseCase getFirstUseCase();
        SecondUseCase getSecondUseCase();
        ThirdUseCase getThirdUseCase();
    }

    interface Router extends BaseRouter{
        void openDetailActivity(Context context);
    }

}
  1. MInteractor
public class MInteractor implements MContact.Interactor {

    private FirstUseCase firstUseCase;
    private SecondUseCase secondUseCase;
    private ThirdUseCase thirdUseCase;

    @Override
    public FirstUseCase getFirstUseCase() {
        if (firstUseCase==null){
            synchronized(this){
                if (firstUseCase==null){
                    firstUseCase=new FirstUseCase();
                }
            }
        }
        return firstUseCase;
    }

    @Override
    public SecondUseCase getSecondUseCase() {
        if (secondUseCase==null){
            synchronized(this){
                if (secondUseCase==null){
                    secondUseCase=new SecondUseCase();
                }
            }
        }
        return secondUseCase;
    }

    @Override
    public ThirdUseCase getThirdUseCase() {
        if (thirdUseCase==null){
            synchronized(this){
                if (thirdUseCase==null){
                    thirdUseCase=new ThirdUseCase();
                }
            }
        }
        return thirdUseCase;
    }
}
  1. MPresenter
public class MPresenter implements MContact.Presenter {

    private MContact.Interactor mInteractor;
    private MContact.View mView;
    private MContact.Router mRouter;

    public MPresenter(MContact.Interactor mInteractor, MContact.View mView, MContact.Router mRouter) {
        this.mInteractor = mInteractor;
        this.mView = mView;
        this.mRouter = mRouter;
    }

    @Override
    public void calculateFirst() {
        mInteractor.getFirstUseCase().setRequestValues(new FirstUseCase.RequestValues(1,1))
                .setUseCaseCallback(new BaseUseCase.UseCaseCallback<FirstUseCase.ResponseValues>() {
                    @Override
                    public void onSuccess(FirstUseCase.ResponseValues response) {
                        //显示到屏幕上
                        mView.setFirstView(response.getResult());
                    }

                    @Override
                    public void onError(String error) {
                        mView.showToast(error);
                    }
                }).run();
    }

    @Override
    public void calculateSecond() {
        mView.showToast("延时计算...");
        mInteractor.getSecondUseCase().setRequestValues(new SecondUseCase.RequestValues(3,3))
                .setUseCaseCallback(new BaseUseCase.UseCaseCallback<SecondUseCase.ResponseValues>() {
                    @Override
                    public void onSuccess(SecondUseCase.ResponseValues response) {
                        mView.setSecondView(response.getResult());
                    }

                    @Override
                    public void onError(String error) {
                        mView.showToast(error);
                    }
                }).run();
    }

    @Override
    public void exeRandom() {
        mInteractor.getThirdUseCase().setRequestValues(new ThirdUseCase.RequestValues(100))
                .setCustomCallBack(new ThirdUseCase.CustomCallBack() {
                    @Override
                    public void ok(ThirdUseCase.ResponseValues result) {
                        mView.setThirdView(result.getResult());
                    }

                    @Override
                    public void fail(String err) {
                        mView.showToast(err);
                    }
                }).run();
    }

    @Override
    public void openDetail() {
        mRouter.openDetailActivity(mView.getContext());
    }


}
  1. MRouter
public class MRouter implements MContact.Router {
    @Override
    public void openDetailActivity(Context context) {
        Intent intent=new Intent(context,DetailActivity.class);
        context.startActivity(intent);
    }
}
  1. FirstUseCase
public class FirstUseCase extends BaseUseCase<FirstUseCase.RequestValues, FirstUseCase.ResponseValues> {

    @Override
    protected void executeUseCase(RequestValues requestValues) {
        //假设,不能大于1000,大的话报错
        int maxError=1000;
        if (requestValues.num1>maxError||requestValues.num2>maxError){
            getUseCaseCallback().onError("不能大于1000");
        }else{
            int result=requestValues.num1+requestValues.num2;
            getUseCaseCallback().onSuccess(new ResponseValues(result));
        }
    }

    public static class RequestValues implements BaseUseCase.RequestValues{
        private int num1;
        private int num2;

        public RequestValues(int num1, int num2) {
            this.num1 = num1;
            this.num2 = num2;
        }
    }
    public static class ResponseValues implements BaseUseCase.ResponseValues{
        private int result;

        public ResponseValues(int result) {
            this.result = result;
        }

        public int getResult() {
            return result;
        }
    }


}
  1. SecondUseCase
public class SecondUseCase extends BaseUseCase<SecondUseCase.RequestValues, SecondUseCase.ResponseValues> {

    @Override
    protected void executeUseCase(RequestValues requestValues) {
        //假设,不能大于1000,大的话报错
        int maxError=1000;
        if (requestValues.num1>maxError||requestValues.num2>maxError){
            getUseCaseCallback().onError("不能大于1000");
        }else{
            //假装耗时操作
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //等待2秒
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //开始计算
                    int result=requestValues.num1*requestValues.num2;
                    //这里会自动把线程切换到主线程,请看父类中的定义UICallBackWrapper
                    getUseCaseCallback().onSuccess(new ResponseValues(result));
                }
            }).start();
        }
    }

    public static class RequestValues implements BaseUseCase.RequestValues{
        private int num1;
        private int num2;

        public RequestValues(int num1, int num2) {
            this.num1 = num1;
            this.num2 = num2;
        }
    }
    public static class ResponseValues implements BaseUseCase.ResponseValues{
        private int result;

        public ResponseValues(int result) {
            this.result = result;
        }

        public int getResult() {
            return result;
        }
    }


}
  1. ThirdUseCase
/**
 * 如果BaseUseCase的回调方法满足不了需求,这里可以重新自定义回调方法
 */
public class ThirdUseCase extends BaseUseCase<ThirdUseCase.RequestValues, ThirdUseCase.ResponseValues> {

    @Override
    protected void executeUseCase(RequestValues requestValues) {
        //假设,不能大于1000,大的话报错
        int maxError=1000;
        if (requestValues.num1>maxError){
            //自定义回调
            getCustomCallBack().fail("不能大于1000");
        }else{
            int a = (int) (Math.random() * requestValues.num1);
            //自定义回调
            getCustomCallBack().ok(new ResponseValues(a));
        }
    }

    public static class RequestValues implements BaseUseCase.RequestValues{
        private int num1;

        public RequestValues(int num1) {
            this.num1 = num1;
        }
    }
    public static class ResponseValues implements BaseUseCase.ResponseValues{
        private int result;

        public ResponseValues(int result) {
            this.result = result;
        }

        public int getResult() {
            return result;
        }
    }

    @Override
    public ThirdUseCase setRequestValues(RequestValues requestValues) {
        super.setRequestValues(requestValues);
        return this;
    }

    /** 自定义回调方法*/
    public interface CustomCallBack{
        void ok(ResponseValues result);
        void fail(String err);
    }
    private CustomCallBack customCallBack;

    public ThirdUseCase setCustomCallBack(CustomCallBack callBack){
        this.customCallBack=callBack;
        return this;
    }

    private CustomCallBack getCustomCallBack(){
        return customCallBack;
    }

}
  1. 这里省去了DetailActivity,自己可以随便写一个Activity组件。

五、实践

每个页面有一个契约类*Contract,主要用来定义接口。优点:逻辑会比较清晰,便于维护。

  1. 以下是老项目的视频通话页面的契约类,仅供参考,如有不足之处,还请指教:
public class NvcContract {

    interface Presenter extends BasePresenter{
        /** 获取初始化数据*/
        void getVideoInitData();
        /** 权限获取成功*/
        void getPermissionSuccess();
        /** 调取聊天流程接口-调取完成了才能显示页面*/
        void startChatApi();
        /** 播放铃声*/
        void playRing();
        /** 停止播放铃声*/
        void stopRing();
        /** 接受邀请信令*/
        void acceptSignalInComing();
        /** 拒绝邀请信令*/
        void rejectSignalInComing();
        /** 取消信令*/
        void cancelSignalOutGoing();
        /** 我是主叫,对方接听电话*/
        void otherAcceptOutGoing();
        /** 我是主叫,对方拒绝电话*/
        void otherRejectOutGoing();
        /** 我是被叫,对方取消了邀请信令*/
        void otherCancelInComing();
        /** 信令超时未处理*/
        void timeOutHandleSignal();
        /** 调取handleChat接口*/
        void handleChatApi(int handleType,String remark);
        /** 挂断电话-还没有开始聊天就异常*/
        void hungUpBeforeEnterRoom(String error);
        /** 初始化房间*/
        void initRtcRoom();
        /** 正常进入房间*/
        void enterRtcRoom();
        /** 加入房间的人是否是对方*/
        void checkCorrectSession(int uid);
        /** 开始计时*/
        void startVideoTimeCount();
        /** 计算通话费用*/
        void calculateAvConsumption(int minuteDuration);
        /** 本机有来电*/
        void phoneSelfBusy();
        /** 销毁房间*/
        void destroyRoom();
        /** 大小屏幕切换*/
        void switchSmallAndBigVideo();
        /** 获取大图视频流*/
        SurfaceView getRemoteSurfaceView();
        /** 设置默认的视图-从后台恢复页面*/
        void recoverActivityVideoView();
        /** 在房间内挂断电话*/
        void clickHungUpInRoom();
        /** 切换前后摄像头*/
        void cameraSwitch();
        /** 切换喇叭-听筒和喇叭切换*/
        void speakerSwitch();
        /** 展示礼物*/
        void showGiftPop();
        /** 展示充值页面*/
        void showCandyPop();
        /** 调取finishChat接口*/
        void finishChatApi(String remark);
    }

    interface View extends BaseView<Presenter>{
        /** 获取activity*/
        Activity getMyActivity();
        /** 大图view*/
        FrameLayout getBigFrameVideo();
        /** 小图view*/
        FrameLayout getSmallFrameVideo();
        /** 获取权限-必要权限不同意-挂断*/
        void getVideoPermission();
        /** 是来电还是去电*/
        boolean isIncomingType();
        /** 展示等待页面-拨出-拨进*/
        void showWaitView(String headUrl,String name,int price);
        /** 禁止用户点击按钮-对方已经取消*/
        void forbidClickWaitView();
        /** 展示视频通话页面*/
        void showRoomView();
        /** 设置通话时长*/
        void setChatTotalTime(String totalTime);
        /** 余额不足提示*/
        void showGlobalDiamondTip();
        /** 关闭activity*/
        void finishView();
        /** 开启悬浮窗服务*/
        void openVideoFloatWindow();
        /** 取消悬浮窗-展示activity*/
        void removeFloatWindowAndEnterActivity();
        /** 监听home按键-需要在进入房间之后监听*/
        void listenHomeBtn();
        /** 解绑service*/
        void myUnbindService();
        void showMyWaitDialog(String msg);
        void hideMyWaitDialog();
        void showToast(String msg,boolean isLongTime);
    }

    interface Interactor extends BaseInteractor{
        void destroy();
        /** 初始化数据的用例*/
        InitDataUseCase getInitDataUseCase();
        /** 调取开始接口的用例*/
        StartChatApiUseCase getStartChatApiUseCase();
        /** 检查余额的用例*/
        CheckAmountUseCase getCheckAmountUseCase();
        /** 调取通话状态接口的用例*/
        HandleChatApiUseCase getHandleChatApiUseCase();
        /** RTC房间的用例*/
        RtcRoomUseCase getRtcRoomUseCase();
        /** 调用结束通话接口的用例*/
        FinishChatApiUseCase getFinishChatApiUseCase();
        /** 计算通话时长的用例*/
        CallTimeCountUseCase getCallTimeCountUseCase();
        /** 通话时长消费金额的用例*/
        AvConsumptionUseCase getAvConsumptionUseCase();
        /** 礼物消费的用例*/
        GiftConsumeUseCase getGiftConsumeUseCase();
    }

    interface Router extends BaseRouter{
        /** 更新用户个人信息*/
        void runUserInfoService(Context context);
        /** 打开评论页面*/
        void openCommentActivity(Context context, AvExtra avExtra);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值