快速入门Dagger2
1、Dagger2的介绍和简单使用:
A fast dependency injector for Android and Java.
一个快速的依赖注入库为java和android,
什么是依赖注入?有兴趣的同学可以看一下我之前转载的一篇依赖注入的文章。
https://github.com/google/dagger
这是Dagger2的github地址,里面介绍了在项目中如何使用和依赖。
1.1 在介绍入门之前,先简单的学习和认识一下基本的概念和名词:
@inject
该注解定义在需要依赖的地方使用,即:标有这个注解的地方是告诉Dagger这个类或者字段需要依赖注入,当调用相关的注入方法时候,Dagger就会创建一个该类的实例,将其依赖注入。
@module
Module类里面的方法是专门提供依赖,所以,一个类被@Module所注解,也就是告诉Dagger,构造实例从哪里去找需要的依赖(或者是创建),modules的一个重要的特点是它们设计为分区和组合在一起,即,假如我们的APP中需要多个module,这个时候,我们可以使用modules这个注解进行将其组合在一块。
@provide
在module中,我们定义的方法用这个注解,以此来告诉Dagger,我们想要创建这些对象,并提供。
@component
component类似于一个桥梁,也可以说一个注入器,即@Inject和@Module之间的桥梁,它的作用是连接这两部分。
这里只是先做一个概念的解释和阐述
2、如何快速使用 ?分为4步:
2.1 在Demo中,我们模拟一个用户管理类(增加用户功能)
public class UserManage {
public void addUser(){
Log.d("UserManage","Add a new User");
}
}
2.2 创建Module去提供相应的构建
@Module
public class UserModule {
@Provides
UserManage provideUserManager(){
return new UserManage();
}
}
2.3 创建Component实现桥梁
@Component(modules =UserModule.class )
public interface UserComponent {
void inject(MainActivity activity);
}
2.4 在Activity中进行依赖注入UserManage,这个类
public class MainActivity extends AppCompatActivity {
@Inject
UserManage userManage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerUserComponent.create().inject(this);
userManage.addUser();
}
}
我相信看上面的代码可能有一定的难度,下面我会详细的解释一下一步步是如何实现,为什么这么做?在详细介绍之前,我们先看看我们的Demo是不是可以运行起来,如果可以打印出addUser()中的日志,说明我们在MainActivity中的将UserManage依赖成功。
R(EK1X3JF9UQM6WD{LPVL%F.jpg
说明我们依赖成功,并且调用了UserMange的addUser()方法,切记我们在运行前,一定要进行rebuild project一下,使用apt插件工具帮我们自动生成代码。
3. Dagger2进阶使用:
到这里,我们算是已经是一个Dagger2的入门了,学会了简单的使用。但是如何将Dagger2运用到自己的实际项目中,还需要在实际项目中使用。在这里我们所有使用的类是没有进行参数的传递,这显然是不符合在实际项目中的业务因此,这里我们需要给UserManage传递一个User参数,代表实际要注入的对象。
@Module
public class UserModule {
@Provides
UserManage provideUserManager(User user){
return new UserManage(user);
}
}
那么我们如何进行依赖注入这个User对象,我们重新rebuild的话,控制台会报下面的错误,可以看出,是因为没有提供User对象,而造成的,那么我们该如何提供这个User对象的实例?
图片.png
有3种方案:(提供给它User对象不就完事了)
3.1、在UserModule中提供一个User对象 ,即:
@Module
public class UserModule {
@Provides
UserManage provideUserManager(User user){
return new UserManage(user);
}
@Provides
User provideUser(){
return new User("OneX_Zgj","22");
}
}
我们在UserManager中打印了相关则信息:
public class UserManage {
public UserManage(User user){
Log.d("TAG", "UserManage: "+user.getName() +" : " +user.getAge());
}
public void addUser(){
Log.d("Tag","Add a new User");
}
}
MainActivity和Component中的代码没有动:
运行结果:可以看到执行成功了,并且依赖注入了User对象
图片.png
3.2:单独创建一个UserCreateModule
单独创建一个UserCreateModule进行provide 一个特定的User对象,但是我们在UserModule中如何使用呢?
先看UserCreateModule中的实现
@Module
public class UserCreateModule {
@Provides
User provideUser(){
return new User("OneX_zgj","22");
}
}
@Module(includes = UserCreateModule.class)
public class UserModule {
@Provides
UserManage provideUserManager(User user){
return new UserManage(user);
}
// @Provides
// User provideUser(){
// return new User("OneX_Zgj","22");
// }
}
现在观察在UserModule中是如何进行实现的?细心的同学肯定会发现在类上面的注解放生了变化,对,就是因为这个注解,我们就可以引用和依赖UserCreateModule中提供的对象。
图片.png3.3:注解依赖到UserComponent中
我们将UserCreateModule加入到UserComponent中,也是可以运行的:
@Component(modules ={UserModule.class,UserCreateModule.class} )
public interface UserComponent {
void inject(MainActivity activity);
}
4. 创建和区分不同的对象实例
4.1在介绍和使用之前,先介绍一下这两个注解
@Qualifier:区分不同的对象实例
@Named :其实是@Qualifier的一种实现,我们也可以自己定义。
4.1.1 @Name 注解的使用:标记不同的对象实例
在实际项目中,我们有业务是进行创建出不同的实例对象,但是从我们的Demo
比如我们开发一般在测试库,而实际发布,需要改用正式版库,那么,普通的话,我们需要修改不同地址等等。
在这里我们简单模拟一下功能,我们使用NetUrl假设封装了地址,我们需要2中不同的url,去适配测试库和正式库,所以在代码中,我们需要根据某种状态或者标识,进行将它们区分出来:
NetUrl的简单实现:
public class NetUrl {
private String url;
public NetUrl(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
在看一下我们在Module中的实现和使用@Name注解区分不同的实例(正式库和测试库)
@Module
public class OkhttpApi {
@Provides
@Named("test")
NetUrl provideNetTestUrl(){
return new NetUrl("http://test");
}
@Provides
@Named("release")
NetUrl provideNetReleaseUrl(){
return new NetUrl("http://release");
}
}
在看一下OkhttpApiComponent中的实现:
@Component(modules = OkhttpApi.class)
public interface OkhttpApiComponent {
void inject(MainActivity activity);
}
接下来我们查看下MainActivity中的代码:需要注意的是,如果在Module中进行加入了@Name注解,我们在MainActivity中的@Inject旁边也要加入@Name注解,指明当前对象需要注入什么样的实例。
public class MainActivity extends AppCompatActivity {
// @Inject
// UserManage userManage;
@Inject
@Named("test")
NetUrl TestApi;
@Inject
@Named("release")
NetUrl ReleaseApi;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerOkhttpApiComponent.create().inject(this);
Log.d("Tag", TestApi.getUrl());
Log.d("Tag", ReleaseApi.getUrl());
}
}
image.png
4.1.2我们通过自定义Scope来实现和区分不同的实例
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Release {
}
1、定义了Release的scope,好奇的同学,可能会问,你是怎么知道这样定义Scope的,哈哈,答案是:我肯定抄的啦,就是模仿@Name注解进行写的。
2、定义Test的Scope
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Test {
}
在Module中替换相应的注解
@Module
public class OkhttpApi {
// @Provides
// @Named("test")
// NetUrl provideNetTestUrl(){
// return new NetUrl("http://test");
// }
//
//
// @Provides
// @Named("release")
// NetUrl provideNetReleaseUrl(){
// return new NetUrl("http://release");
// }
@Provides
@Test
NetUrl provideNetTestUrl(){
return new NetUrl("http://test");
}
@Provides
@Release
NetUrl provideNetReleaseUrl(){
return new NetUrl("http://release");
}
}
在MainActivity中进行替换为我们自定义的Scope
public class MainActivity extends AppCompatActivity {
// @Inject
// UserManage userManage;
// @Inject
// @Named("test")
// NetUrl TestApi;
//
// @Inject
// @Named("release")
// NetUrl ReleaseApi;
@Inject
@Test
NetUrl TestApi;
@Inject
@Release
NetUrl ReleaseApi;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerOkhttpApiComponent.create().inject(this);
Log.d("Tag", TestApi.getUrl());
Log.d("Tag", ReleaseApi.getUrl());
}
}
image.png
@Singleton的使用,在实际项目开发中,我们会对网络请求对象进行单例(比如okhttp实例),来节约内存,这个时候我们就需要进行使用Singleton,来表明该对象是要用单例创建的。
4.2 如何在实际项目中进行单例的操作:(比如确保在App中有only one 个okhttp的实例?)
下面的Demo将演示如何在Application中创建一个单例:
4.2.1 创建Module
@Module
public class AppMoudle {
private MyApp context;
public AppMoudle(MyApp context) {
this.context = context;
}
@Singleton
@Provides
public OkhttpUtils provideOkhttpUtils() {
OkhttpUtils okService = new OkhttpUtils(context);
Log.d("TAG", "provideApiService: " + okService);
return okService;
}
}
4.2.2 创建AppComponent
@Singleton
@Component (modules = AppMoudle.class)
public interface AppComponent {
/**
* 全局单例。所以不用Inject Activity
*
* @return 向下返回ApiService实例
*/
OkhttpUtils getApiService();
}
4.2.3在App中实例化出来AppComponent,确保是唯一的实例
public class MyApp extends Application {
private AppComponent mAppComponent;
@Override
public void onCreate() {
super.onCreate();
mAppComponent = DaggerAppComponent.builder().appMoudle(new AppMoudle(this)).build();
}
public AppComponent getAppComponent() {
return mAppComponent;
}
}
4.2.4 在子类的Component中使用:
UserComponet 中的实现:
@PreActivity
@Component(modules = UserModule.class ,dependencies = AppComponent.class)
public interface UserComponet {
void inject(MainActivity activity);
}
LoginComponent 中的实现:
@PreActivity
@Component(modules = UserModule.class,dependencies = AppComponent.class)
public interface LoginComponent {
void inject(LoginActivity activity);
}
这里我们为什么要用个@PreActivity,因为在AppComponent中使用了Singleton注解,没有scope的component不能依赖有scope的component,而且要不同于依赖的Component,所以这里我们需要自定义一个Scope。
PreActivity的实现
@Scope
@Documented
@Retention(RUNTIME)
public @interface PreActivity {
}
4.2.5在Activity中的使用:
public class MainActivity extends AppCompatActivity {
@Inject
OkhttpUtils okhttpUtils;
@Inject
OkhttpUtils okhttpUtils2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnStart = (Button) findViewById(R.id.btn_start);
DaggerUserComponet.builder().appComponent(((MyApp) getApplication()).getAppComponent()).build().inject(this);
okhttpUtils.logId();
okhttpUtils2.logId();
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, LoginActivity.class));
}
});
}
}
LoginActivity中的实现和MainActivity中的实现完全一样。
最后一步,运行一下程序,我们观察okhttpClient实例的内存地址:
image.png
这样我们实现了App中的单例操作。
5.在使用Dagger2中我们经常会范的一下错误:
1.component的inject方法接受父类型的参数,而调用的时候传入的子类型对象,对象则无法进行注入。
比如:我们在Component使用的是BaseActivity,而实际要注入的是MainActivity,虽然MainActivity extends BaseActivity,但是在Dagger2中,依旧无法进行注入。