文章目录
- Dagger 框架作用
- 基本使用方法
- 引入依赖
- 创建 Object
- 创建 Module
- 创建 Component
- 向 Activity 注入对象
- Component 内部单例
- 全局单例
- 自定义 Scope
- 关于单例作用域的理解
- 注入多种同类型对象
- Component 依赖
- Component 继承
- 传递 Activity
Dagger 框架作用
这里,我们直接跳过低级问题,假设大家已经知道 dagger 是什么
我们着重介绍,它的核心价值和一些高阶的用法
先上结论,再将它的设计理念和使用方式
Dagger 框架的意义在于
-
确定类之间的依赖关系和调用顺序
-
保证代码一定是按照这种依赖关系来编写的
-
使用注解注入的方式统一管理对象创建过程
-
对象创建方式修改时,只需修改 Module 和 Component 代码,不影响注入目标
确定依赖关系,才是其核心目的,其它只是关系明确后的附带效果
基本使用方法
假设我们现在有个 Activity,想要访问数据,有网络数据,也有本地数据
那么我们需要创建一个 HttpObject 和 DatabaseObject 来处理这两个工作
为了让 Activity 专注于业务代码,我们会专门建立一个 Repository 来统一管理各种 Object
这样的设计是完全没问题的,但是得靠代码编写者来保证这种依赖关系
现在我们来看看如何用 Dagger 去实现这个功能
在 Dagger 中,负责完成单个具体功能的类叫做 Module
完成整个业务往往需要多个 Module,统一管理多个 Module,向 Activity 中注入对象的类叫做 Component
下面我们来编写代码,看看实际使用方式
引入依赖
api "com.google.dagger:dagger:2.35.1"
api "com.google.dagger:dagger-android:2.35.1"
api "com.google.dagger:dagger-android-support:2.35.1"
annotationProcessor "com.google.dagger:dagger-compiler:2.35.1"
annotationProcessor "com.google.dagger:dagger-android-processor:2.35.1"
创建 Object
import javax.inject.Inject;
public class HttpObject {
@Inject
public HttpObject() {
}
public void post() {
}
}
这里的@Inject
表示自动创建时调用的构造函数
可以不使用此注解,但必须提供其它方式创建对象,比如Module::@Provides
创建 Module
import dagger.Module;
import dagger.Provides;
@Module
public class HttpModule {
@Provides
public HttpObject provideHttpObject() {
return new HttpObject();
}
}
简单对象管理也可以不提供 Module,此时会使用Object::@Inject
标记的构造函数来创建对象
创建 Component
import dagger.Component;
@Component(modules = {
HttpModule.class, DatabaseModule.class
})
public interface DataComponent {
void injectHomeActivity(HomeActivity homeActivity);
}
Component 也可以不指定 modules,此时Module::@Provides
不会生效,只会调用Object::@Inject
标记的构造函数来创建对象
向 Activity 注入对象
这里需要先 Rebuild,IDE 会帮我们自动生成 Component 实例
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.android.code.databinding.ActivityHomeBinding;
import javax.inject.Inject;
@SuppressWarnings("all")
public class HomeActivity extends AppCompatActivity {
ActivityHomeBinding binding;
@Inject
HttpObject httpObject;
@Inject
DatabaseObject databaseObject;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityHomeBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//编写测试代码
try {
test();
} catch (Throwable e) {
e.printStackTrace();
}
}
@Function
protected void test() throws Throwable {
DaggerDataComponent.builder().build().injectHomeActivity(this);
System.err.println(httpObject.hashCode());
System.err.println(databaseObject.hashCode());
}
}
对象必须通过 Component 注入,因此 Component 是必须的
并且 Component 中的 inject 的方法不允许使用基类,因为 Dagger 编译器要根据真实类名来生成注入代码
Component 内部单例
@Singleton
@Component(modules = {
HttpModule.class, DatabaseModule.class
})
public interface DataComponent {
void injectHomeActivity(HomeActivity homeActivity);
}
Component::@Singleton
可以允许同一个 Component 使用单例模式创建对象
注意,这是的单例是在同一个 Component 对象内部生效的,如果是两个 DataComponent 实例创建的对象,并不是单例的
另外,Component::@Singleton
并不保证所有对象就是单例的,只是提醒我们这个组件有使用到单例特性
具体对象是否是单例的,还要看 Component 是如何创建对象的
如果使用Module::@Provides
来创建对象,则对应方法上要标记@Singleton
如果使用Object::@Inject
来创建对象,则需要在对应的类上面标记@Singleton
如果都没有标记,仅仅是添加了Component::@Singleton
注解,那么最终的对象仍然不是单例的
@Module
public class HttpModule {
@Provides
@Singleton
public HttpObject provideHttpObject() {
return new HttpObject();
}
}
@Singleton
public class HttpObject {
@Inject
public HttpObject() {
}
}
全局单例
全局单例很容易实现
只要将 Component 实例放到 Application 中,整个应用共享同一个 Component 实例即可
自定义 Scope
现在我们已经知道,可以通过@Singleton
来实现全局单例
那么如果我们想在实现在某个类里面单例共享,不同类之间允许多例,要怎么做呢
我们可以通过 Dagger 的自定义 Scope 特性来实现这个目标,步骤如下
首先,自定义一个@LoginActivityScope
注解,表示被标记的类在LoginActivity
里面是单例共享的
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.inject.Scope;
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginActivityScope {
}
实际上,@Singleton
和@LoginActivityScope
一样,只是一个被@Scope
标记的自定义注解,用来标记单例作用域的
下一步,指定 Component 的作用域
import dagger.Component;
@LoginActivityScope
@Component(modules = {
LoginHttpModule.class, LoginDatabaseModule.class
})
public interface LoginDataComponent {
void injectHomeActivity(HomeActivity homeActivity);
}
和@Singleton
一样,我们需要在 Module 或 Object 上使用一样的作用域注解
如果我们还需要在其它 Activity,比如 HomeActivity 内实现单例共享
我们可以再创建@HomeActivityScope
HomeDataComponent
HomeHttpModule
HomeDatabaseModule
最后,在 LoginActivity 中使用 LoginDataComponent 注入对象,在 HomeActivity 中使用 HomeDataComponent 注入
关于单例作用域的理解
其实认真理解我们就会发现
-
Component 创建单例对象的条件是:
Component::Scope
和Module::Scope
或Object::Scope
名称一致 -
事实上,Dagger 其实只提供了 Component 内部单例这一个特性,其它单例效果都是我们自己间接实现的
-
根本不存在所谓的单例作用域,我们只是创建了多个 Component 和 Module,自己控制 Component 该在哪里使用而已
-
如果我们在 LoginActivity 和 HomeActivity 里面都使用 HomeComponent 来注入对象,它们肯定是系统的,作用域的谎言就会被打破
-
@Singleton
@LoginActivityScope
@HomeActivityScope
完全都是一样的东西,只是名称不一样 -
@Scope
并没有自动控制单例作用域的效果,只是给开发者提供了一个标记提醒功能
注入多种同类型对象
有时,同一个类可能有多种实例化方式,用于不同的使用场景
我们可以通过 Dagger 的@Named
特性,来区分同类型但不同定位的对象
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class HttpModule {
@Singleton
@Provides
@Named("Http-1.0")
public HttpObject provideHttpObject1() {
return new HttpObject();
}
@Provides
@Named("Http-2.0")
public HttpObject provideHttpObject2() {
return new HttpObject();
}
}
我们在 Module 中提供多种@Provides
方法,然后通过@Named
去区分他们
在 Activity 中,再通过系统的@Named
指定通过哪种方式创建注入对象即可
@SuppressWarnings("all")
public class HomeActivity extends AppCompatActivity {
@Inject
@Named("Http-1.0")
HttpObject httpObject1;
@Inject
@Named("Http-1.0")
HttpObject httpObject2;
@Inject
@Named("Http-2.0")
HttpObject httpObject3;
@Inject
@Named("Http-2.0")
HttpObject httpObject4;
}
打印对象的 HashCode 可以知道,Http-1.0
的对象是共享的,而Http-2.0
的则是两个不同的对象
Component 依赖
一个 Component 可以依赖另一个 Component,通过其它 Component 创建对象
比如 Fragment 和 Activity 之间的关系,Fragment 中很多数据是可以共享自 Activity,不需要自己去创建
@ActivityScope
@Component(modules = {
HttpModule.class
})
public interface ActivityComponent {
void injectHomeActivity(HomeActivity homeActivity);
//公开接口,表示HttpObject的注入方式可以被其它Component共享
HttpObject getHttpObject();
}
@FragmentScope
@Component(dependencies = {
ActivityComponent.class
})
public interface FragmentComponent {
void injectHomeFragment(HomeFragment homeFragment);
}
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.os.Bundle;
import com.android.code.databinding.ActivityHomeBinding;
import javax.inject.Inject;
@SuppressWarnings("all")
public class HomeActivity extends AppCompatActivity {
ActivityHomeBinding binding;
ActivityComponent activityComponent;
@Inject
HttpObject httpObject;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityHomeBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
test();
}
@Function
protected void test() {
activityComponent = DaggerActivityComponent.builder().build();
activityComponent.injectHomeActivity(this);
System.err.println(httpObject.hashCode());
//add fragment
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(new HomeFragment(), "Home");
fragmentTransaction.commitNow();
}
}
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import javax.inject.Inject;
public class HomeFragment extends Fragment {
FragmentComponent fragmentComponent;
@Inject
HttpObject httpObject;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
HomeActivity activity = (HomeActivity) getActivity();
fragmentComponent = DaggerFragmentComponent.builder().activityComponent(activity.activityComponent).build();
fragmentComponent.injectHomeFragment(this);
System.err.println(httpObject.hashCode());
}
}
通过打印结果可以知道,Fragment 中注入的,和 Activity 中的是同一个对象
组件依赖需要遵循以下关系
-
如果 ActivityComponent 指定了作用域,那么 FragmentComponent 必须也指定作用域,并且作用域不能相同
-
这一点很容易理解,因为对象间存在单例共享,一定是 Fragment 和 Activity 之间那种,有上下级作用域关系的
-
如果 ActivityComponent 没指定作用域,那么 FragmentComponent 作用域无限制
-
这一点也很容易理解,没有作用域限制,相当于每次都创建新对象,仅仅是代码复用的关系了
Component 继承
继承和依赖的区别在于
-
依赖只暴漏某个类的创建方法给外部共享
-
继承将全部功能共享给子组件
继承的使用方法
-
创建子组件,通过
@SubComponent
标注 -
创建子组件 Builder,通过
@SubComponent.Builder
标注 -
指定子组件作用域,不能和父组件相同
-
在父组件中提供创建
SubComponent.Builder
的接口 -
通过父组件获得子组件 Builder 来创建子组件
-
继承和依赖可以混合使用
下面我们来看下代码
@ApplicationScope
@Component(modules = {
HttpModule.class
})
public interface ApplicationComponent {
void injectApplication(APP app);
HomeActivityComponent.Builder activityComponentBuilder();
}
@ActivityScope
@Subcomponent(modules = DatabaseModule.class)
public interface HomeActivityComponent {
void injectHomeActivity(HomeActivity homeActivity);
HttpObject getHttpObject();
@Subcomponent.Builder
interface Builder {
HomeActivityComponent build();
HomeActivityComponent.Builder databaseModule(DatabaseModule databaseModule);
}
}
public class APP extends Application {
private static final ApplicationComponent applicationComponent = DaggerApplicationComponent.create();
public static ApplicationComponent getApplicationComponent(){
return applicationComponent;
}
}
@SuppressWarnings("all")
public class HomeActivity extends AppCompatActivity {
HomeActivityComponent activityComponent;
@Inject
HttpObject httpObject;
@Function
protected void test() {
activityComponent = APP.getApplicationComponent().activityComponentBuilder().build();
activityComponent.injectHomeActivity(this);
System.err.println(httpObject.hashCode());
}
}
传递 Activity
如果我们自动创建的对象,需要传入一个 Activity 实例,该怎么办呢
其实很简单,对象依赖是通过对应的 Module 来管理的,而 Module 是可用自己指定的
所以我们只要将 Activity 传入 Module,再通过Module::@Provides
来创建对象即可
public class DatabaseObject {
HomeActivity homeActivity;
public DatabaseObject(HomeActivity homeActivity) {
this.homeActivity = homeActivity;
}
public HomeActivity getHomeActivity(){
return homeActivity;
}
}
@Module
public class DatabaseModule {
HomeActivity homeActivity;
public DatabaseModule(HomeActivity homeActivity){
this.homeActivity = homeActivity;
}
@ActivityScope
@Provides
public DatabaseObject provideDatabaseObject() {
return new DatabaseObject(homeActivity);
}
}
HomeActivityComponent.Builder builder = APP.getApplicationComponent().activityComponentBuilder();
builder.databaseModule(new DatabaseModule(this));
activityComponent = builder.build();
activityComponent.injectHomeActivity(this);
System.err.println(httpObject.hashCode());
System.err.println(databaseObject.hashCode());
System.err.println(databaseObject.getHomeActivity() == this);