Dagger2 是一個Android依賴注入框架,由谷歌開發,最先的版本Dagger1 由Square公司開發。依賴注入框架主要用於模塊間解耦,提升代碼的健壯性和可維護性。Dagger 這個庫的取名不只僅來自它的本意「匕首」,同時也暗示了它的原理。Jake Wharton 在對 Dagger 的介紹中指出,Dagger 即 DAG-er,這裏的 DAG 即數據結構中的 DAG——有向無環圖(Directed Acyclic Graph)。也就是說,Dagger 是一個基於有向無環圖結構的依賴注入庫,所以Dagger的使用過程當中不能出現循環依賴。java
Android開發從一開始的MVC框架,到MVP,到MVVM,不斷變化。如今MVVM的data-binding還在實驗階段,傳統的MVC框架Activity內部可能包含大量的代碼,難以維護,如今主流的架構仍是使用MVP(Model + View + Presenter)的方式。可是 MVP 框架也有可能在Presenter中集中大量的代碼,引入DI框架Dagger2 能夠實現 Presenter 與 Activity 之間的解耦,Presenter和其它業務邏輯之間的解耦,提升模塊化和可維護性。react
說了那麼多,那什麼是依賴呢?若是在 Class A 中,有 Class B 的實例,則稱 Class A 對 Class B 有一個依賴。例以下面類 Human 中用到一個 Father 對象,咱們就說類 Human 對類 Father 有一個依賴(參考)。android
public class Human { ... Father father; ... public Human() { father = new Father(); } }
那什麼又是依賴注入呢,依賴注入就是非本身主動初始化依賴,而經過外部來傳入依賴的方式,簡單來講就是不使用 new 來建立依賴對象。使用 Dagger2 建立依賴對象,咱們就不用手動初始化了。我的認爲 Dagger2 和 MVP 架構是比較不錯的搭配,Activity 依賴的 Presenter 可使用該DI框架直接生成,實現解耦,簡單的使用方式以下:git
public class MainActivity extends BaseActivity { @Inject MainActivityPresenter presenter; ... }
上面這些主要是對DI框架有一個初步全局的瞭解,下面來看看Dagger2的基本內容。Dagger2 經過註解來生成代碼,定義不一樣的角色,主要的註解有:@Inject、@Module 、@Component 、@Provides 、@Scope 、@SubComponent 等。github
@Inject: 一般在須要依賴的地方使用這個註解。換句話說,你用它告訴Dagger這個類或者字段須要依賴注入。這樣,Dagger就會構造一個這個類的實例並知足他們的依賴。
@Module: Modules類裏面的方法專門提供依賴,因此咱們定義一個類,用@Module註解,這樣Dagger在構造類的實例的時候,就知道從哪裏去找到須要的 依賴。modules的一個重要特徵是它們設計爲分區並組合在一塊兒(好比說,在咱們的app中能夠有多個組成在一塊兒的modules)。
@Provides: 在modules中,咱們定義的方法是用這個註解,以此來告訴Dagger咱們想要構造對象並提供這些依賴。
@Component: Components從根本上來講就是一個注入器,也能夠說是@Inject和@Module的橋樑,它的主要做用就是鏈接這兩個部分。 Components能夠提供全部定義了的類型的實例,好比:咱們必須用@Component註解一個接口而後列出全部的 @Modules組成該組件,如 果缺失了任何一塊都會在編譯的時候報錯。全部的組件均可以經過它的modules知道依賴的範圍。
@Scope: Scopes但是很是的有用,Dagger2能夠經過自定義註解限定註解做用域。後面會演示一個例子,這是一個很是強大的特色,由於就如前面說的同樣,不必讓每一個對象都去了解如何管理他們的實例。api
介紹的差很少了,來看一個簡單的實例,該實例參考了該項目和其相關的文章。該實例只是講解怎麼使用dagger2,並不涉及MVP,同時結合了當前流行的 Retrofit 2.0 、RxAndroid 等庫(回想剛開始的時候作Android本身封裝AsyncTask和使用BroadCast簡直和原始人刀耕火種無異啊)。網絡
首先來看看整個工程的結構:數據結構
在 gradle 配置文件中首先引入須要的庫:apply plugin: 'com.android.application'架構
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt' // 註釋處理
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "com.zyp.archi.githubclient_mdr_0"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
lintOptions {
disable 'InvalidPackage'
}
packagingOptions {
exclude 'META-INF/services/javax.annotation.processing.Processor'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:recyclerview-v7:23.1.1'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'com.google.dagger:dagger:2.0.2'
compile 'com.google.dagger:dagger-compiler:2.0.2'
provided 'org.glassfish:javax.annotation:10.0-b28'
}
因爲 Dagger 使用 apt 生成代碼,在Project gradle中還須要加入:app
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.5.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 } }
網絡相關的API在Application中生成,注意別忘了在Manifest文件中添加 <uses-permission android:name="android.permission.INTERNET"/> 和 綁定 android:name= .AppApplication 。
public class AppApplication extends Application{ private static AppApplication sInstance; private AppComponent appComponent; @Override public void onCreate(){ super.onCreate(); this.sInstance = this; setupCompoent(); } private void setupCompoent(){ appComponent = DaggerAppComponent.builder() .githubApiModule(new GithubApiModule()) .appModule(new AppModule(this)) .build(); } public static AppApplication getsInstance(){ return sInstance; } public AppComponent getAppComponent(){ return appComponent; } }
在 Application 中建立了 AppComponent 實例,並能夠獲取到,注意Appcomponent的實例化方式,Dagger + AppComponent.builder().somModule(new somModule()).build() ,注意寫法,大小寫也要注意,之後介紹Apt生成的原碼的時候就清楚了爲何要這樣寫,我相信這裏也是一個一開始很差理解的地方。接下來看看AppComponent是什麼鬼。
@Component(modules = { AppModule.class, GithubApiModule.class}) public interface AppComponent { // inject what void inject(ReposListActivity activity); }
AppCompoent 是一個 Interface,經過 @Component 添加了兩個 Module : AppModule、GithubApiModule。此外還有一個inject方法,其中的參數表示要注入的位置(先打個預防針,Component中的方法還能夠起到暴露資源,實現Component中的「繼承」的做用)。接下來看看AppModule 和 GithubApiModule。
@Module public class AppModule { private Application application; public AppModule(Application application){ this.application=application; } @Provides public Application provideApplication(){ return application; } }
@Module public class GithubApiModule { @Provides public OkHttpClient provideOkHttpClient() { OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS); okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS); return okHttpClient; } @Provides public Retrofit provideRetrofit(Application application, OkHttpClient okHttpClient){ Retrofit retrofit = new Retrofit.Builder() .baseUrl(application.getString(R.string.api_github)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加Rx適配器 .addConverterFactory(GsonConverterFactory.create()) // 添加Gson轉換器 .client(okHttpClient) .build(); return retrofit; } @Provides protected GithubApiService provideGitHubService(Retrofit retrofit) { return retrofit.create(GithubApiService.class); } }
這兩個Module很簡單,可是也包含不少東西。首先做爲Module,須要使用@Module註解,在被@Module註解修飾的類內部,使用@Provides註解來代表能夠提供的依賴對象。須要注意的是,有些由@Provides 提供的方法須要輸入參數,這些參數是怎麼來的呢?這對於剛剛接觸的新手來講有點棘手。這裏就先說了,這些須要傳入的參數須要其它用@Provides註解修飾的方法生成,好比在GithubModule.class 中的 provideGitHubService(Retrofit retrofit) 方法中的參數就是由另一個 @Provides 註解修飾的方法生成的,這裏就是public Retrofit provideRetrofit(Application application, OkHttpClient okHttpClient),那麼這個provideRetrofit()方法中的參數又是怎麼來的呢?請讀者本身去找。
此外爲何GithubModule會這樣設計,有沒有更加單方法?試想當有多種 ApiService 須要用到的時候,OkhttpClient中的超時設置須要不一樣的時候,Retrofit 實例的 Converter須要不一樣的時候咱們該如何應對?你們能夠思考一下,我也在思考。
這裏使用到了Retrofit,Retrofit的基本使用方法見這裏,雖然Retrofit2.0 還處於 beta 階段,可是這裏仍是任性的使用了,Retrofit2.0 新特性和基本使用方式見這裏。
public interface GithubApiService { @GET("/users/{user}/repos") Observable<ArrayList<Repo>> getRepoData(@Path("user") String user); }
GithubAPiService 中定義了一個訪問須要訪問的接口,注意這裏返回了一個Observable對象,這裏使用到了 RxJava 的相關知識,RxJava的好處也不少,這裏就不解釋了,有興趣入門參考見這裏,此外建議你們參閱這裏,其實都還不夠。
好了準備工做基本上作好了,如今來看看Activity怎麼寫。首先定義 BaseActivity,這裏提一下基本的Android應用開發架構,稍微有經驗的開發者確定都是會對Activity 進行分層的,將一些公共的代碼放在BaseActivity中,固然BaseActivity也許不止一種,或者不止一層,這就要具體問題具體分析了,此外通常還會引入utils 包來定義一些公共的靜態方法來實現對這個應用的AOP,具體能夠參考這裏。這裏BaseActivity 提供了ButterKnife依賴注入,提供了Component創建的方法和佈局文件獲取方法。
public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutId()); ButterKnife.bind(this); setupActivityComponent(AppApplication.getsInstance().getAppComponent()); } protected abstract void setupActivityComponent(AppComponent appComponent); protected abstract int getLayoutId(); }
這裏設計了兩個Activity,一個是MainActivity 一個是 ReposListActivity。MainActivity 提供一個按鈕,點擊則跳轉到ReposListActivity,顯示某一我的的GitHub帳戶上的信息。
public class MainActivity extends BaseActivity { @OnClick(R.id.showButton) public void onShowRepositoriesClick() { startActivity(new Intent(this, ReposListActivity.class)); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public int getLayoutId(){ return R.layout.activity_main; } @Override public void setupActivityComponent(AppComponent appComponent){ } }
/** * Created by zhuyp on 2016/1/10. */ public class ReposListActivity extends BaseActivity { @Bind(R.id.repos_rv_list) RecyclerView mRvList; @Bind(R.id.pbLoading) ProgressBar pbLoading; @Inject GithubApiService githubApiService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initView(); } @Override public int getLayoutId(){ return R.layout.activity_repos_list; } @Override public void setupActivityComponent(AppComponent appComponent){ appComponent.inject(this); } private void initView(){ LinearLayoutManager manager = new LinearLayoutManager(this); manager.setOrientation(LinearLayoutManager.VERTICAL); mRvList.setLayoutManager(manager); ListAdapter adapter = new ListAdapter(); mRvList.setAdapter(adapter); loadData(adapter); } private void loadData(final ListAdapter adapter){ showLoading(true); githubApiService.getRepoData(getUser()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new SimpleObserver<ArrayList<Repo>>() { @Override public void onNext(ArrayList<Repo> repos) { showLoading(false); adapter.setRepos(repos); } @Override public void onError(Throwable e){ showLoading(false); } }); } private String getUser(){ return "bird1015"; } public void showLoading(boolean loading) { Log.i("info",loading + " Repos"); pbLoading.setVisibility(loading ? View.VISIBLE : View.GONE); } }
簡單說一下 ReposListActivity 中的依賴注入,@Inject 注入了GithubApiService,在loadData() 方法中讀取對應用戶GitHub上的信息並返回。這裏咱們只提取了不多一部分信息,並顯示在RecyclerView中。因爲本篇文章不是介紹Rxjava在Android中的應用,RxJava 相關就不作具體解釋了,有興趣能夠從前面提到過的資料中去了解。從上面代碼能夠看到程序的處理邏輯異常清晰簡單,這就是Rxjava的威力所在,可是這也是一個很差上手的東西,建議仍是根據參考資料學習一下吧,無論可否實際運用,至少能看得懂啊。
這只是Dagger2的一個入門實例代碼,其實要搞懂Dagger須要看生成的源碼(後面會寫文章介紹),但願我能儘快再寫一至兩篇總結一下其它特性,好比 SubComponent ,Dependencies,Scope等。上面代碼中還用到的資源我就直接貼在下面了。
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.RepoViewHolder> { private ArrayList<Repo> mRepos; public ListAdapter() { mRepos = new ArrayList<>(); } public void setRepos(ArrayList<Repo> repos) { mRepos = repos; notifyItemInserted(mRepos.size() - 1); } @Override public RepoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_repo, parent, false); return new RepoViewHolder(view); } @Override public void onBindViewHolder(RepoViewHolder holder, int position) { holder.bindTo(mRepos.get(position)); } @Override public int getItemCount() { return mRepos.size(); } public static class RepoViewHolder extends RecyclerView.ViewHolder { @Bind(R.id.item_iv_repo_name) TextView mIvRepoName; @Bind(R.id.item_iv_repo_detail) TextView mIvRepoDetail; public RepoViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } public void bindTo(Repo repo) { mIvRepoName.setText(repo.name ); mIvRepoDetail.setText(String.valueOf(repo.description + "(" + repo.language + ")")); } } }
/** * Created by zhuyp on 2016/1/10. */ public class Repo { public String name; // 庫的名字 public String description; // 描述 public String language; // 語言 // public String testNullField; // 試錯 }
/** * Created by zhuyp on 2016/1/10. */ public class SimpleObserver<T> implements Observer<T> { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(T t) { } }
activity_main.xml
<?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" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <Button android:id="@+id/showButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/main_goto_activity"/> </LinearLayout>
activity_repo_list.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/repos_rv_list" android:layout_width="match_parent" android:layout_height="match_parent"/> <ProgressBar android:id="@+id/pbLoading" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
item_repo.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical"> <TextView android:id="@+id/item_iv_repo_name" tools:text="Repos name" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:textColor="@android:color/holo_purple" android:textSize="22sp"/> <TextView android:id="@+id/item_iv_repo_detail" tools:text="Details" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:textSize="14sp"/> </LinearLayout>
strings.xml
<resources> <string name="app_name">GithubClient_mdr_0</string> <string name="main_mock_data">自定義數據(測試)</string> <string name="main_goto_activity">跳轉列表</string> <string name="api_github">https://api.github.com</string> </resources>