Dagger2融合篇(三)

913 阅读6分钟

第一篇讲解了无参情况两种注入方式和有参情况的两种注入方式。

第二篇讲解了注入迷失怎么解决,局部单例和全局单例模式的写法,以及Component依赖和继承的写法。

这一篇是Dagger2的融合篇,目标让读者能学会把Dagger2融合到mvp框架开发中。

在APP的开发中,有非常多的类都有依赖对象,总不能每一个目标类都配一个Component吧。应该以什么样的原则来划分Component呢,Component应该划分为多小的粒度呢,一般会遵循如下的组织原则:

  • 创建一个全局的Component(ApplicationComponent), 在application中对其进行实例化,一般会在这个component中用来管理APP中的全局类实例。
  • 对于每个页面创建一个Component,一个Activity页面定义一个Component,一个Fragment定义一个Component,使这些component继承自applicationComponent。

一创建全局的Component

全局的实例都有哪些呢?联网的需要单例,SP需要单例,还有全局的Application对象,那么就开始写吧。

创建全局module:

@Module
public class AppModule {

    private MyApplication application;

    public AppModule(MyApplication application) {
        this.application = application;
    }

    public static final int DEFAULT_TIMEOUT = 30;

    @Singleton
    @Provides
    public API provideAPI(Retrofit retrofit) {
        return retrofit.create(API.class);
    }

    @Provides
    public Retrofit provideRetrofit(OkHttpClient client) {
        return new Retrofit.Builder()
                .baseUrl("baseurl")
                .addConverterFactory(GsonConverterFactory.create())//可以添加自定义解析器和默认的解析器
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//添加响应式编程的适配器
                .client(client)
                .build();

    }

    @Provides
    public OkHttpClient provideOkHttpClient() {
        return new OkHttpClient.Builder()
                .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS))
                .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)//设置连接超时时间
                .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                .build();

    }
    @Singleton
    @Provides
    public SharedPreferences provideSharedPreferences() {
        return application.getSharedPreferences("spfile", Context.MODE_PRIVATE);
    }

    @Singleton
    @Provides
    MyApplication provideApplication() {
        return application;
    }

}

这里你应该能看懂,就是第一篇讲解的有参数的第三方注入写法。唯一要讲解的就是 @Singleton注解,他是Scope的默认实现,大家习惯写全局单例用这个注解,而不是去自定义一个注解。

然后就是AppComponent接口:

@Singleton
@Component(modules = AppModule.class)
public interface AppCompoent {

    //当这个Component被别的Component依赖时,必须提供下边方法,不写代表不对依赖Component暴漏对象
    API provideAPI();

    SharedPreferences provideSharedPreferences();

    MyApplication provideApplication();


}

这里有两点要强调:

  • 当这个Component被别的Component依赖时,必须提供下边方法,不写代表不对依赖Component暴漏对象
  • Component的注解Singleton要和module的一致,要不编译不通过。

然后就是再Application中获取这个全局的对象。

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ComponentHolder.setAppComponent(DaggerAppCompoent.builder().appModule(new AppModule(this)).build());
    }
}

然后把获取到的对象保存到另外一个类里边,这个类的代码如下:

public class ComponentHolder {

    private static AppCompoent myAppComponent;

    public static void setAppComponent(AppCompoent component) {
        myAppComponent = component;
    }

    public static AppCompoent getAppComponent() {
        return myAppComponent;
    }


}

然后我们就完成一个一个全局的Component。

二.mvp中P对象的V对象注入

在MVP模式中,V层持有P的对象,P层持有V的对象,而且,我们会把P对象和V对象抽取到父类。

如下代码:

public abstract class BaseActivity<P extends BasePersenter> extends AppCompatActivity {
    @Inject
    public P mPersenter;

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mPersenter != null) {
        mPersenter.detach();
    }
}}
public class BasePresenterImpl<V extends BaseView> implements BasePersenter {

    public V mView;


    public BasePresenterImpl(V View) {
        this.mView = View;
    }


    @Override
    public void detach() {
    
        this.mView = null;//释放View,防止内存泄露
    }

继承BaseActivity的子类HomeActivity的module就要去提供对应的Persenter对象。我们可以在Persenter的构造方法中使用注解他的构造方法,然后通过module提供对应的View对象,而module中的View对象就通过module的构造方法从子类传递过来。这样就完成了p对象的注入,同时view对象也就注入到P中去了(通过构造方法传递进去的)。

代码如下:

@Module
public class HomeModule {

    HomeContact.view view;

    public HomeModule(HomeContact.view view) {
        this.view = view;
    }

    @Provides
    public HomeContact.view provideHomeContactView() {
        return view;
    }

}
@ActivityScope
@Component(modules = HomeModule.class,
        dependencies = AppCompoent.class)
public interface HomeComponent {

