在项目中优雅引入Dagger2+Retrofit+RxJava(RxAndroid) [复制链接]

2017-9-4 15:54
kengsirLi 阅读:1499 评论:1 赞:0
Tag:  

Dagger2

具体的Dagger2描述请大家自行百度,这里只阐述自己对其的一些理解
在查看下文的时候,我们需要了解以下几个概念:
1:什么是依赖注入?
    在此对依赖注入进行解释,我们将依赖 | 注入分开,
    依赖
        就是找你当前对象中所依赖的其他对象,比如说学生对象中存在着教师的引用,调用学生的听课方法,就必然
    需要用到教师这个对象,不然谁去给学生讲课呢?这里,学生对象就依赖了教师对象
    注入
        既然学生对象的创建要依赖于教师对象,或者说学生对象的某些方法依赖了教师对象,那么在学生中教师我们可以通过如下方式来获取
        1:构造方法传入 2,需要用到教师的方法,参数传入
        我们来考虑一下问题,我们需要在调用方法的时候创建学生,创建教师,然后将教师传递过去,是,这种思路没毛病,接下来我们在说一下极端的问题,
        假如说学生依赖了教师,教师依赖了教室,教室又依赖于黑板,等等等等,那么我们在调用学生方法的时候,是不是需要创建出教师,教室,黑板等对象,这种方式看似没毛病,实际上如果对象一级级的依赖于其他对象,
        那么对我们的工作量是十分巨大的
        依赖注入就这么诞生了,既然你要求说你A对象依赖于B对象,甚至说B依赖于C,一层层的嵌套下去,对于开发者来说,都不关心,因为我们有了Dagger2,他会自动帮我们注入对象所需要的依赖对象(的依赖对象).

2:我为什么要使用他?
        在上个问题中我大概已经解释了为什么要使用这么一个框架,他会注入你要操作对象依赖的对象(的依赖对象),不管依赖了多少级,都交给他去做吧

怎么使用他呢?

在你的项目gradle中导入

compile 'com.google.dagger:dagger:2.4'
apt 'com.google.dagger:dagger-compiler:2.4'

接下来我们介绍一下Dagger2提供给我们的注解(目前我们会用到的)

@Component(组成;成分) 模块(组件)的集合

作为Dagger2的注入核心,在代码编写完成之后,dagger-compiler会为我们编译生成对应的类,命名如下 DaggerXxxComponent
下面是我在项目中用到的
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    void injectApplication(MApplication application);
}

我用@Componet对该接口进行声明,说明其实Dagger2的一个模块集,使用@Single告诉Dagger2,该组件是单例的,全局只有这一个
@Component既然是一个模块集,就要求我们对应有其他一系列模块,当然一个模块也是可行的,写法如下
@Component (modules = XxxModule.class)
如果有多个模块,用大括号括起来
这时候不知道你有没有想到这么一个问题?
既然是模块集,那A模块集是否可以引用或者说依赖于另一个模块集呢?
有这种想法就太棒了,模块集肯定也是模块了,那一个模块集引用其他模块集当然是可行的,用代码体现的话是这样的

@PreFragment
@Component(dependencies = AppComponent.class , modules = {FragmentModule.class} )
public interface FragmentComponent {
   //个人设置
   void injectSettingFragment(MySettingFragment mySettingFragment);
}

大家可以先行跳过@PreFragment这个注解,我们往下看
@Component声明FrgamentComponent是组件集, modules声明引用了FragmentModule.class这个组件,而dependencies就是我们需要注意的,依赖于另一个组件集,在此我们依赖的是上面的AppComponent组件集
画个图看一下

组件和组件集.png

组件集依赖组件和组件集.png

@Module(模块; 组件)

组件提供给我们的功能就很强大了,需要在类首加上@Module以及对应的作用域(就是声明该module是一个单例的).编写成代码是这样的
@Singleton
@Module
public class AppModule {
    public Application context;
    public AppModule(Application context) {
        this.context = context;
    }
}

