毫无疑问,Dagger2的 上手是有门槛的,有门槛是因为它里边的概念多,用起来复杂,可是一旦你学会了Dagger2的使用,你一定会爱不释手的。与ButterKnife和AndroidAnnotations不同,Dagger2是由Google开发和维护(之前是Squreup),在 性能上可以说是做到的极致(Dagger2在编译期间进行了依赖注入,完全去除了反射机制),Dagger2要解决的问题也和ButterKnife以及AndroidAnnotations不同,后者主要是解决控件的初始化,线程的切换等等,而Dagger2则类似于Java中的Spring框架,主要是为了解决应用程序在运行时的耦合问题,使用Dagger2可以帮助我们实现低耦合高聚合。OK,那么今天我主要是想通过几个小Demo带大家来学习一下Dagger2的使用。
本文主要包括以下三方面内容:
1.Dagger2的引入
2.ViewPager加载网络图片,使用Dagger2实现解耦
3.ViewPager+TabLayout+Fragment制作导航页,使用Dagger2实现解耦
OK,那就开始吧!
1.Dagger2的引入
在项目中引入Dagger2需要修改两个地方的gradle文件,首先是project的gradle文件,修改成如下样子:
dependencies { classpath 'com.android.tools.build:gradle:2.1.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }
然后修改module的gradle文件:
apply plugin: 'com.neenbedankt.android-apt' dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.1.1' compile 'com.google.dagger:dagger:2.6' apt 'com.google.dagger:dagger-compiler:2.6' compile 'com.squareup.picasso:picasso:2.5.2' }
这里有几个地方需要说一下,首先Dagger2我们可以直接在jCenter中搜索到的,可以搜到之后直接添加,那么和Dagger2有关的是两个文件:
compile 'com.google.dagger:dagger:2.6' apt 'com.google.dagger:dagger-compiler:2.6'
但是这两个一个用了compile一个用了apt,使用apt表示该引用类库只在编译的时候起作用,在打包的时候并不会打包到apk中去。
2.ViewPager加载网络图片,使用Dagger2实现解耦
正常情况下,我们使用ViewPager加载网络图片可能是这样写(这里我就贴出关键的ViewPagerAdapter,完整代码大家在文末自行下载):
public class VpAdapter extends PagerAdapter { private Context context; private List<String> list; public VpAdapter(Context context, List<String> list) { this.context = context; this.list = list; } @Override public int getCount() { return list.size(); } @Override public boolean isViewFromObject(View view, Object object) { return view==object; } @Override public Object instantiateItem(ViewGroup container, int position) { ImageView iv = new ImageView(context); iv.setScaleType(ImageView.ScaleType.CENTER_CROP); Picasso.with(context).load(list.get(position)).into(iv); container.addView(iv); return iv; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } }
然后在Activity中来初始化Adapter,并设置给ViewPager:
VpAdapter adapter = new VpAdapter(this,list); viewPager.setAdapter(adapter);
OK,这样我们的ViewPager就能加载出网络图片了,可是这种写法耦合性太强,我们需要进行适当的解耦,解耦,当然是使用Dagger2进行解耦了,OK,使用了Dagger2之后,我们先来看看怎么改造ViewPagerAdapter,如下:
public class VpAdapter extends PagerAdapter { .... .... @Inject public VpAdapter(Context context, List<String> list) { this.context = context; this.list = list; } .... .... }
首先我在VpAdapter的构造方法上添加了一个@Inject注解,这是我们接触到Dagger2中的第一个注解,那它有什么含义呢?@Inject注解有两个不同含义和用法,第一种就是标记在构造方法上,一会在Activity中当系统需要对VpAdater的实例进行注入的时候,会自动调用具有@Inject注解的构造方法;第二种用法就是标记在需要依赖的变量上,以让Dagger2为其提供依赖。OK,改造完VpAdapter之后,我们再来看看如何改造MainActivity,首先,当我再需要获取一个VpAdapter实例的时候我已经不需要new了:
@Inject VpAdapter adapter;
声明一个变量,该变量具有@Inject注解(注意,这是@Inject注解的第二种用法),这个表示该变量需要依赖,需要由Dagger2为其提供依赖。可是仅仅这样就能实现VpAdapter变量的初始化吗?肯定是不可以。一个现实的问题是VpAdapter在实例化的时候需要传递的参数怎么传?这时,我们就得引出Dagger2中的另外一个注解了,@Module,使用了@Module注解的类专门提供依赖,谁需要依赖,都可以从这里获取。OK,那我们来看看我们这里提供依赖的类:
@Module public class AppModule { private Context context; public AppModule(Context context) { this.context = context; } @Provides Context providesContext() { return context; } @Provides List<String> providesImageUrlList() { List<String> list = new ArrayList<>(); list.add("http://img4.cache.netease.com/photo/0001/2016-08-13/900x600_BUBDM7GI00AO0001.jpg"); list.add("http://img4.cache.netease.com/photo/0001/2016-08-13/900x600_BUBDM7JI00AO0001.jpg"); list.add("http://img3.cache.netease.com/photo/0001/2016-08-13/900x600_BUBDM85900AO0001.jpg"); list.add("http://img3.cache.netease.com/photo/0001/2016-08-13/900x600_BUBDM8F500AO0001.jpg"); list.add("http://i0.sinaimg.cn/dy/slidenews/76_img/2016_32/76522_1882718_992616.jpg"); return list; } }
我在VpAdapter构造方法初始化的时候需要传递两个参数,一个是上下文,还有一个是图片地址的集合,这两个参数我都在这个Module中来提供,@Module注解上文已经说过了,这里还有一个@Provides注解,该注解是专门用来注解方法的,并且该注解只可以在@Module中使用,使用了@Provides注解的方法,在需要提供依赖的时候被调用(这里就是当系统初始化VpAdapter的时候调用)。OK,仅仅这样还不够,我们还需要有一个东西能够将@Module和@Inject连接起来,这个东西叫做@Compoent,也可以称作为一个注入器。所有的注入器都要以接口的形式来定义,接口中添加注入的方法,一般情况下我们使用inject作为方法名,方法的参数就是我们要注入的容器。Dagger2会帮我们生成一个名为DaggerXXXX的@Component的实现类。OK,我们来看看本案例的注入器:
@Component(modules = AppModule.class) public interface AppComponent { void inject(MainActivity activity); }
modules参数表示我们要注入的@Module类,也可以是多个@Module类,注意inject方法的参数为MainActivity,这里不可以写作MainActivity的父类Activity,因为这里写啥,Dagger就回去对应的类中寻找@Inject注解进行注入,很明显我们需要注入的变量都在MainActivity中,而不是在Activity中。
注入器也有了,那我们最后再来看看怎样在MainActivity中进行注入吧:
@Inject VpAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this); ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); viewPager.setAdapter(adapter); }
有没有发现代码一下变得非常简洁。注意DaggerAppComponent类是根据我们的AppComponent类自动生成的,appModule方法中传入提供依赖的Module,而inject方法则表示调用@Component的实现类将Module提供的实例注入的VpAdapter的构造方法中。这样写了之后我们的工程就算完成了。
运行效果如下:
OK,我们再来总结一下我们这里所接触到的几个注解:
1. @Inject(两个作用,一个是标记在构造方法上让Dagger来使用,另一个是标记在需要依赖的变量上让Dagger2为其提供依赖)
2. @Provides(注解方法,该注解只可以在@Module中使用,使用了该注解的方法在需要提供依赖时被调用)
3. @Module(用@Module注解的类是专门用来提供依赖)
4. @Component(一个接口,@Inject和@Module之间的桥梁,也称作注入器)
3.ViewPager+TabLayout+Fragment制作导航页,使用Dagger2实现解耦
上面那个例子是不是觉得还不过瘾?那我们再来看一个稍微复杂一点的案例:
先来看看运行效果:
上面滚动的东西是一个TabLayout(不懂TabLayout的小伙伴可以参考使用TabLayout快速实现一个导航栏),下面是ViewPager,ViewPager中放的是Fragment。
先来看看布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" tools:context="org.lenve.dagger2vpfragment.MainActivity"> <android.support.design.widget.TabLayout android:id="@+id/tab_layout" app:tabMode="scrollable" android:layout_width="match_parent" android:layout_height="48dp"></android.support.design.widget.TabLayout> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent"></android.support.v4.view.ViewPager> </LinearLayout>
再来看看Fragment的适配器:
public class MyAdapter extends FragmentPagerAdapter { private List<String> titles; private List<Fragment> fragments; @Inject public MyAdapter(FragmentManager fm, List<String> titles, List<Fragment> fragments) { super(fm); this.titles = titles; this.fragments = fragments; } @Override public Fragment getItem(int position) { return fragments.get(position); } @Override public int getCount() { return fragments.size(); } @Override public CharSequence getPageTitle(int position) { return titles.get(position); } }
注意该适配器的构造方法有三个参数,所以我一会需要在Module中提供至少三个对应的方法,来为这个构造方法进行注入,那么Module是什么样子呢?
@Module public class AppModule { private AppCompatActivity appCompatActivity; public AppModule(AppCompatActivity appCompatActivity) { this.appCompatActivity = appCompatActivity; } @Provides FragmentManager providesFragmentManager() { return appCompatActivity.getSupportFragmentManager(); } @Provides List<String> providesTitles() { List<String> list = new ArrayList<>(); for (int i = 0; i < 9; i++) { list.add("张三:" + i); } return list; } @Provides List<Fragment> providesFragmentList(List<String> titles) { List<Fragment> fragments = new ArrayList<>(); for (String title : titles) { fragments.add(BaseFragment.getInstance(title)); } return fragments; } }
三个方法分别返回Adapter需要的三个参数。再来看看AppComponent:
@Component(modules = AppModule.class) public interface AppComponent { void inject(MainActivity activity); }OK,万事俱备,只差最后在MainActivity进行注入了:
public class MainActivity extends AppCompatActivity { private ViewPager viewPager; private TabLayout tabLayout; @Inject MyAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this); tabLayout = (TabLayout) findViewById(R.id.tab_layout); viewPager = (ViewPager) findViewById(R.id.viewpager); viewPager.setAdapter(adapter); tabLayout.setupWithViewPager(viewPager); } }
OK,这个Demo和我们第一Demo有点类似,所以我没有做过多解释,完整的Project在文末下载。
Dagger2其他常用注解我会在下一篇文章中介绍。
以上。
Demo 下载http://download.csdn.net/detail/u012702547/9603154