您的位置:首页 > 其它

Dagger2 使用初步

2016-01-10 22:29 387 查看
  Dagger2是一个Android依赖注入框架,由谷歌开发,最早的版本Dagger1由Square公司开发。依赖注入框架主要用于模块间解耦,提高代码的健壮性和可维护性。Dagger这个库的取名不仅仅来自它的本意“匕首”,同时也暗示了它的原理。JakeWharton在对Dagger的介绍中指出,Dagger即DAG-er,这里的DAG即数据结构中的DAG——有向无环图(DirectedAcyclicGraph)。也就是说,Dagger是一个基于有向无环图结构的依赖注入库,因此Dagger的使用过程中不能出现循环依赖。

  Android开发从一开始的MVC框架,到MVP,到MVVM,不断变化。现在MVVM的data-binding还在实验阶段,传统的MVC框架Activity内部可能包含大量的代码,难以维护,现在主流的架构还是使用MVP(Model+View+Presenter)的方式。但是MVP框架也有可能在Presenter中集中大量的代码,引入DI框架Dagger2可以实现Presenter与Activity之间的解耦,Presenter和其它业务逻辑之间的解耦,提高模块化和可维护性。

  说了那么多,那什么是依赖呢?如果在ClassA中,有ClassB的实例,则称ClassA对ClassB有一个依赖。例如下面类Human中用到一个Father对象,我们就说类Human对类Father有一个依赖(参考)。

publicclassHuman{
...
Fatherfather;
...
publicHuman(){
father=newFather();
}
}


  那什么又是依赖注入呢,依赖注入就是非自己主动初始化依赖,而通过外部来传入依赖的方式,简单来说就是不使用new来创建依赖对象。使用Dagger2创建依赖对象,我们就不用手动初始化了。个人认为Dagger2和MVP架构是比较不错的搭配,Activity依赖的Presenter可以使用该DI框架直接生成,实现解耦,简单的使用方式如下:

publicclassMainActivityextendsBaseActivity{
@Inject
MainActivityPresenterpresenter;

...

}


  上面这些主要是对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,同时结合了当前流行的Retrofit2.0、RxAndroid等库(回想刚开始的时候做Android自己封装AsyncTask和使用BroadCast简直和原始人刀耕火种无异啊)。

  首先来看看整个工程的结构:



  在gradle配置文件中首先引入需要的库:applyplugin:'com.android.application'


applyplugin:'com.android.application'
applyplugin:'com.neenbedankt.android-apt'//注释处理