对应上我们刚才的AppComponent组件集,我们就要考虑一件事了,我怎么将组件集和组件进行绑定,尽管我们的AppComponent声明了自己是依赖于AppModule的
在代码中这样写
DaggerAppComponent.builder().appModule(new AppModule(this)).build();
我们来还原一下这行代码
DaggerAppComponent.Builder builder = DaggerAppComponent.builder();
            builder = builder.appModule(new AppModule(this));
            builder.build();
我们来解释一下这三行代码
第一句 构建出来DaggerAppComponent组件集这个Builder对象,
第二句,给builder加入组件,加入AppModule组件
第三句,真正的创建好组件集对象
在这句话执行完之后,就实现了容器的创建,
DaggerAppComponent.builder().appModule(new AppModule(this)).build().injectApplication(this);
这句话执行完之后就将当前对象告诉Dagger2容器,我需要用到你容器中的某些组件,他会自动的去容器中寻找或创建,只需要在你需要的对象上加@Inject注解

就像这样
public class MApplication extends Application {
    @Inject
    public Retrofit retrofit;
    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().appModule(new AppModule(this)).build().injectApplication(this);
    }

这样,我们按理说就可以使用retrofit这个对象了,
但是,我们还不可以运行这个程序,因为Dagger2不知道怎么创建Retrofit对象,在容器中也找不到该对象,

接下来我们来介绍一下Dagger2容器创建依赖对象的两种方式
1: 如果依赖对象的构造方法上加了@Inject注解,那么Dagger2就会调用该构造方法去创建对象
2: 如果你要说 你的依赖对象,是第三方框架提供的,你根本没权利修改他的代码,那么我要怎么创建该对象?
    Dagger2给我们提供了一种方式
    就像这样
    // 提供retrofit
    @Provides
    @Singleton
    public Retrofit providerRetrofit(OkHttpClient client){
        return new Retrofit.Builder()
                .client(client)
                .baseUrl(GlobalConstract.ip)
                .addConverterFactory(ScalarsConverterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
    }

在组件中提供某一对象,在其方法上加入@Provides注解,就相当于通知Dagger2说,我给你提供Retrofit的创建方式,你如果不知道怎么创建,就来Module中找找看
    继续看,providerRetrofit该方法要求传递一个OkHttpClient 对象作为参数,但是OkHttpClient 对象怎么创建呢? 他也是第三方的框架,我们是没权利修改他的构造方法的,怎么解决?
      // 提供OkHttpClient
    @Singleton
    @Provides
    public OkHttpClient providerOkHttpClient(){

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(10, TimeUnit.SECONDS);
        OkHttpClient client = builder.build();
        return client;
    }
我们在Module中加入OkHttpClient 的创建方式,那么providerRetrofit的参数就会由Dagger2去创建好在传递进去
至此,我们的Retrofit对象方式也有了,我们需要在哪里使用,就在其声明上加入@Inject注解
    @Inject
    public Retrofit retrofit;
    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().appModule(new AppModule(this)).build().injectApplication(this);
    }
这样就可以使用Retrofit对象了,是不是感觉很复杂?但是在你真正使用到的时候,你会感觉为什么Dagger2的管理是这么的舒服,这么让人放心

这俩注解我们先不谈,其实见名知意,大概也能了解个差不多了

@Singlet(单一)  单例需要用到的
@Scope(范围)  作用域

我们来重点谈谈这俩

@Inject 注入
    既然是要注入,那么很明显自己是需要将容器提供的对象拿出来,并且注入到自己的对象中直接使用,不用自己去创建那一级级的对象关系链,简化工作
    但是你自己创建的对象怎么知道哪些是需要自己去创建或者说哪些是需要在容器中寻找的呢?@Injcet提供给我们的就是这么一个功能,只要你需要的对象头上加了@Inject注解,就声明了该对象需要在容器中去寻找,不用自己手动创建
