在新增其他功能之前,我想趁現在app規模還很小的時候先套用相依性注入(Dependency Injection;簡稱DI)來增進程式品質。
我們使用Google的DI library Dagger2,它的效能很好並會在build時就檢查有無設置錯誤,更在2.10版時對Android推出了優化的使用方式。如果是第一次接觸Dagger2的話個人覺得上手會有點難,所以今天重點會放在成功建置Dagger2框架,明天再陸續修改現有程式。
假設我們有兩個class分別為User和它的寵物Dog:
class User {
public User() {
// 每次我們使用User時,都建立了新的Dog instance
Dog dog = new Dog();
...
}
}
上例中,每次使用new User()時都會在Constructor內呼叫new Dog(),兩個class成為了一對(coupled),而User須依賴Dog才能順利使用,我們便稱Dog為User的Dependency。
我們無法單獨使用User而不要建立新的Dog,這樣會產生幾個問題:
那比較好的設計是怎樣呢?
class User {
private Dog dog;
public User(Dog dog) {
// 從外部取得Dog
this.dog = dog;
...
}
}
將Dog改成parameter由外部建立再傳給User,這樣簡單的修改就可以解決上面的3個問題:
這樣的設計稱做控制翻轉(Inversion of Control;IoC),即一個class若需要用到其他的class則應由外部傳入,而非在內部用new實例化,也是DI框架的核心概念。
Dagger2的起手比較難理解一點,我們今天先建置完成並測試可注入(inject)就好,目標是將Retrofit service注入MainActivity中使用。
加入gradle dependencies:
// Dagger core
implementation "com.google.dagger:dagger:2.14.1"
annotationProcessor "com.google.dagger:dagger-compiler:2.14.1"
// Dagger android
implementation "com.google.dagger:dagger-android-support:2.14.1"
annotationProcessor "com.google.dagger:dagger-android-processor:2.14.1"
我們使用Dagger核心及新推出的dagger.android,這樣Dagger在Android架構中更符合IoC原則。若你的app沒有使用support library的話就把com.google.dagger:dagger-android-support
改成com.google.dagger:dagger-android
。
1.建立Module:建立一個class並用@Module
註記,表示此Module內的物件可以讓其他class用注入的方式取得。
@Module
class AppModule {
@Provides
@Singleton
GithubService provideGithubService() {
return new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(new LiveDataCallAdapterFactory())
.build()
.create(GithubService.class);
}
}
建立method provideGithubService()
並加上@Provides
標註,此method的回傳型態為GithubService表示在Module內放了GithubService供其他class取用,而method的內容便是此GithubService如何生成。
@Singleton
是Dagger會幫我們處理此物件的Singleton pattern讓我們不用自己實作。
這樣我們就在Module中新增了一個Singleton的GithubService可以取用了,達到跟原本Retrofit Manager一樣的效果。
接著建立另一個Module,在此Module中指定哪些Activity/Fragment要用Inject的方式取得物件,我們以MainActivity為例:
@Module
public abstract class BuildersModule {
@ContributesAndroidInjector
abstract MainActivity contributeMainActivity();
}
Module的部分到這邊就完成了。
2.建立Component:建立一個interface加上@Component
註記,並將要用到的Module加入其中,如此Dagger便會自動產生程式碼讓我們能inject那些Module中的項目。
@Singleton
@Component(modules = {
AndroidSupportInjectionModule.class,
AppModule.class,
BuildersModule.class})
public interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
Builder application(GithubApp application);
AppComponent build();
}
void inject(GithubApp app);
}
AndroidSupportInjectionModule是dagger.android協助我們做inject的Module,如果你不是用support library的話就改成AndroidInjectionModule,另外兩個就是我們剛剛建立的Module。
Component寫好後需「Make Project」,快速鍵CTRL+F9
。
3.於Application中初始化:Make Project完成後Dagger會產生DaggerAppComponent class,接著就能初始化Dagger:
public class GithubApp extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
@Override
public void onCreate() {
super.onCreate();
...
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this);
}
@Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingAndroidInjector;
}
}
這樣建置就完成啦,接著在Activity中注入GithubService測試看看有無成功。
4.在Activity注入物件:使用@Inject
注入物件。
public class MainActivity extends AppCompatActivity {
@Inject
GithubService githubService;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
if (githubService != null) {
Timber.d("Hello Dagger!");
}
...
}
}
我們用@Inject
標註GithubService並在onCreate
時呼叫AndroidInjection.inject(this)
,這樣就完成注入了,當MainActivity開啟時就會看到Log印出Hello Dagger表示githubService是可用的。注意AndroidInjection.inject(this)需在super.onCreate之前呼叫。
這樣就完成在Module中加入物件並注入到Activity使用了。
回顧一下我們做的內容,首先建立AppModule並在裡面放了GithubService讓其他class取用,接著建立BuilderModule標出是哪些Activity要用Inject來取得物件,最後要建立Component來連結這些Module並在Application中初始化。這樣就可以開始將Module中的物件注入到Activity了。
今天目標是成功建置Dagger所以只在Activity使用,明天會開始在Fragment及自訂Class中使用DI,改進我們目前程式的設計。
GitHub source code:
https://github.com/IvanBean/ITBon2018/tree/day09-implement-dagger2
Reference:
Demystifying the new Dagger Android Injection API
Introduction to Dagger 2, Using Dependency Injection in Android
Dagger 2 for Android Beginners