简介
VIPER 是视图 (View),交互器 (Interactor),主持人 (Presenter),实体 (Entity) 以及路由 (Routing) 的首字母缩写,各个部分职责明确,遵循单一职责原则。 
VIPER是基于The Clean Architecture的思想,改进而来的方案,可以参考Uncle Bob的《整洁架构之道》。随着项目需求的增加,可以使用更多的usecase(用例),不会增加单个类的代码量,使得代码看起来整洁,易于维护。 
经验
如果一个页面,承载了太多的逻辑,比如: 音视频页面、派对房等,属于功能超多的页面,可以使用这个架构。如果是简单的页面,比如: 登录、注册或者列表展示的页面,使用常规的mvc架构,就足够了,代码也不会乱到哪里去。对于小团队来说,可以灵活点。就像Uncle Bob说的,不能嫁给框架。
定义基础类
- BaseView
public interface BaseView<T extends BasePresenter> {
void setPresenter(T presenter);
}
- BaseInteractor
public interface BaseInteractor {
}
- BasePresenter
public interface BasePresenter {
}
- BaseRouter
public interface BaseRouter {
}
- 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();
}
}
}
- 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);
}
});
}
}
- 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);
}
}
练习
做一个简单的项目,右边是项目结构,如图: 
- 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>
- 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;
}
}
- 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);
}
}
- 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;
}
}
- 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());
}
}
- MRouter
public class MRouter implements MContact.Router {
@Override
public void openDetailActivity(Context context) {
Intent intent=new Intent(context,DetailActivity.class);
context.startActivity(intent);
}
}
- 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;
}
}
}
- 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;
}
}
}
- 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;
}
}
实践
每个页面有一个契约类*Contract,主要用来定义接口。优点:逻辑会比较清晰,便于维护。
|