@Provides 提供
    我在上面也说过了,Dagger2提供给我们两种创建对象的方式,而@Provides则是在第二种情况时使用的,第三方框架得类你没权限修改,但是你可以告诉Dagger2该框架的某些对象是怎么创建的,这么告诉呢,就是@Provides了,只需要在model中写下providerXXX()方法,返回XXX,在方法声明头用@Provides标注一下,就完成了.

现在假设你已经理解了Dagger2的一些简单用法,我就不再深入说了,以后如果我们有时间,在来详细分析,大家讨论学习,共同进步

接下来的篇幅我会把自己最近学RxJava的心得全部分享出来,如果我的代码有问题,想法有错误,或者说路是错的,希望大家能批评指正,万分感谢

Retrofit 和 RxAndroid的完美搭配

我暂时不去讲Retrofit和RxAndroid的使用,网上的教程很多,也都写的很棒,大家先自行百度了解学习,之后我会一点点更新

哇,好像不写他们怎么使用我就无法下手码字了... 脑袋都是懵的...

好了言归正传,我们继续

我们都知道,在项目中,有着服务器给的各种接口,将接口分类整理之后可能会是这样的


接口分类.png

接口的声明按照Retrofit的规范


接口.png

至于为什么接口统一返回ResponseSet<T>呢,我是这么想的,

首先我们来看一下Responset这个类
public class ResponseSet<T> {
    public Integer state;
    public String msg;
    public String action;
    public T json;
}
我们在来看一下服务器返回的数据格式
{
  "json": {
    "have_home_info": false,
    "have_baby": false,
    "user_id": "f39da9fa5c3ff1bd015c3ff4584b0000"
  },
  "action": "login",
  "state": 0
}
这个数据格式是不会变化的,在请求成功之后会返回action(你请求的接口),state(请求状态码,对应请求结果),而json则是变化的,可能是一个user,一个vaccine,一个station.
如果请求失败了会有服务端返回的失败原因msg

这时候我们就要去考虑,既然所有的接口都会返回公共的部分,是每次都要写一遍吗? 这样肯定是不正确的做法,于是就想到了继承,想到了泛型,因为我本人想练习一下泛型的使用就在此采用泛型了,ResponsetSet要有一个泛型,这个类型实际上就是服务器给返回的json字段转换成的javabean类型,我们来解释一下这样做的好处.
既然我们选择了在项目中使用RxAndroid,我们应该知道filter这个方法了,过滤,对,就是过滤,现在有这么一个需求,我需要将所有的非成功的方法全部拦截,不进行处理,怎么做? 如果每次服务器返回数据对应你的一个javabean,你怎么做? 总不能写一个个的过滤器吧?
这时候就体现我这么做的好处了,既然所有的请求最终都会返回一个ResponseSet,那么过滤器就唯一了,只需要这样

public class RxResponseSetFilter implements Func1<ResponseSet,Boolean> {
    @Override
    public Boolean call(ResponseSet resultSet) {
        Boolean flag = false;
        if(resultSet.state == ResultCode.SUCCESS){
            flag = true;
        }else {
            flag = false;
            ToastUtil.show(resultSet.msg);
        }
        return flag;
    }
}

以后只要我们需要过滤结果操作的时候用这个对象就可以了

解决了数据过滤之后,我们继续
就拿登录来当例子吧

        Observable<ResponseSet<LoginBean>> ob = mModel.login(username, pwd);
        ob
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .filter(new RxResponseSetFilter())
        .subscribe(new SimpleObserver<Boolean>() {
            @Override
            public void onCompleted() {
                updateJPushRegisterId();
            }

            @Override
            public void onNext(Boolean aBoolean) {
                startMainHome(aBoolean);
            }

        });