    void inject(MainActivity activity);
}
public class HomePersenter extends BasePresenterImpl<HomeContact.view>  {

    @Inject
    public HomePersenter(HomeContact.view View) {
        super(View);
    }

  
}

注意:这里的ActivityScope注解是自定义的注解,这里如果不标记这个注解,会编译错误,因为依赖的Component有作用域注解,这里就要提供一个和依赖作用域不同的注解。

三P层中获取请求网络实例

我们请求网络的实力写的是全局单例模式,而HomeComponent依赖与全局Component,所以我们请求网络的实例可以注入到Activity中,但是对应的P层却注入不到请求网络对象。但是我们p层拥有View对象,我们就可以让View提供一个返回请求网络对象的方法。这样就可以获取到请求的网络的实例。代码如下:

public interface BaseView {

    API getAPI();

}

对应的BaseActivity中添加如下代码:

public abstract class BaseActivity<P extends BasePersenter> extends AppCompatActivity implements BaseView {

    @Inject
    public P mPersenter;

    @Inject
    public API api;

    @Override
    public API getAPI() {
        return api;
    }
......
}

到此我们就把Dagger2简单的融合到mvp中,并且可以简单使用。但是我们还会面临一些细节需要去探讨怎么解决。

四简单页面没有P层

我们在上边探讨了正常有P层关于Dagger2的融合,并在基类中注入了api和Persenter对象,但是实际开发中,有的页面没有P层,但是我们却注入了P层,我们应该怎么解决这个问题呢?

去创建一个EmptyPersenter类,并把构造方法注入,代码如下:

public class EmptyPersenter implements BasePersenter {
    @Inject
    public EmptyPersenter() {
    }
......
}

这样我们就可以在没有P层的时候去泛型一个空的对象。

同时我们基类还有APi对象的注入,那么就需要我们去创建一个HotComponent去依赖全局的Component,否则会报错。

@ActivityScope
@Component(modules = HotModule.class, dependencies = AppCompoent.class)
public interface HotComponent {
    void inject(HotActivity activity);
}

同时还可以注入这个简单页面内,其他实例对象的注入。

五页面对象全部用注入赋值

假如我们新建一个FindActivity继承BaseActivity,让这个页面展示一个列表,让adapter和layoutmanager对象全部用注入赋值。

@Module
public class FindModule {

    private FindContact.View view;

    public FindModule(FindContact.View view) {
        this.view = view;
    }

    @Provides
    public FindContact.View provideFindContactView() {
        return view;
    }


    @Provides
    RecyclerView.LayoutManager provideLayoutManager() {
        return new LinearLayoutManager(view.getActivity(), LinearLayoutManager.VERTICAL, false);
    }

    @Provides
    FindAdapter provideAdapter() {
        return new FindAdapter(null);
    }

}

注意:这里的view.getActivity(),实现思想和p中获取请求对象的思路是一样,不做过多的说明。

对应的页面代码:

public class FindActivity extends BaseActivity<FindPersenter> implements FindContact.View {

    private RecyclerView recyclerView;
    @Inject
    RecyclerView.LayoutManager linearLayoutManager;
    @Inject
    FindAdapter adapter;

    @Override
    protected void inject() {
        DaggerFindComonent.builder().appCompoent(ComponentHolder.getAppComponent()).findModule(new FindModule(this)).build().inject(this);
    }

    @Override
    public int getLayout() {
        return R.layout.activity_find;
    }

    @Override
    protected void initView(Bundle savedInstanceState) {
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setAdapter(adapter);
    }
    @Override
    protected void initListener() {
    }
    @Override
    public void getSerivceData() {
        mPersenter.getData();
    }
    @Override
    public void setData(List<String> list) {
        adapter.setNewData(list);
    }
    //对module类提供的上下文
    @Override
    public Context getActivity() {
        return this;
    }
}

这样就把所有对象通过依赖注入的思想去赋值。

以上就是dagger2融合到mvp中的用法。这里说一下笔者自己的看法:dagger2的目的是为了解耦类之间的依赖关系而单生了,但是在小的项目中使用Dagger2会让开发者感觉很多余,原因就是因为项目小,逻辑简单。导致感觉繁琐。就比如上边的例子,连adapter和LayoutManager对象都用注入去赋值,有种去你邻居家里 十米远, 你却要开车的感觉。

读者有没有发现我们在每个页面都都写入了注入的代码,而并不是抽取到父类,这就是Dagger2在Android中使用的缺陷。为了解决这个问题,又出来了Dagger-android框架。至于Dagger-Android笔者并不打算开文章讲解用法,因为在实际项目中我并不打算用Dagger2,或者Dagger-Android去开发项目。并且在kotlin项目中更加显得Dagger2无用~后续可能会对Kotlin的依赖注入框架Kodein做一下研究。

最后本文的Demo地址:github.com/XiFanYin/Da…