Android Annotations+Retrofit+Rxjava2+okhttp3+MVP框架搭建

  • 前言
  • AndroidAnnotations与注解框架对比
      • 1、ButterKnife(编译时注解)
      • 2、XUtils(运行时注解)
      • 3、AndroidAnnotations(编译时注解)
      • 4、注解框架对比
  • Retrofit+OkHttp
  • RxJava
  • MVP搭建
    • 1、Retrofit+Okhttp封装
    • 2、BaseView、BasePresenter
    • 3、使用RxJava配合Retrofit进行请求:
    • 4、使用RxJava管理请求
  • 使用
  • 总结

前言

  之前一直在qq空间和公众号发布文章,这几天才转移到博客平台。对博客平台的各种规则还不是特别熟悉,属于博客小白。但是我会坚持更新,将实用的,易懂的文章推送给大家。感谢大家的点赞和关注。
  上一篇文章 《Android Gradle统一管理打包》收到了很多点赞和评论。Gradle统一管理在项目体量较小的情况下是不必要的,但是如果体量稍大,或者使用了插件化框架。那么它的好处就会显示出来,在上一篇的基础上,继续进行探索,梳理流行框架Annotations+Retrofit+Rxjava2+okhttp3+MVP框架的搭建。
  而Annotations+Retrofit+Rxjava2+okhttp3+MVP框架又和gradle有什么关系呢?
  本来想通过一篇 Small插件化+Annotations+Retrofit+Rxjava2+okhttp3+MVP 来描述在使用插件化的情况下moudle过多的情况,但是Small插件化是一个很重要的东西,如果全部混在一起写会造成篇幅过长或者内容过于杂乱。所以本篇抛开Small插件化只描述Annotations+Retrofit+Rxjava2+okhttp3+MVP的搭建。下一篇博文再对Small插件化和此框架进行融合。

AndroidAnnotations与注解框架对比

注解(Annotation)也叫元数据。是一种代码级别的说明。它与类、接口、枚举在同一个层次。可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。说白了就是一种标记,可以用自己的注解解释器在编译时进行处理(编译时注解),或者在运行时通过反射对其进行处理(运行时注解)。注解框架有很多,常用的有ButterKnife、AndroidAnnotations、XUtils等。

1、ButterKnife(编译时注解)

ButterKnfie出自Jakewharton大神,是使用非常多的一个注解框架。
ButterKnife通过反射在编译时生成一个ViewBinder对象,类似于:

MainActivity$ViewBinder implements ButterKnife.ViewBinder

在这个类中就有使用注解声明的一些组件和监听。
因为ButterKnife在编译时通过反射一次性生成了这些需要使用到的类,所以对运行时的速度是没有什么影响的。

2、XUtils(运行时注解)

XUtils和ButterKnife类似,都使用了反射来对注解声明的字段或者对象进行赋值。
区别在于XUtils是在运行时进行反射,众所周知反射的效率相对于原生代码是较慢的。
虽然设备性能在提升,但是在组件、事件过多的情况下还是造成初始化速度降低。

3、AndroidAnnotations(编译时注解)

AndroidAnnotations和ButterKnife类似,都是编译时注解。所以在性能方面不相上下,但是两者的实现思路却不同:
ButterKnfie使用反射来对注解标记的字段赋值。而AndroidAnnotations会生成一个继承于当前类的子类来对标记的字段进行赋值。
AndroidAnnotations生成的类后面都会带有一个符号“_”,运行的时候也运行的是这个生成的子类,而不是我们的当前类。
看一下AndroidAnnotations生成的代码就能够很清楚的了解它的思路,首先我们看一下使用了注解的StartActivity中的代码。
StartActivity.java

//使用EActivity注解加载布局
@EActivity(R.layout.activity_start)
public class StartActivity extends BaseActivity implements StartContract.View {
    private StartContract.Presenter mPresenter;
    
