Dagger2 使用初步

Dagger2 是一個Android依賴注入框架,由谷歌開發,最早的版本Dagger1 由Square公司開發。依賴注入框架主要用於模塊間解耦,提高代碼的健壯性和可維護性。Dagger 這個庫的取名不僅僅來自它的本意“匕首”,同時也暗示了它的原理。Jake Wharton 在對 Dagger 的介紹中指出,Dagger 即 DAG-er,這裏的 DAG 即數據結構中的 DAG——有向無環圖(Directed Acyclic Graph)。也就是說,Dagger 是一個基於有向無環圖結構的依賴注入庫,因此Dagger的使用過程中不能出現循環依賴。

  Android開發從一開始的MVC框架,到MVP,到MVVM,不斷變化。現在MVVM的data-binding還在實驗階段,傳統的MVC框架Activity內部可能包含大量的代碼,難以維護,現在主流的架構還是使用MVP(Model + View + Presenter)的方式。但是 MVP 框架也有可能在Presenter中集中大量的代碼,引入DI框架Dagger2 可以實現 Presenter 與 Activity 之間的解耦,Presenter和其它業務邏輯之間的解耦,提高模塊化和可維護性。

  說了那麼多,那什麼是依賴呢?如果在 Class A 中,有 Class B 的實例,則稱 Class A 對 Class B 有一個依賴。例如下面類 Human 中用到一個 Father 對象,我們就說類 Human 對類 Father 有一個依賴(參考)。

複製代碼
public class Human {
    ...
    Father father;
    ...
    public Human() {
        father = new Father();
    }
}
複製代碼

  那什麼又是依賴注入呢,依賴注入就是非自己主動初始化依賴,而通過外部來傳入依賴的方式,簡單來說就是不使用 new 來創建依賴對象。使用 Dagger2 創建依賴對象,我們就不用手動初始化了。個人認爲 Dagger2 和 MVP 架構是比較不錯的搭配,Activity 依賴的 Presenter 可以使用該DI框架直接生成,實現解耦,簡單的使用方式如下:

複製代碼
public class MainActivity extends BaseActivity {
      @Inject
       MainActivityPresenter presenter;

     ...  
    
}
複製代碼

  上面這些主要是對DI框架有一個初步全局的瞭解,下面來看看Dagger2的基本內容。Dagger2 通過註解來生成代碼,定義不同的角色,主要的註解有:@Inject、@Module 、@Component 、@Provides 、@Scope 、@SubComponent 等。

  @Inject: 通常在需要依賴的地方使用這個註解。換句話說,你用它告訴Dagger這個類或者字段需要依賴注入。這樣,Dagger就會構造一個這個類的實例並滿足他們的依賴。
  @Module: Modules類裏面的方法專門提供依賴,所以我們定義一個類,用@Module註解,這樣Dagger在構造類的實例的時候,就知道從哪裏去找到需要的 依賴。modules的一個重要特徵是它們設計爲分區並組合在一起(比如說,在我們的app中可以有多個組成在一起的modules)。
  @Provides: 在modules中,我們定義的方法是用這個註解,以此來告訴Dagger我們想要構造對象並提供這些依賴。
  @Component: Components從根本上來說就是一個注入器,也可以說是@Inject和@Module的橋樑,它的主要作用就是連接這兩個部分。 Components可以提供所有定義了的類型的實例,比如:我們必須用@Component註解一個接口然後列出所有的   @Modules組成該組件,如 果缺失了任何一塊都會在編譯的時候報錯。所有的組件都可以通過它的modules知道依賴的範圍。
  @Scope: Scopes可是非常的有用,Dagger2可以通過自定義註解限定註解作用域。後面會演示一個例子,這是一個非常強大的特點,因爲就如前面說的一樣,沒必要讓每個對象都去了解如何管理他們的實例。

  介紹的差不多了,來看一個簡單的實例,該實例參考了該項目和其相關的文章。該實例只是講解怎麼使用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中還需要加入:

複製代碼
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>
複製代碼

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章