Dagger2 入门实践

2,034 阅读9分钟

前言

什么是依赖注入

众所周知Dagger是一个依赖注入(Dependency Injection)框架简称DI,那么什么是依赖注入呢?其实熟悉Spring的小伙伴应该都非常清楚,因为Spring的核心思想就是依赖注入,管是控制层的Action对象,还是业务层的Service对象,还是持久层的DAO对象,都可在Spring的 管理下有机地协调、运行。

那么到底什么是依赖注入呢?

对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用,传递给它。也可以说,依赖被注入到对象中。也就是说,被注入的对象不需要重复的初始化就可以获得相应的引用。

比如说小明去超市买酱油,这样我们肯定需要一个小明的对象和酱油的对象。

物品基类

public class Things{

}  

酱油类

public class Sauce extend Things{

public void sauce(String type,float price){

    }
}

小明类

public class XiaoMing {
Things sauce=new Sauce("海天",5.6$);

/**
 * 买的动作
 * @param object
 */
private  void buy(Things things){
……
……
}
……
…………
………………
}

代码看起来貌似没有什么大问题,但是可以注意酱油在小明类中高度耦合,也就是说明天小明不打酱油了,去买毛线的时候小明这个类就要发生更改,后天买棒棒糖的时候当然也需要更改,这样下来小明没累死,我累死了。

如果使用依赖注入就不一样了

小明类

public class XiaoMing {
@Inject
Things things;

/**
 * 买的动作
 * @param object
 */
private  void buy(Things things){
……
……
}
……
…………
………………
}

我们根本不需要关注小明究竟买什么,也不用关注买的东西的颜色、大小、价格。也就是说被注入的地方会自动获得一个初始化好的对象。

Dagger2

Dagger2简介

Dagger2是Dagger1的分支,由谷歌公司接手开发,目前的版本是2.0。Dagger2是受到AutoValue项目的启发。Dagger是依赖注入的一种,说到依赖注入,标准定义是目标类中所依赖的其他的类的初始化过程,不是通过手动编码的方式创建,而是通过技术手段可以把其他的类的已经初始化好的实例自动注入到目标类中。说简单就是一次构建,到处注入。

使用Dagger2的好处

  1. 依赖的注入和配置独立于组件之外。

  2. 因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。

  3. 依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。

Dagger2基本组成

  • @Inject: 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。
  • @Module: Modules类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的 依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的app中可以有多个组成在一起的modules)。
  • @Provide: 在modules中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。
  • @Component: Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。

Dagger2简单入门

  • 引入Dagger

首先在项目根目录的 build.gradle文件中引入apt

buildscript {
repositories {
    jcenter()
}
dependencies {
    classpath 'com.android.tools.build:gradle:2.2.3'
    //引入相应apt
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

allprojects {
repositories {
    jcenter()
}
}

然后在所在module的build.gradle做如下配置

//首先引入 apt
apply plugin: 'com.neenbedankt.android-apt'
……
……
dependencies {
//java注解
provided 'javax.annotation:javax.annotation-api:1.2'
//dagger2
compile 'com.google.dagger:dagger:2.5'
//dagger编译器
apt 'com.google.dagger:dagger-compiler:2.5'
}
  • 简单例子

下面继续举个简单的栗子。
把User对象的值赋值到Activity里去。
代码的简单结构如下

User类

public class User {
private String name;
private int age;

public User() {
}

public User(String name, int age) {
    this.name = name;
    this.age = age;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}
}

UserModule类

@Module//标识该类提供依赖
public class UserModule {
@Provides//告诉Dagger我们想要构造对象并提供这些依赖
User provideUser() {
    return new User("周杰伦", 38);
}
}

UserComponent类

//Component 链接被注入的地方和提供依赖的地方
@Component(modules = UserModule.class)
public interface UserComponent {
void inject(DaggerActivity activity);
}

这个时候简单的注入就基本完成了,但是我们注意到Component文件是一个接口文件,我们肯定无法直接使用,现在我们需要借助于Dagger让它帮助我们根据注解生成对象

点击android studio的工具栏的 make progect

或者

就会在bulid文件夹下生成相应的文件如下:

当然我们用到的暂时只有以Dagger开头的Component文件,这个时候我们就可以在activity中使用完成User的注入了

DaggerActivity类

public class DaggerActivity extends AppCompatActivity {
//需要注入User对象
@Inject
User user;
UserComponent userComponent;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_dagger);

    this.textView = (TextView) findViewById(R.id.textView);
    //完成对User的注入
    userComponent = DaggerUserComponent.builder().build();
    userComponent.inject(this);
    //根据注入的User对象设置TextView
    textView.setText(user.getName()+":"+user.getAge());
}
}

布局文件就不在放出来了,就是一个简单的textview。具体效果如下。

这样我们就完成了Dagger的一个简单的注入,下面会介绍Dagger的一些进一步的用法

Dagger2深入学习

@Singleton 使用该注解实现单例的效果

还是上面的栗子,如果我们在Activity中注入两个User对象

public class DaggerActivity extends AppCompatActivity {
@Inject
User user1;
@Inject
User user2;

UserComponent userComponent;
private TextView textView;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_dagger);

    this.textView = (TextView) findViewById(R.id.textView);
    userComponent = DaggerUserComponent.builder().build();
    userComponent.inject(this);
    textView.setText("user1-->"+user1.toString()+"\nuser2-->"+user2);

}
}

然后对比两者的地址。

可以发现两者的地址并不相同,可见Dagger为我们生成了两个对象,但是我们有时候并不需要两个对象,如一些全局的配置
SharedPreferences 对象、数据库管理对象等,要是在平时我们就必须自己通过手写单例来实现,但是使用Dagger的@Singleton 注解我们就可以很轻松的完成对象的单例,而丝毫不用考虑单例如何实现。

UserModule类

@Module
public class UserModule {
@Singleton
@Provides
User provideUser() {
    return new User("周杰伦", 38);
    }
}

UserComponent类

@Singleton
@Component(modules = UserModule.class)
public interface UserComponent {
void inject(DaggerActivity activity);
}

只需要一个简单的注解就可以实现单例。这时候我们需要重新点击 make project来重新生成编译注解。

再次运行,我们可以看到这次Dagger只为我们生成了一个对象。

@Scope: Scopes自定义注解作用域

Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。如PerApp、perActivity等

PerActivity

@Scope
@Retention(RUNTIME)
public @interface PerActivity {}

PerApp

@Scope
@Retention(RUNTIME)
public @interface PerApp {}

该自定义注解需要同时在Module和component中使用才会起作用,会在下面的栗子中进一步说明。

其他相关注解

@Qualifier限定符,可以对注解做出一定的限定,具体可以参照java相关注解。

懒加载 lazy 即在等到调用的时候才注入。
在使用的地方使用user.get()就能得到一个User对象

public class DaggerActivity extends AppCompatActivity {
@Inject
Lazy <User> user;

UserComponent userComponent;
private android.widget.TextView textView;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_dagger);

    this.textView = (TextView) findViewById(R.id.textView);
    userComponent = DaggerUserComponent.builder().build();
    userComponent.inject(this);
    User myUser = this.user.get();
    textView.setText(myUser.getName()+":"+ myUser.getAge());
}

}

Dagger与MVP结合

上面讲了那么多的Dagger的基础,我们还没有进行真正的运用,接下来我们就结合MVP(如果不知道什么是MVP的话,自行Gogle)来一起来做一个简单的栗子。

首选看一下简单的代码结构

可以看到我们仅仅是在原来的MVP结构上新增两个(module和component),与MVP的结合我们主要是用在对persenter的改造上,完成对Persenter的注入。

由上述的代码命名我们可以很容易的看出这是一个简单模拟登陆的栗子,在model层主要完成登陆判断的逻辑,在persenter层主要是对View的控制,在module主要是完成persenter注入提供依赖,在component层主要是协调被注入Activity和module的关系。

User.class

public class User {
private String userName;
private String password;

public User() {
}

public User(String userName, String password) {
    this.userName = userName;
    this.password = password;
}

public String getUserName() {
    return userName;
}

public void setUserName(String userName) {
    this.userName = userName;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}
}

ILoginModel.class

public interface ILoginModel {

void login(String userName, String password,LoginListener listener);

interface LoginListener {
    void loginSuccess();
    void loginFailed(String msg);
}
}

LoginModelImpl.class

完成登陆逻辑,这里可以进行网络操作,完成登陆操作,这次我们仅仅在本地模拟判断用户名和密码,这里使用postDelayed()来模拟网络操作。

public class LoginModelImpl implements ILoginModel {

@Override
public void login(String userName, String password, LoginListener listener) {
     new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
    if (userName.equals("flyou") && password.equals("553274238")) {
        listener.loginSuccess();
    } else {
        listener.loginFailed("用户名密码错误");
    }
      },2000);
}
}

ILoginView.class

提供视图相关接口

public interface ILoginView {
void showLoading();
void hideLoading();
void showError(String msg);
void LoginSuccess();
}

LoginPersenter.class

完成View和数据的协调。

public class LoginPersenter {
private ILoginView loginView;
private ILoginModel loginModel;

public LoginPersenter(ILoginView loginView) {
    this.loginModel=new LoginModelImpl();
    this.loginView = loginView;
}
public void login(final String userName, final String password){
    loginView.showLoading();

            loginModel.login(userName, password, new ILoginModel.LoginListener() {
                @Override
                public void loginSuccess() {
                    loginView.hideLoading();
                    loginView.LoginSuccess();
                }

                @Override
                public void loginFailed(String msg) {
                    loginView.hideLoading();
                    loginView.showError(msg);
                }
            });
        }
       }
}

PerApp.class

自定义注解范围

@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)

public @interface PerApp {
}

LoginPersenterModule.class

和上面简单的例子一样我们需要使用LoginPersenter就要完成对LoginPersenter的注入,唯一不同就是这里的我们是这里我们依赖的LoginPersenter需要参数,那么我们再这里就要完成第参数的依赖,如果有多个参数我们就要完成对多个参数的依赖

@Module
public class LoginPersenterModule {
private LoginActivity loginActivity;

public LoginPersenterModule(LoginActivity loginActivity) {
    this.loginActivity = loginActivity;
}
@PerApp
@Provides
LoginActivity provideLoginActivity(){
    return loginActivity;
}

@PerApp
@Provides
LoginPersenter ProvideLoginPersenter(LoginActivity loginActivity){
    return new LoginPersenter(loginActivity);
}
}

LoginPersenterCompoent.class
完成module与注入地方的关系协调

@PerApp//定义注解范围
@Singleton
@Component(modules = LoginPersenterModule.class)
public interface LoginPersenterCompoent {
void inject(LoginActivity activity);
}

LoginActivity.class

public class LoginActivity extends AppCompatActivity implements ILoginView {
@Inject
LoginPersenter loginPersenter;
private LoginPersenterCompoent loginPersenterCompoent;
private android.widget.EditText userName;
ProgressDialog progressDialog;
PasswordToggleEditText2 password;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);
    this.password = (PasswordToggleEditText2) findViewById(R.id.password);
    this.userName = (EditText) findViewById(R.id.userName);
    loginPersenterCompoent= DaggerLoginPersenterCompoent.builder()
    .loginPersenterModule(new LoginPersenterModule(this)).build();
    loginPersenterCompoent.inject(this);
}

public void login(View view) {
    loginPersenter.login(userName.getText().toString().trim(),password.getText().toString().trim());

}

@Override
public void showLoading() {
     progressDialog=ProgressDialog.show(LoginActivity.this,"正在登录","正在登录请稍后……",true,false);
}

@Override
public void hideLoading() {
    progressDialog.dismiss();
}

@Override
public void showError(String msg) {
    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}

@Override
public void LoginSuccess() {
    Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
}
}

布局文件真的很简单,就不再贴出了。

到这里,文章就基本结束了,笔者有什么写错的地方欢迎拍砖~