    //使用ViewById注解查找组件
    @ViewById(R.id.cdpv_start)
    CountDownProgressView mCountDownView;            //倒计时组件
    private static final long mDelayTimeLong = 2000l;//倒计时毫秒

    //加载布局之前调用方法的注解
    @AfterInject
    void afterInject() {
        new StartPresenter(this);
        StatusBarUtil.immersive(this);
    }
    
    //加载布局之后调用方法的注解
    @AfterViews
    void afterViews() {
        mCountDownView.setTimeMillis(mDelayTimeLong);
        mCountDownView.start();
        countDown();
    }
    
    //运行在子线程的注解
    @Background(delay = mDelayTimeLong, id = "delay_to_main")
    void countDown() {
        startMain();
    }

    //运行在主线程的注解
    @UiThread
    void startMain() {
        if (UserStateUtil.getInstance().isLoggedOn()) {
            MainActivity_.intent(getContext()).start();
        } else {
            LoginActivity_.intent(getContext()).start();
        }
        finish();
    }
    
    //点击事件监听注解
    @Click(R.id.cdpv_start)
    void onCountDownClick() {
        BackgroundExecutor.cancelAll("delay_to_main", true);
        startMain();
    }

    @Override
    public void setPresenter(StartContract.Presenter presenter) {
        mPresenter = Null.checkNotNull(presenter);
    }

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        BackgroundExecutor.cancelAll("delay_to_main", true);
    }
}

上面的代码是启动页本类的实现,可以看到使用了AndroidAnnotations的一些常用注解。
而这些注解都会通过框架生成对应的代码,下面是通过本类生成的StartActivity_.java子类的部分代码:
StartActivity_ . java 部分代码

public final class StartActivity_
        extends StartActivity
        implements HasViews, OnViewChangedListener {
    private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
        init_(savedInstanceState);//此处调用了AfterInject
        super.onCreate(savedInstanceState);
        OnViewChangedNotifier.replaceNotifier(previousNotifier);
        setContentView(R.layout.activity_start);//此处通过EActivity加载
    }
    
    private void init_(Bundle savedInstanceState) {
        OnViewChangedNotifier.registerOnViewChangedListener(this);
        afterInject();
    }

    @Override
    public <T extends View> T internalFindViewById(int id) {
        return ((T) this.findViewById(id));
    }

    @Override
    public void setContentView(int layoutResID) {
        super.setContentView(layoutResID);
        onViewChangedNotifier_.notifyViewChanged(this);
    }
    
    //查找组件设置监听
    @Override
    public void onViewChanged(HasViews hasViews) {
        this.mCountDownView = hasViews.internalFindViewById(R.id.cdpv_start);
        if (this.mCountDownView != null) {
            this.mCountDownView.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View view) {
                           StartActivity_.this.onCountDownClick();
                        }
                   }
            );
        }
        afterViews();
    }
    
    //创建UI线程
    @Override
    void startMain() {
        UiThreadExecutor.runTask("", new Runnable() {

                    @Override
                    public void run() {
                        StartActivity_.super.startMain();
                    }
                }
                , 0L);
    }
    
    //创建子线程
    @Override
    void countDown() {
        BackgroundExecutor.execute(new BackgroundExecutor.Task("delay_to_main", 2000L, "") {
                   @Override
                   public void execute() {
                       try {
                           StartActivity_.super.countDown();
                       } catch (final Throwable e) {
                           Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
                       }
                   }
               }
        );
    }
}

从上面的代码可以看到,生成的StartActivity_子类中有本类定义的方法,还有对注解事件的实现。
原理大概了解了之后,就是AndroidAnnotations的使用了,使用AndroidAnnotations需要导入:

//AndroidAnnotations
implementation 'org.androidannotations:androidannotations:4.3.1'

在AndroidManifest中注册使用@EActivity标记的Activity需要使用框架生成的Activity,如:


<activity
    android:name=".moudle.login.LoginActivity_"
    android:screenOrientation="portrait" />**

