Dagger2在Android中的使用

在我们写程序的过程中,不可避免的就会牵扯到一些依赖,如会创建各种工厂类来生产我们所要依赖的对象等等。太多依赖会让我们的程序看起来很乱,而且很不利于测试,所以我们就使用Dagger2来进行依赖注入。

依赖注入到底是个什么概念呢?我自己的理解就是将对象的生产和使用给分开了。比如说有一个Car对象,当你要出行的时候就自己new一个Car对象,然后使用这个Car对象出行。在这里你出行的时候Car就是你的依赖,没有这个Car就无法出行,所以就自己来创建这个Car。如果使用依赖注入了呢?创建和使用Car是分隔开的,创建的地方不考虑使用,使用的时候也不用考虑来创建,只要说我需要一辆Car,Dagger2就会自动帮你创建好并让你使用。这就在一定程度上解耦了程序,让你的模块更加地可以重用。

下面我们就在Android中试试Dagger2吧。但是首先还要弄清楚一些概念:

  1. @Inject: 可以用在构造方法上,这样就告诉Dagger2使用这个构造方法来创建对象,如果构造方法里面有参数依赖的话会自动给填充上;也可以用在成员变量上,Dagger2会自动将这个变量初始化。用@Inject修饰的内容可以理解为产品
  2. @Provides: 用来修饰方法来提供各种依赖,方法的返回类型就是所提供的依赖类型,用@Provides修饰的可以理解为生产机器
  3. @Module:所有的@Provides方法都必须放到一个Moudle中,一个Moudle就是使用@Moudle修饰的类,可以理解为一个工厂
  4. @Component:修饰一个接口,将依赖的生产和使用结合起来,可以理解为运输部门吧,将工厂生产的产品运送到使用它的人们手里。

光看文字可能还不是很明白,下面就来写代码实践吧。在这里我使用一个展示炉石传说卡片的app作为例子,我们使用Retrofit来访问网络接口,得到卡片的信息等。那我们会有哪些依赖呢?

  1. ApplicationContext对象,在Android app中使用十分广泛,比如我们需要获取资源文件等。
  2. 一个okHttpClient的对象,供Retrofit使用
  3. 一个使用Retrofit创建的网络访问的对象
  4. 可能会用到的Application对象

下面就一步步来吧,首先我们创建一个Moudle来提供对okHttpClient的依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Module
public class NetworkModule {
@Provides
@NonNull
@Singleton
public OkHttpClient provideOkHttpClient() {

OkHttpClient okHttpClient = new OkHttpClient();

HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(BuildConfig.DEBUG ? BODY : NONE);

OkHttpClient newClient = okHttpClient.newBuilder()
.addInterceptor(httpLoggingInterceptor)
.build();
return newClient;
}
}

通过@Singleton注释可以确保这个OkHttpClient对象是一个单例模式,对于这个client对象,我们加入了HttpLoggingInterceptor来控制log的显示,便于调试。

下面来创建一个网络访问的Moudle,在这之前我们还要定义好网络访问的接口:

1
2
3
4
5
6
7
8
public interface IApi {
@Headers({
"X-Mashape-Key:U4y8yvgRDUmshqUkNb1LJxmsRCBap1WWG0wjsnUj07GxYfsKUI",
"Accept: application/json"
})
@GET("info")
Observable<Info> getInfo();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Module
public class ApiModule {
private static final String BASE_URL = "https://omgvamp-hearthstone-v1.p.mashape.com";

@Provides
@Singleton
public IApi getCardsApi(OkHttpClient client) {

Retrofit CardsApiAdapter = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return CardsApiAdapter.create(IApi.class);
}
}

我们将使用Rxjava来处理网络返回的内容,并内置一个GsonConverterFactory来将请求的Gson信息自动转化成对象的Bean。需要注意的是getCardsApi的参数就是一个OkHttpClient对象,由于我们在前面提供了对OkHttpClient的依赖,所以这里使用的是会自动传进一个OkHttpClient对象。

下面来创建提供Conext和Application的Moudle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Module
public class AppModule {
private final MainApp mainApp;
private final Context mContext;

public AppModule(MainApp mainApp, Context mContext) {
this.mainApp = mainApp;
this.mContext = mContext;
}

@Provides
@Singleton
public MainApp getMainApp() {
return mainApp;
}

@Provides
@Singleton
public Context provideApplicationContext() {
return mContext;
}
}

这些Moudle提供了所有我们需要的依赖,下面使用一个Component接口来将这些Moudle结合起来

1
2
3
4
5
6
7
8
9

@Singleton
@Component(modules = {AppModule.class, NetworkModule.class, ApiModule.class,})
public interface AppComponent {
void inject(MainApp app);

void inject(MainActivity mainActivity);

}

在这里接口里,我们定义了两个inject方法,我们将在MainApp和MainActivity里面使用Moudle里面提供的依赖。
在MainApp里面,我们创建一个AppComponent对象进行依赖的注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MainApp extends Application {
private Context mContext;

public AppComponent getmAppComponent() {
return mAppComponent;
}

private AppComponent mAppComponent;

@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
initializeInjector();
}

private void initializeInjector() {
mAppComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this, mContext))
.networkModule(new NetworkModule())
.apiModule(new ApiModule())
.build();
mAppComponent.inject(this);
}
}

