一.配置:
1.project的build.gradle添加:
dependencies {
... // 其他classpath
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //添加apt命令
}
2.在module的build.gradle添加:
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'// 註釋處理
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
applicationId "com.dagger2demo"
minSdkVersion 14
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:24.2.1'
testCompile 'junit:junit:4.12'
//dagger2 依賴注入框架
compile 'com.google.dagger:dagger:2.0.2'
apt 'com.google.dagger:dagger-compiler:2.0.2'
provided 'org.glassfish:javax.annotation:10.0-b28'
}
二.基本使用.
dagger2的主要作用就是用來解耦,使用dagger2以後,再也不用頻繁的去new 對象了.就是這麼神奇.
當我們創建一個類的時候可以給它的構造函數加上@inject,在通過注入器component(注入器)將這個類注入到我們需要使用的地方.
@inject 注入,這裏可以使用到需要進行注入的對象的構造函數上面,然後在需要使用到該對象的地方也可以直接將該類進行注入進來.(這種方式只適合我們自己寫的類,並且我們可以對構造函數進行修改的地方如果使用的是第三方庫那麼就需要使用到moudle這種方式了)
1.一個是使用在構造函數上,通過標記構造函數讓Dagger2來使用(Dagger2通過Inject標記可以在需要這個類實例的時候來找到這個構造函數並把相關實例new出來)從而提供依賴。注意:不能給這個類中的多個構造器作@Inject註解.
2.另一個作用就是標記在需要依賴的變量讓Dagger2爲其提供依賴。
public class Apple {
@Inject
public Apple() {
}
}
@Component()
public interface AppleComponets {
void inject(MainActivity mainActivity);
}
@Inject
Apple mApple;
-------
DaggerAppleComponets.builder().build().inject(this);
@module:項目中使用到了第三方的類庫,第三方類庫又不能修改,所以根本不可能把Inject註解加入這些類中,這時我們的Inject就失效了。Module其實是一個簡單工廠模式,Module裏面的方法基本都是創建類實例的方法。
** 構造方法帶有參數問題 **
@Module
public class AppModule {
@Provides
public OkHttpClient provideOkHttpClient() {
OkHttpClient okhttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build();
return okhttpClient;
}
@Provides
public Retrofit provideRetrofit(OkHttpClient okhttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.client(okhttpClient)
.baseUrl("https://api.github.com")
.build();
return retrofit;
}
}
這種用來生產Dependency的、用 @Provides修飾過的方法叫Provider方法。這裏要注意第二個Provider方法 provideRetrofit(OkHttpClient okhttpClient),這個方法有一個參數,是OkHttpClient。這是因爲創建一個Retrofit對象需要一個OkHttpClient的對象,這裏通過參數傳遞進來。這樣做的好處是,當Client向管理員(Component)索要一個Retrofit的時候,Component會自動找到Module裏面找到生產Retrofit的這個 provideRetrofit(OkHttpClient okhttpClient)方法,找到以後試圖調用這個方法創建一個Retrofit對象,返回給Client。但是調用這個方法需要一個OkHttpClient,於是Component又會去找其他的provider方法,看看有沒有哪個會生產OkHttpClient。於是就找到了上面的第一個provider方法: provideOkHttpClient()。找到以後,調用這個方法,創建一個OkHttpClient對象,再調用 provideRetrofit(OkHttpClient okhttpClient)方法,把剛剛創建的OkHttpClient對象傳進去,創建出一個Retrofit對象,返回給Client。當然,如果最後找到的 provideOkHttpClient()方法也需要其他參數,那麼管理員還會繼續遞歸的找下去,直到所有的Dependency都被滿足了,再一個一個創建Dependency,然後把最終Client需要的Dependency呈遞給Client。
@Provides: Module中的創建類實例方法用@Provides進行標註,方法的名稱用providesXXX方式進行書寫,Component在搜索到目標類中用Inject註解標註的屬性後,Component就會去Module中去查找用Provides標註的對應的創建類實例方法,這樣就可以解決第三方類庫用dagger2實現依賴注入了。
@Provides
public Apple providesApple() {
return new Apple("red");
}
@Component : Component也是一個註解類,一個類要想是Component,必須用Component註解來標註該類,並且該類是接口或抽象類。它一端連接目標類,另一端連接目標類依賴實例,它把目標類依賴實例注入到目標類中。
@Singleton :標註創建的對象爲單例模式.
@Inject
Apple mApple;
@Inject
Apple mAppleTwo;
如上使用註解的方式創建兩個對象,我們可以通過輸出對象的地址,看到這兩個對象並不是同一個對象,那麼我們這個時候就需要使用到@singleton來進行單例的標記.
@Module
public class AppleMoudule {
@Provides
@Singleton
Apple providesApple() {
return new Apple("red");
}
}
@Singleton
@Component(modules = AppleMoudule.class)
public interface AppleComponets {
void inject(MainActivity mainActivity);
}
一個Component可以依賴多個Module,如
@Component(modules = {AModule.class,BModule.class,...})
同樣,Component也可以依賴另一個Component,如
@Component(dependencies = BComponent.class,modules = {AModule.class,BModule.class,...})
甚至多個Component,如
@Component(dependencies = {BComponent.class,CComponent.class,...}
,modules = {AModule.class,BModule.class,...})
有個需要注意的地方是被依賴的Component需要將對象暴露出來,依賴的Component才能夠獲取到這個對象,如
//被依賴Component
@Component(modules = {...})
public interface BComponent {
A a();
B b();
}
//依賴Component
@Component(dependencies = BComponent.class,modules = {...})
public interface CComponent {
}
CComponent可以通過BComponent獲取A,B的對象,其他BComponent的對象對CComponent來說是透明的,不可見。
Qualifier(限定符)
通過注入的方式:創建類實例有2個維度可以創建:
-
通過用Inject註解標註的構造函數來創建(以下簡稱Inject維度)
-
通過工廠模式的Module來創建(以下簡稱Module維度)
這2個維度是有優先級之分的,Component會首先從Module維度中查找類實例,若找到就用Module維度創建類實例,並停止查找Inject維度。否則纔是從Inject維度查找類實例。所以創建類實例級別Module維度要高於Inject維度。
基於同一個維度條件下,若一個類的實例有多種方法可以創建出來,那注入器(Component)應該選擇哪種方法來創建該類的實例呢?如下圖,基於Inject維度: 我把上面遇到的問題起個名字叫依賴注入迷失。
代碼例子如下:
public class Apple {
public String color = "red";
public Apple() {
}
public Apple(String color) {
this.color = color;
}
}
@Module
public class AppleMoudule {
@Provides
Apple providesApple() {
return new Apple();
}
@Provides
Apple providesAppleTwo() {
return new Apple("green");
}
}
那麼可以給不同的創建類實例的方法用標識進行標註,用標識就可以對不同的創建類實例的方法進行區分(標識就如給不同的創建類實例方法起了一個id值)。同時用要使用的創建類實例方法的標識對目標類相應的實例屬性進行標註。那這樣我們的問題就解決了,提到的標識就是Qualifier註解,當然這種註解得需要我們自定義。
Qualifier(限定符)就是解決依賴注入迷失問題的。
注意 dagger2在發現依賴注入迷失時在編譯代碼時會報錯。 Person對象具有兩個構造方法,根據不同的參數值構造不同的方法。
@named
例子: Person對象具有兩個構造方法,根據不同的參數值構造不同的方法。
public class Person {
private Context mContext;
public Person(Context context){
mContext = context;
Log.i("dagger","create");
}
public Person(String name){
Log.i("dagger",name);
}
ActivityModule中添加@Named標記
@Module
public class ActivityMoudule {
@Named("Context") // 通過context創建Person 對象
@Provides
Person providePersonContext(Context context){
// 此方法需要Context 對象
return new Person(context);
}
@Named("name") // 通過name創建Person 對象
@Provides
Person providePersonName(){
// 此方法需要name
return new Person("1234");
}
使用時,也需要添加此標記
public class MainActivity extends AppCompatActivity{
@Named("context") // 標記
@Inject
Person person;
@Named("name") // 標記
@Inject
Person person2;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注入
component.inject(this);*/
// 依賴對象 Component
AppComponent appCom = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
// 子類依賴對象 ,並注入
DaggerActivityComponent.builder()
.appComponent(appCom)
.activityMoudule(new ActivityMoudule())
.build()
.inject(this);
}
}
使用時,使用者的@Inject上,必須要加入註解@Named("xxx"),不然編譯期會報錯。 這樣使用過程中,雖然解決了問題,但是通過字符串標記一個對象,容易導致前後不匹配,可以通過自定義註解的方式解決。
添加兩個註解,分別對應Context和name。
@Qualifier // 關鍵詞
@Retention(RetentionPolicy.RUNTIME) // 運行時仍可用
public @interface PersonForContext {
// Context 對象的註解
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonForName {
// name 對象的註解
}
在使用@Named("")的地方替換爲上面的註解
@PersonForContext // 通過context創建Person 對象
@Provides
Person providePersonContext(Context context){
// 此方法需要Context 對象
return new Person(context);
}
@PersonForName // 通過name創建Person 對象
@Provides
Person providePersonName(){
// 此方法需要Context 對象
return new Person("123");
}
@PersonForContext // 標記
@Inject
Person person;
@PersonForName // 標記
@Inject
Person person2;
作用域(Scopes)
dagger2自帶了一個作用域 @Singleton 一般用這個來創建單例模式.
實際上@Singleton或者是自定義的作用域沒有創建單例模式的能力,主要是初始化了一次就會造成單例的假象
比如我們需要創建一個全局的單例,這個時候我們將component在application類裏面進行初始化,這樣component裏面注射的對象就是全局單例的,因爲他們只是實例化了一次.
在activity裏面定義一個scope,activityScope,這樣如果在module裏面對注入的對象添加activityScope作用域,那麼在activity裏面所注入的對象就是同一個對象.
dependencies 一個沒有scope的組件component不可以依賴一個有scope的組件component。如果父類 有scope,那麼如果子類需要使用dependencies依賴父類,那麼子類就需要有scope.要不然父類和子類就需要都沒有scope.
子組件和父組件的scope不能相同。父類(dependencies)有範圍,子類必須有,且scope作用域要小於它.我們通常的ApplicationComponent都會使用Singleton註解,然後在application裏面進行初始化,這樣就可以保持全局的單例.也就會是說我們如果自定義component必須有自己的scope。
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerApplication {
}