android{
compileSdkVersion23
buildToolsVersion"23.0.2"

defaultConfig{
applicationId"com.zyp.archi.githubclient_mdr_0"
minSdkVersion16
targetSdkVersion23
versionCode1
versionName"1.0"
}

lintOptions{
disable'InvalidPackage'
}

packagingOptions{
exclude'META-INF/services/javax.annotation.processing.Processor'
}

buildTypes{
release{
minifyEnabledfalse
proguardFilesgetDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
}
}

dependencies{
compilefileTree(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生成代码,在Projectgradle中还需要加入:

buildscript{
repositories{
jcenter()
}
dependencies{
classpath'com.android.tools.build:gradle:1.5.0'
classpath'com.neenbedankt.gradle.plugins:android-apt:1.8'
//NOTE:Donotplaceyourapplicationdependencieshere;theybelong
//intheindividualmodulebuild.gradlefiles
}
}


  网络相关的API在Application中生成,注意别忘了在Manifest文件中添加<uses-permissionandroid:name="android.permission.INTERNET"/>和绑定android:name=.AppApplication。

publicclassAppApplicationextendsApplication{

privatestaticAppApplicationsInstance;
privateAppComponentappComponent;

@Override
publicvoidonCreate(){
super.onCreate();
this.sInstance=this;
setupCompoent();
}

privatevoidsetupCompoent(){
appComponent=DaggerAppComponent.builder()
.githubApiModule(newGithubApiModule())
.appModule(newAppModule(this))
.build();
}

publicstaticAppApplicationgetsInstance(){
returnsInstance;
}

publicAppComponentgetAppComponent(){
returnappComponent;
}
}


  在Application中创建了AppComponent实例,并可以获取到,注意Appcomponent的实例化方式,Dagger+AppComponent.builder().somModule(newsomModule()).build(),注意写法,大小写也要注意,以后介绍Apt生成的原码的时候就清楚了为什么要这样写,我相信这里也是一个一开始不好理解的地方。接下来看看AppComponent是什么鬼。

@Component(modules={AppModule.class,GithubApiModule.class})
publicinterfaceAppComponent{
//injectwhat
voidinject(ReposListActivityactivity);
}


  AppCompoent是一个Interface,通过@Component添加了两个Module:AppModule、GithubApiModule。此外还有一个inject方法,其中的参数表示要注入的位置(先打个预防针,Component中的方法还可以起到暴露资源,实现Component中的“继承”的作用)。接下来看看AppModule和GithubApiModule。

@Module
publicclassAppModule{
privateApplicationapplication;

publicAppModule(Applicationapplication){
this.application=application;
}

@Provides
publicApplicationprovideApplication(){
returnapplication;
}
}


@Module
publicclassGithubApiModule{

@Provides
publicOkHttpClientprovideOkHttpClient(){
OkHttpClientokHttpClient=newOkHttpClient();
okHttpClient.setConnectTimeout(60*1000,TimeUnit.MILLISECONDS);
okHttpClient.setReadTimeout(60*1000,TimeUnit.MILLISECONDS);
returnokHttpClient;
}

@Provides
publicRetrofitprovideRetrofit(Applicationapplication,OkHttpClientokHttpClient){
Retrofitretrofit=newRetrofit.Builder()
.baseUrl(application.getString(R.string.api_github))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加Rx适配器
.addConverterFactory(GsonConverterFactory.create())//添加Gson转换器
.client(okHttpClient)
.build();
returnretrofit;
}

@Provides
protectedGithubApiServiceprovideGitHubService(Retrofitretrofit){

returnretrofit.create(GithubApiService.class);
}
}


  这两个Module很简单,但是也包含很多东西。首先作为Module,需要使用@Module注解,在被@Module注解修饰的类内部,使用@Provides注解来表明可以提供的依赖对象。需要注意的是,有些由@Provides提供的方法需要输入参数,这些参数是怎么来的呢?这对于刚刚接触的新手来说有点棘手。这里就先说了,这些需要传入的参数需要其它用@Provides注解修饰的方法生成,比如在GithubModule.class中的provideGitHubService(Retrofitretrofit)方法中的参数就是由另外一个@Provides注解修饰的方法生成的,这里就是publicRetrofitprovideRetrofit(Applicationapplication,OkHttpClientokHttpClient),那么这个provideRetrofit()方法中的参数又是怎么来的呢?请读者自己去找。

  此外为什么GithubModule会这样设计,有没有更加单方法?试想当有多种ApiService需要用到的时候,OkhttpClient中的超时设置需要不同的时候,Retrofit实例的Converter需要不同的时候我们该如何应对?大家可以思考一下,我也在思考。

  这里使用到了Retrofit,Retrofit的基本使用方法见这里,虽然Retrofit2.0还处于beta阶段,但是这里还是任性的使用了,Retrofit2.0新特性和基本使用方式见这里

publicinterfaceGithubApiService{
@GET("/users/{user}/repos")
Observable<ArrayList<Repo>>getRepoData(@Path("user")Stringuser);
}


  GithubAPiService中定义了一个访问需要访问的接口,注意这里返回了一个Observable对象,这里使用到了RxJava的相关知识,RxJava的好处也很多,这里就不解释了,有兴趣入门参考见这里,此外建议大家参阅这里,其实都还不够。

  好了准备工作基本上做好了,现在来看看Activity怎么写。首先定义BaseActivity,这里提一下基本的Android应用开发架构,稍微有经验的开发者肯定都是会对Activity进行分层的,将一些公共的代码放在BaseActivity中,当然BaseActivity也许不止一种,或者不止一层,这就要具体问题具体分析了,此外一般还会引入utils包来定义一些公共的静态方法来实现对这个应用的AOP,具体可以参考这里。这里BaseActivity提供了ButterKnife依赖注入,提供了Component建立的方法和布局文件获取方法。

publicabstractclassBaseActivityextendsAppCompatActivity{

@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
ButterKnife.bind(this);
setupActivityComponent(AppApplication.getsInstance().getAppComponent());
}

protectedabstractvoidsetupActivityComponent(AppComponentappComponent);
protectedabstractintgetLayoutId();

}


  这里设计了两个Activity,一个是MainActivity一个是ReposListActivity。MainActivity提供一个按钮,点击则跳转到ReposListActivity,显示某一个人的GitHub账户上的信息。

publicclassMainActivityextendsBaseActivity{

@OnClick(R.id.showButton)
publicvoidonShowRepositoriesClick(){
startActivity(newIntent(this,ReposListActivity.class));
}

@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);

}

@Override
publicintgetLayoutId(){
returnR.layout.activity_main;
}

@Override
publicvoidsetupActivityComponent(AppComponentappComponent){

}
}