我们暂且不谈model层做了什么,现在读一遍易读性非常好的代码.
先在io线程请求数据,请求到时候切换回主线程,然后对结果进行过滤,过滤掉非成功的操作,之后注册消费者SimpleObserver,做进入主页面操作,后台更新推送id操作
现在我们把目光聚焦到这一段代码,思考一下有什么是多余的,或者说是以后的操作都需要重复的编写,
我们发现网络请求的io线程,切换回主线程,过滤操作都是需要重复做的,现在我们就要考虑一件事,怎么model层返回的事件进行统一的变换,不需要重复写代码,即便是少写一行也算事吧?
Rxandroid提供给我们这样一组对象和方法 Transformer及compose(Transformer) 而compose会返回给我们一个观察者(经过一系列变换的观察者),这样我们就想清楚了,上代码看一下

这个是切换线程
babyServes.addBabyCycle(babyId,content,pictures).compose(RxTransformUtils.<ResponseSet>defaultSchedulers());

public class RxTransformUtils {
    public static <T> Observable.Transformer<T, T> defaultSchedulers() {
        return new Observable.Transformer<T, T>() {

            @Override
            public Observable<T> call(Observable<T> tObservable) {
                return tObservable
                        .unsubscribeOn(Schedulers.io())
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        ;
            }
        };
    }
}

你可能会问了,你现在想过滤结果,怎么办,过滤结果这个操作不一定是所有的方法都需要过滤,而线程的切换却是必须的,所以我在这没有写统一的过滤

在所有数据逻辑处理完成之后,是时候注册消费了,我们又考虑到好像有很多业务需要有一个加载框,在网络请求结束后关闭,怎么做?

我是这样做的,定义了两个消费者 SimpleObserver 和 ProgressObserver
看一下代码

//就是一个实现了Subscriber接口的方法,空实现我们不关心的方法,我们关心的方法重写一下就行
public class SimpleObserver<T> extends Subscriber<T> {

    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {
            e.printStackTrace();
    }

    @Override
    public void onNext(T o) {

    }
}


//一个带有进度条的消费者观察者,是一个抽象类,抽象方法为injectContext需要在实例化的时候注入当前界面的Context,
public abstract class ProgressObserver<T> extends Subscriber<T> {

    private static final String TAG = "ProgressObserver";
    private Context mContext;
    private ProgressDialog dialog;

    @Override
    public void onCompleted() {
        dialog.dismiss();
    }

    @Override
    public void onError(Throwable e) {
        e.printStackTrace();
        Log.i(TAG,"onError");
        ToastUtil.show("服务器懵逼了...");
        dialog.dismiss();
    }


    @Override
    public abstract void onNext(T t);

    @Override
    public void onStart() {
        super.onStart();
        mContext = injectContext();
        dialog = new ProgressDialog(mContext);
        dialog.setMessage("加載中...");
        dialog.setCancelable(false);
        dialog.show();
    }

    //进度条需要依赖于context,注入当前界面的context就行
    public abstract Context injectContext();

}

这样就解决了需要进度条的情况.

头快炸了,第一次写技术性的博客,不不,是第一次写博客,也都是自己在平时写代码的时候总结的经验和遇到的坑以及看自己糟糕透顶的代码的一次次改变,下次再写吧,写写rxjava的变换,我遇到的实际情况是这样的.

例:
    现在用户在注册完成之后要进行登录,当然是在后台偷偷进行登录,怎么做?用rxjava怎么做?
    很多人都是嵌入嵌入在嵌入,迷之缩进,我刚开始也是这样
    动动脑子想一下,我们下一篇博客继续讨论


    我们始终都坚信着一件事:
    有问题不可怕,可怕的是我们知道了问题的所在还不去解决问题


分享到:
我来说两句
facelist
您需要登录后才可以评论 登录 | 立即注册
所有评论(1)
James1991 2017-9-8 18:27
感谢分享
回复

领先的中文移动开发者社区
18620764416
7*24全天服务
意见反馈:1294855032@qq.com

扫一扫关注我们

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粤ICP备15117877号 )