需要注意的一点就是里面DaggerAppComponent是由Dagger2自动生成的类,所以我们再写这些代码前要首先编译一下工程,让Dagger2生成这些类,否则是会报错的。

下面就在Activity里面使用吧,首先也是进行注入:

1
2
3
4
5
6
7
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
((MainApp) getApplication()).getmAppComponent().inject(this);
}

然后就可以直接进行网络的访问了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Inject
IApi mIApi;

@OnClick(R.id.text)
void onclick() {
CLogger.i("onclick");
Observable<Info> info = mIApi.getInfo();
info.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<Info>() {
@Override
public void onCompleted() {

}

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

@Override
public void onNext(Info info) {
CLogger.i(info.patch);
}
});
}

在这里,我们访问网络所需要的mIApi对象将由Dagger2自动给我们生成,我们拿过来用就可以了。赶快来实验一下吧,通过log,我们可以看到我们正确地请求到了所要的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
02-18 15:14:43.061 15902-15902/cn.com.mushuichuan.heartstonecards I/CLogger: MainActivity.java[Line: 26] onclick
02-18 15:14:43.061 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: --> GET https://omgvamp-hearthstone-v1.p.mashape.com/info http/1.1
02-18 15:14:43.061 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: X-Mashape-Key: U4y8yvgRDUmshqUkNb1LJxmsRCBap1WWG0wjsnUj07GxYfsKUI
02-18 15:14:43.061 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: Accept: application/json
02-18 15:14:43.061 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: --> END GET
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: <-- 200 OK https://omgvamp-hearthstone-v1.p.mashape.com/info (2317ms)
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: Cache-Control: private, must-revalidate
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: Content-Type: application/json
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: Date: Thu, 18 Feb 2016 07:14:49 GMT
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: expires: -1
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: pragma: no-cache
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: Server: Mashape/5.0.6
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: Set-Cookie: XSRF-TOKEN=eyJpdiI6ImRrT1dUN1dcLzVPcnlzVTRZNlVXUURRPT0iLCJ2YWx1ZSI6ImJYNkRONURJeEJTVW9ydndKeGM0Q1RQMmdYR3JzcTgxKzJtOGtcL0FKRHpNNEtaWStiVURMSVFTWldTY0lrdTR4bG1CYnJQN1JKNDRsXC82M25tS0pyNVE9PSIsIm1hYyI6IjdmNjVjYmJmMjZlYzQ0YjM4ZWZmYmVhYThhZDA5MzYxN2Y5OGFjODc3YmM1ODI4NjAwMjNiZTlhOWJlNDljNjQifQ%3D%3D; expires=Thu, 18-Feb-2016 09:14:49 GMT; Max-Age=7200; path=/
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: Set-Cookie: laravel_session=eyJpdiI6ImMwQnZ3QXVDUWNIMVNNT1Z2bUR1VlE9PSIsInZhbHVlIjoiODFndUFnYlBENUVoMXQ0TThsWmFzQldLN2Y0NUR0TTluaE1mMEZZdExwTVVtaFBySTB1K1wvRzFZQThCQVdrbXRHQkxtVExURVJycWRkc2ZrMmZpNXRRPT0iLCJtYWMiOiI3NDAxYzM1ZDA0MzkyYjMxNzlmNzk2YmNmNGM5OTc5NDU2YmY1MTY4OGU1NTQyMTE3ZDY5NmNlMWNjZmI0ODAwIn0%3D; expires=Thu, 18-Feb-2016 09:14:49 GMT; Max-Age=7200; path=/; httponly
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: X-Powered-By: PHP/5.6.13
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: Content-Length: 681
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: Connection: keep-alive
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: OkHttp-Sent-Millis: 1455779684200
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: OkHttp-Received-Millis: 1455779685387
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: {"patch":"4.1.0.10956","classes":["Druid","Hunter","Mage","Paladin","Priest","Rogue","Shaman","Warlock","Warrior","Dream"],"sets":["Basic","Classic","Credits","Naxxramas","Slush","Goblins vs Gnomes","Missions","Promo","Reward","System","Blackrock Mountain","Hero Skins","Tavern Brawl","The Grand Tournament","The League of Explorers"],"types":["Hero","Minion","Spell","Enchantment","Weapon","Hero Power"],"factions":["Horde","Alliance","Neutral"],"qualities":["Free","Common","Rare","Epic","Legendary"],"races":["Demon","Dragon","Mech","Murloc","Beast","Pirate","Totem"],"locales":["deDE","enGB","enUS","esES","esMX","frFR","itIT","koKR","plPL","ptBR","ruRU","zhCN","zhTW","jaJP"]}
02-18 15:14:45.381 15902-16339/cn.com.mushuichuan.heartstonecards D/OkHttp: <-- END HTTP (681-byte body)
02-18 15:14:45.391 15902-16339/cn.com.mushuichuan.heartstonecards I/CLogger: MainActivity.java[Line: 42] 4.1.0.10956

实验成功,可以继续进行接下来的开发了。
完整代码请见Github