/**
*Createdbyzhuypon2016/1/10.
*/
publicclassReposListActivityextendsBaseActivity{

@Bind(R.id.repos_rv_list)
RecyclerViewmRvList;

@Bind(R.id.pbLoading)
ProgressBarpbLoading;

@Inject
GithubApiServicegithubApiService;

@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);

initView();
}

@Override
publicintgetLayoutId(){
returnR.layout.activity_repos_list;
}

@Override
publicvoidsetupActivityComponent(AppComponentappComponent){
appComponent.inject(this);
}

privatevoidinitView(){
LinearLayoutManagermanager=newLinearLayoutManager(this);
manager.setOrientation(LinearLayoutManager.VERTICAL);
mRvList.setLayoutManager(manager);

ListAdapteradapter=newListAdapter();
mRvList.setAdapter(adapter);
loadData(adapter);
}

privatevoidloadData(finalListAdapteradapter){
showLoading(true);
githubApiService.getRepoData(getUser())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(newSimpleObserver<ArrayList<Repo>>(){
@Override
publicvoidonNext(ArrayList<Repo>repos){
showLoading(false);
adapter.setRepos(repos);
}
@Override
publicvoidonError(Throwablee){
showLoading(false);
}
});
}

privateStringgetUser(){
return"bird1015";
}

publicvoidshowLoading(booleanloading){
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等。上面代码中还用到的资源我就直接贴在下面了。

publicclassListAdapterextendsRecyclerView.Adapter<ListAdapter.RepoViewHolder>{

privateArrayList<Repo>mRepos;

publicListAdapter(){
mRepos=newArrayList<>();
}

publicvoidsetRepos(ArrayList<Repo>repos){
mRepos=repos;
notifyItemInserted(mRepos.size()-1);
}

@Override
publicRepoViewHolderonCreateViewHolder(ViewGroupparent,intviewType){
Viewview=LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_repo,parent,false);
returnnewRepoViewHolder(view);
}

@Override
publicvoidonBindViewHolder(RepoViewHolderholder,intposition){
holder.bindTo(mRepos.get(position));
}

@Override
publicintgetItemCount(){
returnmRepos.size();
}

publicstaticclassRepoViewHolderextendsRecyclerView.ViewHolder{

@Bind(R.id.item_iv_repo_name)
TextViewmIvRepoName;
@Bind(R.id.item_iv_repo_detail)
TextViewmIvRepoDetail;

publicRepoViewHolder(ViewitemView){
super(itemView);
ButterKnife.bind(this,itemView);
}

publicvoidbindTo(Reporepo){
mIvRepoName.setText(repo.name);
mIvRepoDetail.setText(String.valueOf(repo.description+"("+repo.language+")"));
}
}
}


/**
*Createdbyzhuypon2016/1/10.
*/
publicclassRepo{
publicStringname;//库的名字
publicStringdescription;//描述
publicStringlanguage;//语言
//publicStringtestNullField;//试错
}


/**
*Createdbyzhuypon2016/1/10.
*/
publicclassSimpleObserver<T>implementsObserver<T>{
@Override
publicvoidonCompleted(){

}

@Override
publicvoidonError(Throwablee){

}

@Override
publicvoidonNext(Tt){

}
}


activity_main.xml

<?xmlversion="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

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns: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

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns: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="Reposname"
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>
<stringname="app_name">GithubClient_mdr_0</string>

<stringname="main_mock_data">自定义数据(测试)</string>
<stringname="main_goto_activity">跳转列表</string>
<stringname="api_github">https://api.github.com</string>

</resources>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: