【android】摆正姿势,dagger2原来如此简单
1.前言
依稀记得两年之前就偷偷学习过dagger2的使用,当然那个时候也是云里雾里,对照着博客的确实现了对象的注入,但是后来基本上没有再深入的学习过,尽管偶尔也会浏览到dagger2的文章,但是实际项目中并没有使用,一直搁置着,最近忽然脑子发热,想学习了,恰好学到了这里,趁着脑子热,索性就记录下,待以后需要使用的翻看便知。
2.什么是dagger2
dagger2是一款非常有名的依赖注入框架,那么问题来了,什么是依赖注入,很简单,就是字面意思,当你需要创立对象的时候,依赖于其余的程序进行注入,而不是自己主动去创立。
平常我们用到的set方法其实也是一种依赖注入的方式,再比方有参构造函数传递过来的对象同样属于注入的一种。
优点:
- 避免无用的体力劳动(重复屡次new对象)
- 更好的管理类的实例,避免耦合
引入dagger2:
annotationProcessor 'com.google.dagger:dagger-compiler:2.11' implementation 'com.google.dagger:dagger-android:2.11' implementation 'com.google.dagger:dagger-android-support:2.11' // if you use the support libraries annotationProcessor 'com.google.dagger:dagger-android-processor:2.11'
3.最简单的对象注入(无参)
dagger2的使用依赖于几个比较主要的注解,下面跟随我一切来实现一个简单的对象注入,我们想要在MainActivity创立一个School对象:
1.首先创立School类
public class School { @Inject public School() { }}
可以看到我们在School的构造函数上加上了@Inject注解,此处的作用就是 :
标识School类可以被注入,相对于给注入的对象指定一个创立的来源,不然程序无法知晓假如创立school对象。
2.school类有了,也标明了它可以被注入(使用@Inject注解),下面我们就要找个东西来创立这个实例,@Component注解就是这个作用,它相当于一个注入器,负责生产类的实例,起到一个桥梁的作用,一端是生产,一端是注入。
@Componentpublic interface MainComponent { void inject(MainActivity activity);}
3.Android Studio中 build—> make project,或者者单独编译下module也可以,编译完成后将会自动生成dagger2注入所需的代码,代码生成位置在:
2.png
此时我们开始把school对象注入到MainActivity中,代码如下:
public class MainActivity extends AppCompatActivity { @Inject School school; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //inject方法负责连接所创立的实例和mainActivity,此时实例被赋值给school变量 DaggerMainComponent.create().inject(this); Log.e("daggerLog","school :【"+school+"】"); }}
此时运行程序,就会出现下面的打印结果:
1.png
我们发现,MainActivity并没有new School()的操作,仅仅通过@Inject注解,并且进行inject连接之后,对象就“自动”产生了。
有些杠精可能要说了,这TM实在逗我,创立个对象一行代码搞定了,你给我弄这么多花里胡哨的不嫌麻烦吗?我只能说你说的没毛病,单个对象不频繁的创立或者许并不需要这么麻烦,但是为了降低代码的耦合性,或者者说从架构方面考虑,可能这真的很有必要。
4.有参的对象注入
上面我们只是简单定义了一个无参的School对象,那么有参的对象应该如何注入呢,下面我们改造下School类。
public class School { private String name; private String address; @Inject public School(String name, String address) { this.name = name; this.address = address; }}
嗯,也没干啥,就添加了两个参数,我们来编译一下:
3.png
完犊子,报错了,看错误提醒里面出现了@Provides注解,那么对于有参的构造函数,该如何进行注入呢,dagger2给我们提供了两个注解,@Module和@Provides,
@Module标识我是用来提供注入实例的,@Provides用来表示具体提供实例的方法,看下面的这段代码:
@Modulepublic class MainModules { @Provides public School provideSchool(){ return new School("南京高级中学","江苏南京"); }}
在class名字上方加上@Module注解,在具体提供实例的地方增加上@Provides注解,此时School就具有了被注入的能力,但前面我们说到连接实例和被注入端使用的是@Component注解,那么如何为@Component指定使用哪个Module注入呢?
@Component(modules = MainModules.class)public interface MainComponent { void inject(MainActivity activity);}
很简单,通过@Component中的modules关键字来指定提供实例的module,假如有多个module可以使用{}括起来,形如下面这样的形式:
@Component(modules = {MainModules.class,OtherModules.class})
此时我们再次编译项目,并且修改MainActivity代码如下:
public class MainActivity extends AppCompatActivity { @Inject School school; @Inject School school2; private Button btnJump; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerMainComponent.create().inject(this); Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】"); Log.e("daggerLog","school add >【"+school+"】"); Log.e("daggerLog","school2 add >【"+school2+"】"); }}
运行后,结果打印如下:
4.png
此时School对象已经注入完毕,可以正常使用了,此处大家可能发现了我们注入了两个School对象,但假如我们想保持School单例的话,该如何操作呢?
当然dagger2不会让我们为这些所担忧,它提供了@Singleton注解用于解决单例的情况;
注意:Singleton是局部单例,和生命周期绑定,注入activity单例范围就是当前activity之内,注入application就是全局单例。
下面我们来验证一下,修改代码,在Module和Component中分别加入@Singleton注解,缺一不可,此时代码如下:
@Modulepublic class MainModules { @Singleton @Provides public School provideSchool(){ return new School("南京高级中学","江苏南京"); }}@Singleton@Component(modules = MainModules.class)public interface MainComponent { void inject(MainActivity activity);}
运行后,结果打印如下:
5.png
可以看到此时在MainActivity中的确实现了单例,那么我们再次修改下代码,新添加SecordActivity,并把School对象注入到SecordActivity中,并在MainActivity中增加跳转按钮,点击后跳转到SecordActivity:
@Singleton@Component(modules = MainModules.class)public interface MainComponent { void inject(MainActivity activity); void inject(SecordActivity activity);}public class SecordActivity extends AppCompatActivity { @Inject School school; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_secord); DaggerMainComponent.create().inject(this); Log.e("daggerLog","secord school add >【"+school+"】"); }}
编译运行后,点击跳转后打印结果如下:
6.png
就像我们前面说的那样,@Singleton是局部单例,此时的两个Activity中的School对象已经是不同的对象了。
我们看下@Singleton的源码:
@Scope@Documented@Retention(RUNTIME)public @interface Singleton {}
发现里面有个@Scope注解,@Scope其实才是单例的关键,@Scope是用来管理生命周期的,而@Singleton是dagger2通过@Scope提供的一种默认实现。
5.全局单例的实现
下面我们看一下全局单例该如何实现,首先创立MyApplicaiton:
public class MyApplicaiton extends Application { private AppComponent appComponent; @Override public void onCreate() { super.onCreate(); appComponent = DaggerAppComponent.builder().build(); } public AppComponent getAppComponent() { return appComponent; }}@Singleton@Component(modules = {AppModule.class})public interface AppComponent { void inject(MainActivity activity); void inject(SecordActivity activity);}@Modulepublic class AppModule { @Singleton @Provides public School provideSingleSchool(){ return new School("全局单例的School","中国"); }}public class MainActivity extends AppCompatActivity { @Inject School school; @Inject School school2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MyApplicaiton)getApplication()).getAppComponent().inject(this); Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】"); Log.e("daggerLog","school add >【"+school+"】"); Log.e("daggerLog","school2 add >【"+school2+"】"); }}public class SecordActivity extends AppCompatActivity { @Inject School school; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_secord); ((MyApplicaiton)getApplication()).getAppComponent().inject(this); Log.e("daggerLog","secord school add >【"+school+"】"); }}
而后运行,跳转到SecordActivity,日志打印如下:
7.png
通过日志可以发现,所有的School对象都是同一对象,实现了全局的单例!
6.如何传递context.
有些时候我们在通过@Provides提供对象的时候需要context参数,形如下面的形式:
public class ShowUtils { private Context context; @Inject public ShowUtils(Context context) { this.context = context; }}
这个时候假如我们正常通过@Provides的方式提供的话,dagger是无法确定此处的context是哪种类型的,是Application的?还是Activity的?此时编译就会报错,那么我们该如何指定Context具体是何种类型呢?
此时又出现了一个新的注解@Qualifier,@Qualifier的作用是当dagger无法确定类型的时候,或者者说当我们@module里有多个provides提供相同类型的返回值(dagger提供对象是根据返回值匹配的)的时候,对类型进行限定,下面举个例子来体验一下,首先自己设置一个限定注解:
@Qualifier@Documented@Retention(RetentionPolicy.RUNTIME)public @interface ForActivityContext {}
而后我们修改下上面例子中的MainModules:
@Modulepublic class MainModules { private Context context; public MainModules(Context context) { this.context = context; } @Singleton @Provides public School provideSchool(){ return new School("南京高级中学","江苏南京"); } @Singleton @Provides public ShowUtils provideShowUtils(){ return new ShowUtils(context); } //注意这里,此处限定我们使用的context是activity的context @ForActivityContext @Provides public Context provideContext(){ return context; }}
当然我们还需要在需要此对象的Activity上增加此注解:
@ForActivityContextpublic class MainActivity extends AppCompatActivity { @Inject School school; @Inject School school2; @Inject ShowUtils showUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerMainComponent.builder().mainModules(new MainModules(this)).build() .inject(this); Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】"); Log.e("daggerLog","school add >【"+school+"】"); Log.e("daggerLog","school2 add >【"+school2+"】"); Log.e("daggerLog","showUtils add >【"+showUtils+"】"); }}
此时运行结果如下,可以看到showUtils对象成功注入到了MainActivity中:
8.png
再看另外一种情况:
当我们需要在一个@module里提供同一对象的不同实例的时候,该如何指定具体需要由哪个@Provides来提供呢?
先看下面一段代码:
@Singleton @Provides public School provideSchool(){ return new School("南京高级中学","江苏南京"); } @Singleton @Provides public School provideSchoolOther(){ return new School("国外学校","外国"); }
其余代码不修改,直接编译会报下面的错误:
9.png
不要慌,@Qualifier来帮你处理这个问题,首先使用@Qualifier自己设置一个注解:
@Qualifier@Documented@Retention(RetentionPolicy.RUNTIME)public @interface SchoolType { int type() default 0;}
而后在@provides中增加此注解:
@SchoolType @Singleton @Provides public School provideSchool(){ return new School("南京高级中学","江苏南京"); } @SchoolType(type = 1) @Singleton @Provides public School provideSchoolOther(){ return new School("国外学校","外国"); }
在MainActivity中@Inject处指定需要使用那种type的对象来进行注入:
@ForActivityContextpublic class MainActivity extends AppCompatActivity { @SchoolType @Inject School school; @SchoolType(type = 1) @Inject School school2; @Inject Student student; @Inject ShowUtils showUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerMainComponent.builder().mainModules(new MainModules(this)).build() .inject(this); Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】"); Log.e("daggerLog","school add >【"+school+"】"); Log.e("daggerLog","school2 add >【"+school2+"】"); Log.e("daggerLog","school2 name:【"+school2.getName()+"】 address:【"+school2.getAddress()+"】"); Log.e("daggerLog","showUtils add >【"+showUtils+"】"); }}
运行结果如下:
10.png
可以看到school和school2对象都成功的注入到了MainActivity中,因为我们指定了@SchoolType并且使用了不同的type,所以创立了两个不同的对象。
那么是不是我们需要限定的时候就需要自己设置注解呢,当然不是,dagger2为我们提供了一个注解@Named,我们来看下@Named的源码:
@Qualifier@Documented@Retention(RUNTIME)public @interface Named { /** The name. */ String value() default "";}
是不是瞬间豁然开朗,这不就是我们刚刚定义的@SchoolType一样嘛,只是名字不同罢了,当然把上面的@SchoolType直接替换成@Named也是一样的,不信大家可以试试!
说了这么多,对于dagger基本使用大家是不是有点头绪了,本篇我们主要讲解了基本注解@Inject,@Component,@Module,@Provides,@Qualifier等的使用,当然dagger2还是很多更高阶的用法,比方dependencies依赖,又比方subcomponent注解我们还没有讲到,这些内容准备放在下篇博文中进行讲解,敬请期待!
本文源码地址:
hanxiaofeng/StudyAndroid — > daggeruse目录
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 【android】摆正姿势,dagger2原来如此简单