同样,使用@EApplication标记的Application也需要使用框架生成的子类,如:

android:name=".application.XXXXApplication_"

Service也同样类似,都需要使用框架生成的子类。
AndroidAnnotations全部的注解文档在其GitHub上可以查阅:
https://github.com/androidannotations/androidannotations/wiki/AvailableAnnotations

4、注解框架对比

Android Annotations+Retrofit+Rxjava2+okhttp3+MVP框架搭建_第1张图片
这张图片引用自CSDN,Annotations使用的复杂度稍高于另外两个框架,并不是指接入特别困难。

Retrofit+OkHttp

Retrofit是一个请求框架,和Okhttp出自同一个团队,OkHttp负责进行底层的网络请求,Retrofit负责对请求事件进行装配,对请求结果进行解析。Retrofit对请求进行封装,很大程度上降低了代码的耦合程度。
引入:

    implementation 'com.squareup.okhttp3:okhttp:3.8.1'              //okhttp
    implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1' //okhttp拦截器
    implementation 'com.squareup.retrofit2:retrofit:2.0.2'          //retrofit
    implementation 'com.squareup.retrofit2:converter-gson:2.0.2'    //retrofit的Gson解析器
    implementation 'com.squareup.retrofit2:adapter-rxjava:2.0.2'    //retrofit的RxJava适配器支持
    implementation 'com.google.code.gson:gson:2.7'                  //Gson

RxJava

RxJava是一个很火的线程调度框架,这样描述或许不太准确,想要深入了解的可以打开传送门:
《给 Android 开发者的 RxJava 详解》
《RxJava 与 Retrofit 结合的最佳实践》
引入:

    implementation 'io.reactivex:rxandroid:1.0.1'                   //RxAndroid
    implementation 'io.reactivex.rxjava2:rxjava:2.1.1'              //RxJava

MVP搭建

使用AndroidAnnotations和Retrofit+Rxjava2+okhttp3都简化了代码的逻辑,降低了代码的耦合度,再引入MVP的架构,则会让项目更加清晰明了。MVP的概念已经很早就诞生了,经过了很长时间的发展,依然被很多人采用,存在即是合理,接下来就整个框架进行展示。

1、Retrofit+Okhttp封装

首先,需要对Retrofit和Okhttp进行初始化:

/**
 * @author WuYang
 * @version v1.0 设置网络请求
 */
public abstract class RetrofitSingleton {
    protected static ApiInterface apiService = null;
    public static Retrofit retrofit = null;
    public static OkHttpClient okHttpClient = null;
    
    //此处的ApiInterface就是通过注解定义请求接口的类
    public static void init() {
        initOkHttp();
        initRetrofit();
        if (retrofit != null)
            apiService = retrofit.create(ApiInterface.class);
    }
    
    //初始化OkHttp
    private static void initOkHttp() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        //如果不是发布版本,则初始化logging拦截器
        if (!TzcmApplication_.isRelease) {
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(loggingInterceptor);
        } else {
            Log.i("温馨提示", "反编译及攻击本软件将承担法律风险!请勿以身试法!");
        }
        //是否强制取消代理来防止抓包
        if (TzcmApplication_.isCancelProxy) {
            builder.proxy(Proxy.NO_PROXY);
        }
        //设置超时
        builder.connectTimeout(90, TimeUnit.SECONDS);
        builder.readTimeout(90, TimeUnit.SECONDS);
        builder.writeTimeout(90, TimeUnit.SECONDS);
        //错误重连、断线重连
        builder.retryOnConnectionFailure(false);
        //添加自定义请求头捕获器,如token等
        builder.addInterceptor(new AddCookiesInterceptor());
        okHttpClient = builder.build();
    }
    
    //初始化Retrofit
    private static void initRetrofit() {
        retrofit = new Retrofit.Builder()
                .baseUrl(XXXXApplication_.isRelease ? ApiInterface.API_HOST_ONLINE : ApiInterface.API_HOST_OFFLINE)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
    }
    
    //拦截器,添加一些固定的参数。如:token、deviceId、cookie等
    public static class AddCookiesInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request.Builder builder = chain.request().newBuilder();
            //添加token
            if (UserStateUtil.getInstance().isLoggedOn()) {
                builder.addHeader(CommonKey.ApiParams.TOKEN, UserStateUtil.getInstance().getToken());
            }
            return chain.proceed(builder.build());
        }
    }
}

