您的位置:首页 > 其它

解锁Dagger2使用姿势(一)

2016-08-14 22:25 435 查看
毫无疑问,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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: