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>
make it simple, make it happen

Dagger2 使用初步的更多相关文章

  1. Dagger2 生成代码学习

    接上一篇文章介绍了Dagger2的初步使用,相信刚接触的人会觉得很奇怪,怎么会有很多自己没有定义的代码出现,为什么Component的创建方式是那样的.为了搞清楚这些东西,我们需要查看一下Dagger ...

  2. Dagger2进阶必备技能

    之前写过一篇文章介绍Dagger2的初步知识, 本篇文章主要介绍Dagger2的进阶知识点. 主要包含的内有有 @Binds与@Provides的使用 Provider与Lazy的使用 依赖与包含 D ...

  3. 【Android - 框架】之Dagger2+MVP的用法

    MVP模式本身相比于MVC模式就已经把View层和Controller层从Activity中进行了分离,将Model层和View层用Presenter层隔开,实现了初步的解耦.如果再加入Dagger2 ...

  4. Android注解使用之Dagger2实现项目依赖关系解耦

    前言: 最近牵头发起公司app的重构工作,如何通过重构让项目的耦合降低.开发效率提高,一直是我努力的方向,今天来学习一下一个注解框架Dagger2,然后看看如何使用它来降低项目的耦合. Dagger2 ...

  5. 移动端之Android开发的几种方式的初步体验

    目前越来越多的移动端混合开发方式,下面列举的大多数我都略微的尝试过,就初步的认识写个简单的心得: 开发方式 开发环境 是否需要AndroidSDK 支持跨平台 开发语言&技能 MUI Win+ ...

  6. CSharpGL(29)初步封装Texture和Framebuffer

    +BIT祝威+悄悄在此留下版了个权的信息说: CSharpGL(29)初步封装Texture和Framebuffer +BIT祝威+悄悄在此留下版了个权的信息说: Texture和Framebuffe ...

  7. Android自定义View初步

    经过上一篇的介绍,大家对于自定义View一定有了一定的认识,接下来我们就以实现一个图片下显示文字的自定义View来练习一下.废话不多说,下面进入我们的正题,首先看一下我们的思路,1.我们需要通过在va ...

  8. 初步认识Node 之Node为何物

    很多人即便是在使用了Node之后也不知道它到底是什么,阅读完本文你应该会有一个初步的.具体的概念了.    Node的目标 提供一种简单的构建可伸缩网络程序的方法.那么,什么是可伸缩网络程序呢?可伸缩 ...

  9. Dagger2系列之使用方法

    本系列只讲使用方法和使用中遇到的问题,如果还对dagger2还不了解的童鞋儿可以参考文章: http://www.jianshu.com/p/cd2c1c9f68d4 http://www.jians ...

随机推荐

  1. scala中的Actor

    1.介绍 2.简单示例 3.第二个程序 4.通信程序 package day01 import scala.actors.Actor case class Message(content: Strin ...

  2. 使用postman发送数据并构建collections执行测试

    1.安装 下载地址:https://www.getpostman.com/.直接安装,成功后在chorme的应用程序中会多出一个Postman.如果无法在google store上直接安装,可以下载. ...

  3. CSS元素居中的常用方法

    只有普通流和绝对定位的元素可以设置居中,浮动元素是不存在居中的. 1.块级元素居中 1) 普通流元素(position : static 或 position : relative) 水平居中:{ m ...

  4. Issue 3:数据处理基本认识

    介绍 传统数据库对数据处理一般都分成两类:OLTP和OLAP. 数据分析(OLAP)的前提条件是要准备数据. 然后才是具体的数据分析,对此,可以分为统计型的数据分析和挖掘性的数据分析. 最后对分析结果 ...

  5. win7怎么彻底关闭全/半角转换快捷键? imetool.exe

    from:http://bbs.csdn.net/topics/370040889 全半角转换最tm的烦人,快捷键是shift+space,不小心按到,就各种不爽, 系统看着是可以重新设置快捷键的,但 ...

  6. .NET Core Analysis

    .NET Core 1.0.1 Module Component .NET Core MongoDB MongoDB.Driver There has a nuget package availabl ...

  7. Hadoop等软件常见运行问题及解决办法

    Hadoop常见问题及解决办法  1.问题:java.io.IOException: Could not locate executable null\bin\winutils.exe in the ...

  8. layzr.js新版使用方法

    转载请注明出处:http://www.cnblogs.com/fiter/p/5413919.html 前言 今天研究这个JS库,本人新手,将官方的js代码下载下来后始终无法正常使用,经过多番尝试,终 ...

  9. MongoDB安装及入门

    下载 windows下的是3.2的版本 https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/ mongodb采用27 ...

  10. Leetcode: Rearrange String k Distance Apart

    Given a non-empty string str and an integer k, rearrange the string such that the same characters ar ...