接下来就创建ApiInterface:

/**
 * @author WuYang
 * @version v1.0 API接口
 */
public interface ApiInterface {
    String API_HOST_OFFLINE = "http://www.xxxxxxxx.com/";//线下的api 地址
    String API_HOST_ONLINE = "http://www.xxxxxxxx.com/";//线上地址

    String API_LOGIN = "ChujiMallServer/app/appDelivery/api/deliveryLogin";

    //用户登录
    @FormUrlEncoded
    @POST(API_LOGIN)
    Observable<UserInfoResponse> mLoginAPI(@FieldMap Map<String, Object> params);

定义好接口之后需要对接口声明的方法进行实现:

public class RetrofitApi extends RetrofitSingleton {
    private volatile static RetrofitApi instance;

    /**
     * Returns singleton class instance
     * 使用单例模式中的双重加锁
     */
    public static RetrofitApi getInstance() {
        if (instance == null) {
            synchronized (RetrofitApi.class) {
                if (instance == null) {
                    instance = new RetrofitApi();
                }
            }
        }
        return instance;
    }

    /**
     * 登录
     * @param account  账号,必填
     * @param password 密码,必填
     */
    public Observable login(String account, String password) {
        HashMap<String, Object> params = new HashMap<>();
        params.put(CommonKey.ApiParams.LOGIN_ID, account);
        params.put(CommonKey.ApiParams.PASSWORD, password);
        return apiService.mLoginAPI(params);
    }
}

然后再Application中进行init:

@EApplication
public class XXXXApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        initRetrofit();
    }

    private void initRetrofit() {
        RetrofitSingleton.init();
    }
}

2、BaseView、BasePresenter

定义接口BaseView和BasePresenter。

public interface BaseView<T extends BasePresenter> {
    //设置Presenter
    void setPresenter(T presenter);
    //获取上下文
    Activity getContext();
    //可以添加其余在View层的方法
    ... ...
}

public interface BasePresenter {
    //提供一个公用的开始方法
    void start();
    //可以添加一些其余公用的方法,如showLoading(),dismissLoading()等。
    ... ...
}

3、使用RxJava配合Retrofit进行请求:

/**
 * @author YangWu
 * @description 请求接口基类
 */
public abstract class OnRequestCallback<T extends BaseResponse> extends Subscriber<T> {

    public abstract void onFailed(int code, String respMsg, String respCode, T response);

    public abstract void onException(Throwable e);

    public abstract void onResponse(T response);

    public abstract void onFinish();

    public void onStart() {
        if (!RxNetTool.isAvailable(TzcmApplication_.getInstance())) {
            onFailed(-1, "网络连接异常,请检查网络连接", "E99999", null);
            onFinish();
            unsubscribe();
            return;
        }
    }

    public final void onCompleted() {
        onFinish();
    }

    public final void onError(Throwable e) {
        try {
            onException(e);
            onFinish();
        } catch (Exception ex) {
            e.printStackTrace();
        }
    }

    public final void onNext(T response) {
        if (!Null.isNull(response) && response.isSuccess()) {
            onResponse(response);
        } else {
            onFailed(response.code, response.respMsg, response.respCode, response);
        }
    }
}

/**
 * @author YangWu
 * @description 请求接口
 */
public abstract class OnSimpleRequestCallback<T extends BaseResponse> extends OnRequestCallback<T> {

    private Context mContext;

    public OnSimpleRequestCallback(Context context) {
        this.mContext = context;
    }

    @Override
    public void onStart() {
        super.onStart();
    }

    @Override
    public void onFinish() {

    }

    @Override
    public void onFailed(int code, String respMsg, String respCode, T response) {
        ToastUtils.show(respMsg);
        XPopupManager.dismissLoading();
    }

    @Override
    public void onException(Throwable e) {
        Log.e(mContext.getPackageName(), e.getMessage());
        XPopupManager.dismissLoading();
    }
}

定义一个BaseResponse来统一接口请求的数据格式:

/**
 * @author YangWu
 * @description 请求体基类
 */
public class BaseResponse implements Serializable {

    public String respMsg;
    public String respCode;
    public int code;

    public boolean isSuccess() {
        return code == 200;
    }
}

4、使用RxJava管理请求

到这里,框架需要的基本东西已经搭建完成了。接下来使用RxJava对请求的队列进行管理:

/**
 * @author YangWu
 * @description RxJavaActionManager 请求队列操作接口
 */
public interface RxActionManager<T> {

    void add(T tag, Subscription subscription); //添加请求

    void remove(T tag);                         //移除请求

    void cancel(T tag);                         //取消请求

    void cancelAll();                           //取消所有请求
}


/**
 * @author YangWu
 * @description RxApi管理器
 */
public class RxApiManager implements RxActionManager<Object> {

    private static RxApiManager sInstance = null;
    private ArrayMap<Object, Subscription> maps;

    public static RxApiManager get() {
        if (sInstance == null) {
            synchronized (RxApiManager.class) {
                if (sInstance == null) {
                    sInstance = new RxApiManager();
                }
            }
        }
        return sInstance;
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private RxApiManager() {
        maps = new ArrayMap<>();
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void add(Object tag, Subscription subscription) {
        maps.put(tag, subscription);
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void remove(Object tag) {
        if (!maps.isEmpty()) {
            maps.remove(tag);
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    public void removeAll() {
        if (!maps.isEmpty()) {
            maps.clear();
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void cancel(Object tag) {
        if (maps.isEmpty()) {
            return;
        }
        if (maps.get(tag) == null) {
            return;
        }
        if (!maps.get(tag).isUnsubscribed()) {
            maps.get(tag).unsubscribe();
            maps.remove(tag);
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void cancelAll() {
        if (maps.isEmpty()) {
            return;
        }
        Set<Object> keys = maps.keySet();
        for (Object apiKey : keys) {
            cancel(apiKey);
        }
    }
}

使用

还是以Login为例,首先实现一个LoginContract对View层和Presenter层的方法进行管理。

public class LoginContract {
    //LoginActivity中需要实现的方法LoginSuccess();
    interface View extends BaseView<Presenter> {
        void loginSuccess();
    }
    //LoginPresenter中需要实现的方法login();
    interface Presenter extends BasePresenter {
        void login(String number, String password);
    }
}

然后对LoginActivity进行编写:

@EActivity(R.layout.activity_login)
public class LoginActivity extends BaseActivity implements LoginContract.View {
    private LoginContract.Presenter mPresenter;
    
    //查找组件
    @ViewById(R.id.til_login_account)
    TextInputLayout mAccountTil;
    @ViewById(R.id.et_login_account)
    EditText mAccountEt;
    @ViewById(R.id.til_login_password)
    TextInputLayout mPasswordTil;
    @ViewById(R.id.et_login_password)
    EditText mPasswordEt;
    @ViewById(R.id.tv_login)
    TextView mLoginTv;
    
    //依赖注入后设置StatusBar模式为沉浸式,并且实例化Presenter
    @AfterInject
    void afterInject() {
        StatusBarUtil.immersive(this);
        new LoginPresenter(this);
    }

    @Click(R.id.tv_login)
    void onLoginTvClick() {
        String account = mAccountEt.getText().toString().trim();
        String password = mPasswordEt.getText().toString().trim();
        if (PublicUtils.isEmpty(account)) {
            AnimationUtils.startShakeByViewIsNeedVibrate(mAccountTil, true);
            mAccountEt.setError("请输入账号");
            return;
        }
        if (PublicUtils.isEmpty(password)) {
            AnimationUtils.startShakeByViewIsNeedVibrate(mPasswordTil, true);
            mPasswordEt.setError("请输入密码");
            return;
        }
        mPresenter.login(account, password);
    }
    
    //View层方法,登录成功后在Presenter层会回调此方法
    @Override
    public void loginSuccess() {
        MainActivity_.intent(getContext()).start();
        finish();
    }

    @Override
    public void onBackPressed() {
        if (RxTool.isFastClick(2000)) {
            TzcmApplication_.getInstance().exitApp();
        } else {
            ToastUtils.show("再按一次退出程序");
        }
    }
    
    //将Presenter绑定到View层
    @Override
    public void setPresenter(LoginContract.Presenter presenter) {
        mPresenter = Null.checkNotNull(presenter);
    }
    
    //View层实现的获取上下文的方法
    @Override
    public Activity getContext() {
        return this;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //在页面销毁时通过RxJava取消登录请求
        RxApiManager.get().cancel(ApiInterface.API_LOGIN);
    }
}

可以看到,在点击登录按钮时调用了mPresenter.login(account, password);方法,这个方法在P层进行处理:

/**
 * @author: WuYang
 * @describe: 登录Presenter
 */
public class LoginPresenter implements LoginContract.Presenter {
    private LoginContract.View mView;
    
    //绑定View层
    public LoginPresenter(@NonNull LoginContract.View view) {
        mView = Null.checkNotNull(view);
        mView.setPresenter(this);
    }
    
    //继承自Presenter的方法,可以处理一些页面初始化的逻辑
    @Override
    public void start() {

    }
    
   /**
    * 在View层调用login方法
    * 通过Retrofit的subscribe观察者模式进行请求,使用上面的RxJava的请求框架OnSimpleRequestCallback进行请求
    * 接收的UserInfoResponse是继承于BaseResponse的子类,根据业务逻辑具有自己的属性
    * 请求成功后通过mView.loginSuccess();方法回调到View层进行处理
    */
    @Override
    public void login(String number, String password) {
        XPopupManager.showLoading(mView.getContext(), "正在登录", ApiInterface.API_LOGIN);
        Subscriber subscriber = new OnSimpleRequestCallback<UserInfoResponse>(mView.getContext()) {
            @Override
            public void onResponse(UserInfoResponse response) {
                UserStateUtil.getInstance().updateUser(response.data);
                XPopupManager.dismissLoading();
                mView.loginSuccess();
            }
        };
        RxApiManager.get().add(ApiInterface.API_LOGIN, subscriber);
        RetrofitApi.getInstance().login(number, password)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(subscriber);
    }
}

总结

  通过这样的构建方式可以很好的将代码解耦,便于项目后期的维护和开发。接下来我会将Small插件化框架再融合进行,然后通过Gradle统一管理。后续我会将搭建的这个框架代码上传至github,应该就是这几天,要从qq空间和公众号转移过来的东西有点多。


长路漫漫,菜不是原罪,堕落才是原罪。
我的CSDN:https://blog.csdn.net/wuyangyang_2000
我的简书:https://www.jianshu.com/u/20c2f2c3560a
我的掘金:https://juejin.im/user/58009b94a0bb9f00586bb8a0
我的GitHub:https://github.com/wuyang2000
个人网站:http://www.xiyangkeji.cn
个人app(茜茜)蒲公英连接:https://www.pgyer.com/KMdT
我的微信公众号:茜洋 (定期推送优质技术文章,欢迎关注)
Android技术交流群:691174792

以上文章均可转载,转载请注明原创。


你可能感兴趣的:(Android开发,Android,mvp,框架,Retrofit,RxJava)