App框架实现-dagger2

作者: 码农一颗颗 | 来源:发表于2017-12-21 22:55 被阅读231次

    由于公司App架构需要调整,所以最近在封装App框架,首先给上github地址请点击这里
    该框架的组成MVP+Dagger2+RxJava+Retrofit+OkHttp+RxCache+单元测试(Junit+Mockito)
    目前只是个最简单的封装版本,后续会对这个框架进行维护,希望大家多多支持。

    本篇文章首先介绍Dagger2依赖注入框架(Dependency Injection)

    Dagger2目录.png

    下文中的dagger2依赖注入用DI简写来代替。

    Dagger2 关键字

    @Inject

    用来标记字段,表示这个字段需要DI来提供实例。
    用来标记构造方法,表示这个类可以用来提供DI实例。也就是提供@Inject修饰的字段的实例。
    标记的构造方法有参数,那么参数由DI实例来提供。
    @Inject标记类的构造方法不能进行重载。也就是只能有一个构造方法。
    如下代码例子:

    public class BaseActivity<t extends="" ipresenter=""> extends AppCompatActivity {
        ......
        //用来标记字段,表示mPresenter需要DI来提供实例
        @Inject
        protected T mPresenter;
        ......
    }
    
    public class DataRepositoryManager implements IDataRepositoryManager {
        ......
        private Retrofit retrofit;
        private RxCache rxCache;
        //标记构造方法,表示DataRepositoryManager类可以提供DI实例
        //retrofit和rxCache需要DI来提供实例
        //@Inject标记构造方法之后,这个类只能有一个构造方法,不能重载
        @Inject
        public DataRepositoryManager(Retrofit retrofit, RxCache rxCache){
            this.retrofit = retrofit;
            this.rxCache = rxCache;
        }
        ......
    }
    
    @Module

    用来标记类,这个类用来提供DI实例。也就是给@Inject标记的字段或者@Inject标记的构造方法参数提供实例。(只有标记@Provides的方法才能提供DI实例,请见下文)

    @Provides

    用来标记方法,在@Module标记的类中,只有@Provides标记的方法才能提供DI实例。其他方法都是普通方法。
    有两种方式可以提供DI实例,第一种@Inject标记的构造方法,第二种在@Module标记的类中,只有@Provides标记的方法
    @Module和@Provides如下代码

    @Singleton
    //这个标记表示这个类中的方法可以提供DI实例
    @Module
    public class DataRepositoryModule {
         ......
        @Singleton
        //这个标记表示这个方法可以提供DI实例,也就是提供RxCache。
        //提供上文中DataRepositoryManager构造方法的RxCache参数
        //同时这个方法的参数也是需要DI实例
        @Provides
        RxCache providerRxCache(Application application, File cacheDir, @Nullable RxCacheConfig rxCacheConfig) {
            RxCache.Builder builder = new RxCache.Builder();
    
            if (null != rxCacheConfig) {
                rxCacheConfig.configRxCache(application, builder);
            }
            return builder.persistence(cacheDir, new GsonSpeaker());
        }
        ......
    }
    
    @Component

    它是@Module@Inject之间沟通的桥梁,他将@Module类中@Provides标记的方法返回的实例赋值给@Inject标记的字段或者@Inject标记构造方法的参数。
    如下代码例子:

    @Singleton
    //modules表示DI实例是由ApplicationModule.class, DataRepositoryModule.class, AppDelegateConfig.class类中的@Provides方法提供
    @Component(modules = {ApplicationModule.class, DataRepositoryModule.class, AppDelegateConfig.class})
    public interface AppComponent {
        //这个方法必须写,表示AppDelegateManager类中需要的DI实例由ApplicationModule.class, DataRepositoryModule.class, AppDelegateConfig.class类中的@Provides方法提供
        void inject(AppDelegateManager delegate);
        //由于@Component标记的类可以依赖"dependencies"(也可以理解为类的继承),所以想要暴露的方法就要在这个类中定义,这些方法必须都是"modules"提供的方法,如下方法可以在依赖的类中使用
        Application getApplication();
        RxCache getRxCache();
        Retrofit getRetrofit();
        ......
    }
    
    @ActivityScope
    //LoginComponent继承了AppComponent.class中提供的方法,也就是上面代码中,声明的方法
    @Component(dependencies = AppComponent.class, modules = LoginModule.class)
    public interface LoginComponent {
        void inject(LoginActivity loginActivity);
    
    }
    
    @Qualifier

    限定符:用于标记方法,如果@Provides标记的方法返回值相同必须要通过@Qualifier定义的注解来区分使用哪个方法返回DI实例,否则编译不通过。
    @Inject标记的字段或者@Inject标记的构造方法的参数,用@Qualifier定义的注解来区分需要的DI实例。
    如下代码例子:

    //限定符用来定义注解,这个注解提供本地Mock数据
    @Qualifier
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MockData {
    }
    
    @Singleton
    @Component(modules = {ApplicationModule.class, DataRepositoryModule.class, AppDelegateConfig.class})
    public interface AppComponent {
        void inject(AppDelegateManager delegate);
        ......
        //如下两个方法的返回值类型相同,如果不用@Qualifier进行标记,是无法通过编译的,
        //原因是由@Inject标记的字段无法找到准确的方法来提供DI实例。
        IDataRepositoryManager providerRepositoryManager();
        @MockData
        IDataRepositoryManager providerMockRepositoryManager();
        ......
    }
    
    @Singleton
    @Module
    public class DataRepositoryModule {
        ......
         //如下两个方法的返回值类型相同,如果不用@Qualifier进行标记,是无法通过编译的,
         //原因是由@Inject标记的字段无法找到准确的方法来提供DI实例。
        @Singleton
        @Provides
        public IDataRepositoryManager providerRepositoryManager(Retrofit retrofit, RxCache rxCache) {
            return new DataRepositoryManager(retrofit, rxCache);
        }
    
        @Singleton
        @Provides
        @MockData
        public IDataRepositoryManager providerMockRepositoryManager(Application application, @Nullable DataRepositoryModule.MockDataConfig mockDataConfig) {
            return new MockDataRespsitoryManager(application,mockDataConfig);
        }
        ......
    }
    
    public class LoginInteractorImpl extends BaseModel implements LoginContract.LoginInteractor {
        private ServiceApi serviceApi;
        //下面构造方法的参数由DI来提供,@MockData限定符表示选择上面代码片段的providerMockRepositoryManager方法来提供DI实例,
        //如果去掉@MockData限定符表示使用上面代码片段的providerRepositoryManager来提供DI实例
        @Inject
        public LoginInteractorImpl(@MockData IDataRepositoryManager repositoryManager) {
            super(repositoryManager);
            serviceApi = repositoryManager.getRepositoryDataService(ServiceApi.class);
        }
        @Override
        public Observable<apiresponse<userlogin>&gt; getUserLogin(String phone, String smsCode, String blockBox, String extendParam) {
            return serviceApi.getUserLogin(phone,smsCode,blockBox,extendParam);
        }
        ......
    }
    
    @Named(“name”)

    使用场景完全和@Qualifier一样,但是他是通过“()"中的字符串来区分的。

    @Singleton

    作用域:用于标记类或者方法,从字面意思看大家肯定会想到单例模式,一个类如果加上这个注解这个类就变成了单例?答案是否定的,也就是这个注解不会使类变成单例这点千万要记住,他只是一个标记,表示这个类的实现是单例的。 在App中只存在一个,具体的单例代码还是需要自己写。
    如下代码例子:

    //只是标记这个类要被实现成单例,需要自己保证类的对象在App声明周期内只有一个
    @Singleton
    @Component(modules = {ApplicationModule.class, DataRepositoryModule.class, AppDelegateConfig.class})
    public interface AppComponent {
        void inject(AppDelegateManager delegate);
        ......
    }
    
    //用这个类来保证AppComponent在App声明周期只有一个实例,单例
    public class AppDelegateManager {
        private AppComponent appComponent;
        private static final AppDelegateManager sAppDelegateManager = new AppDelegateManager();
        public static final AppDelegateManager getInstance() {
            return sAppDelegateManager;
        }
        protected AppDelegateManager() {
        }
        public final void init(Application application, AppDelegateConfig appDelegateConfig) {
            appComponent = DaggerAppComponent
                    .builder()
                    .applicationModule(new ApplicationModule(application))
                    .dataRepositoryModule(new DataRepositoryModule())
                    .appDelegateConfig(appDelegateConfig)
                    .build();
            appComponent.inject(this);
        }
        public AppComponent getAppComponent() {
            return appComponent;
        }
    }
    
    //在自定义的Application类中初始化AppDelegateManager,来保证在App声明周期内只有一个AppComponent实例
    public class CustomApplication extends Application implements IApp {
        ......
        @Override
        public void onCreate() {
            super.onCreate();
            AppDelegateConfig appDelegateConfig = new AppDelegateConfig
                    .Builder()
                    .setBaseUrl("http://www.weather.com.cn/adat/sk/")
                    .setCacheDir(getCacheDir())
                    .builder();
            AppDelegateManager.getInstance().init(this, appDelegateConfig);
        }
        @Override
        public AppComponent getAppComponent() {
            return AppDelegateManager.getInstance().getAppComponent();
        }
        ......
    }
    
    @Scope

    自定义作用域注解:他的作用也是使程序员可以直观的看到这个类的作用域,其他没有任何作用。
    @Component依赖关系中@Scope注解必须不相同否则会报错。

    // 用来标识Activity作用域
    @Scope
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ActivityScope {
    }
    
    @Singleton
    @Component(modules = {ApplicationModule.class, DataRepositoryModule.class, AppDelegateConfig.class})
    public interface AppComponent {
        ......
        void inject(AppDelegateManager delegate);
        ......
    }
    
    //LoginComponent类依赖AppComponent类他必须用@ActivityScope来标记进行区分,否则编译报错
    @ActivityScope
    @Component(dependencies = AppComponent.class, modules = LoginModule.class)
    public interface LoginComponent {
        void inject(LoginActivity loginActivity);
    }
    

    Dagger2依赖注入的过程

    这里引用牛晓伟大神文章中的一段话
    步骤1:查找Module中是否存在创建该类的方法。
    步骤2:若存在创建类方法,查看该方法是否存在参数
    步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
    步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
    步骤3:若不存在创建类方法,则查找Inject注解的构造函数,
    看构造函数是否存在参数
    步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
    步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

    插图

    dagger2.png

    Dagger2总结:

    1.解耦

    解耦是dagger2最大的好处,类不可能是单独存在的否则他将毫无意义,这样就会出现在这个类中会有很多的类进行组合使用来完成业务逻辑,在这个类中就会有很多的new class,假如AClass这个类在,BClass、CClass、DClass.......N多个类中都new了一个实例,这时为了满足业务逻辑AClass的构造方法需要修改,增加参数或者修改参数,这回是灾难性的后果,在每个new的地方都需要修改一遍N多个类,如果不小心就会出现各种奇怪的bug。更加复杂的是对象的相互依赖,dagger2就是为了解耦,创建对象在同一的类中只有一份,然后将对象注入到需要的类中,这样如果修改了只会在一个地方进行 修改达到解耦的效果。
    总结一句话,dagger2省略了N多个new class,只需要在一个地方创建对象。

    2.方便Mock数据

    在开发前期往往都是客户端对着接口文档进行开发业务逻辑,这就涉及到读取本地的Mock数据。我们做的是将mock数据写成json文件保存到本地,切换传入Model(MVP中的M)类中的数据获取方式来读取本地mock数据,数据获取方式DataRepository就是通过依赖注入(DI)注入到Model中,可以统一切换很方便。

    3.方便单元测试

    由于依赖对象都是通过DI注入到类中的,所以通过Mockito.mock对象非常方便,单元测试稍后文章会讲到。

    参考文献:
    http://www.jianshu.com/p/1d42d2e6f4a5
    http://www.jianshu.com/p/65737ac39c44
    http://www.jianshu.com/p/cd2c1c9f68d4
    https://github.com/JessYanCoding/MVPArms

    相关文章

      网友评论

        本文标题:App框架实现-dagger2

        本文链接:https://www.haomeiwen.com/subject/riuiwxtx.html