个人觉得网上关于 dagger2 文章中关于 @Scope 和 @Subcomponent 解释的并不是很详细,也可能是我个人能力有限不能够理解,所以写下这篇文章,希望能够帮助后人更方便的入门。
-
@Scope是什么 -
@Scpoe的使用 -
@Subcomponent是什么 -
@Subcomponent的使用 -
@Subcomponent和@Component的实际使用场景定义
@Scope 是什么
scope 翻译过来就是辖域,再结合到计算机上,其实就是作用域的意思,学过高级语言的应该都知道设计模式中一个模式叫做单例模式,单例即为全局中该对象的实例只存在一个,而在 dagger2 中,@scope 的一个默认实现就是 @Singleton,乍一看,很神奇啊,仅仅使用一个注解就可以实现单例!
@Scpoe 怎么用
那么接下来我们就看一下它的使用。代码如下:
public class User {}
@Module
public class UserModule {
@Provides
@Singleton
User provideUser() {
return new User();
}
}
@Singleton
@Component(modules = UserModule.class)public interface UserComponent {
void inject(MainActivity activity);
void inject(SecondActivity activity);
}
我们创建一个普通的 User 类,然后创建它的 Module,并且用 @Singleton 标记该 User 返回对象,最后我们再创建它的 Component,然后用 @Singleton 标记这个 Component。这是一个标准的套路流程。接下来我们创建一个 MainActivity 和一个 SecondActivity,代码如下:
public class MainActivity extends AppCompatActivity {
@Inject
User mUser1;
@Inject
User mUser2;
private TextView mContentTextView;
private Button mContentButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContentTextView = (TextView) findViewById(R.id.tv_content);
mContentButton = (Button) findViewById(R.id.btn_content);
// [1]
UserComponent component = DaggerUserComponent.create();
component.inject(this);
// 第一行为 mUser1 的信息,第二行为 mUser2 的信息,第三行为该类中 UserComponent 的信息
mContentTextView.setText(mUser1.toString() + "\n" + mUser2.toString() + "\n" + component.toString());
mContentButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
});
}
}
public class SecondActivity extends AppCompatActivity {
@Inject
User mUser;
private TextView mContentTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
mContentTextView = (TextView) findViewById(R.id.tv_content);
// [2]
UserComponent component = DaggerUserComponent.create();
component.inject(this);
// 第一行为 mUser 的信息,第二行为该类中 UserComponent 的信息
mContentTextView.setText(mUser.toString() + "\n" + component.toString());
}
}
重点的代码部分我已经给标注出来了,接下来就是跑起来了,如图所示:

可以的,可以看到,单例实现成功了,我们仅仅通过一个 @Singleton 标记就使得对象实现了单例模式,接下来我们点一下按钮跳转到 SecondActivity 中,如图所示:

但是此时我们发现,不对啊你丫的,SecondActivity 的 User 对象的地址和 MainActivity 中的 User 对象地址并不一样啊,这个单例好像失效了啊!事实上并不是这样,那么为什么这个单例“失效”了呢?细心的小伙伴们已经看到了,两个 Activity 中的 Component 对象的地址是并不一样的,这样就好理解了 ——— 由于 Component 对象不是同一个,当然它们注入的对象也不会是同一个。那么我们如何解决这个问题呢?提供以下两个方案:
- 第一个,我们在
Application层初始化UserComponent,然后在 Activity 中直接获取这个UserComponent对象,由于Application在全局中只会初始化一次 -> 所以Application中的UserComponent对象只初始化一次 -> 我们每次在 Activity 中获取Application中的这个UserComponent当然就是同一个的啦,Application代码如下:
public class JokerApplication extends Application {
UserComponent mComponent;
public static JokerApplication getApplicationContext(Context context) {
return (JokerApplication) context.getApplicationContext();
}
@Override
public void onCreate() {
super.onCreate();
mComponent = DaggerUserComponent.create();
}
public UserComponent getComponent() {
return mComponent;
}
}
我们只需要将 [1] 和 [2] 处的代码更改成 UserComponent component = JokerApplication.getApplicationContext(this).getComponent();这样我们不就能将我们的 UserComponent “单例”化了吗?
效果如下图所示:


ok,问题解决!
- 第二个,我们将
UserComponent改成抽象类,然后使用单例模式,这样每次 Activity 中获取的 Component 不也是同一个么?代码如下:
@Singleton
@Component(modules = UserModule.class)
public abstract class UserComponent {
private static UserComponent mComponent;
public static UserComponent getInstance() {
if (mComponent == null) {
synchronized (UserComponent.class) {
if (mComponent == null) {
mComponent = DaggerUserComponent.create();
}
}
}
return mComponent;
}
public abstract void inject(MainActivity activity);
public abstract void inject(SecondActivity activity);
}
接下来我们只需要将 [1]和[2] 处的代码更改成 UserComponent component = UserComponent.getInstance();这样我们能将我们的 UserComponent 单例化了。截图我就不截出来了。
重点来了,dagger2 这不是跟我们开玩笑嘛,这标记了 @Singleton 和没标记没区别啊,单例模式还是要我们自己手动实现,这不是傻 x 么?(起码最开始我是这么想的),但是实际上 google 的大牛们不是傻 x 啊,其实 @Singleton 注解还是有几个作用的:首先一方面能让你直面的了解到这是一个单例,其次这个 @Singleton 能够更好的管理 Modlue 和 Component
之间的关系。 Dagger2 需要保证 Component 和 Module 是匹配的,就需要用到这个注解。再者,使用好这个注解,可以很方便地令我们理清楚层次,比如不同层级的 Component 需要有不同的 @Scope。
说完自带的 @Singleton 之外就是我们自定义 @Scope 注解了,格式如下:
@Scope
@Retention(RUNTIME)
public @interface PerActivity {}
然后就可以在需要使用的地方运用上这个注解了,使用过程中需要注意——两个拥有依赖关系的 Component 是不能有相同 @Scope 注解的!
@Subcomponent 是什么
实际场景中,我们可能需要一个基 Component 来提高代码的复用,或者需要借助其他的 Component 来扩展本 Componet 的功能需求,我们有以下两种方法可以实现这样的需求——
-
使用
@Component(dependencies = BaseComponent.class, modules = ...) -
使用
@Subcomponent(modules = ...)标记子 Component,同时在 BaseComponent 中提供一个返回子 Component 对象的方法。
那么它们的不同之处在哪里呢?@Component 只能获取到依赖的 Component 所暴露出来的对象,而 @Subcomponent 则可以获取到父类所有的对象。嗯!我们不妨根据一个现实情况举个栗子——一个 UserComponent 是要依赖一个背包 Component 来存活,因为背包 Module 提供衣服和水两个对象,代码如下——
public class Food {}
public class Cloth {}
@Module
public class PackModule {
@Provides
Cloth provideCloth() {
return new Cloth();
}
@Provides
Food provideFood() {
return new Food();
}
}
@Component(modules = PackModule.class)
public interface PackComponent {
Cloth getCloth();
}
请注意上面的 PackComponent 类,它仅仅暴露了 Cloth 对象获取的方法,而并没有提供 Food 对象获取的方法。接下来我们就在实践中修改 UserComponent 以解答 @Subcompont 和 @Component 的区别——
使用 @Component
就之前的 UserComponent 代码而言我们只需要在 @Component 中添加对 PackComponent 的依赖即可,代码如下:
@Singleton
@Component(dependencies = PackComponent.class, modules = UserModule.class)
public interface UserComponent {
void inject(MainActivity activity);
}
然后我们在 MainActivity 中进行依赖注入,代码如下:
public class MainActivity extends AppCompatActivity {
@Inject
Cloth mCloth; // [3]
private TextView mContentTextView;
private Button mContentButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContentTextView = (TextView) findViewById(R.id.tv_content);
PackComponent component = DaggerPackComponent.create();
DaggerUserComponent.builder().packComponent(component).build().inject(this);
mContentTextView.setText(mCloth.toString());// [4]
}
}
我们跑起来,截图如下:

没有任何问题,我们成功的将 Cloth 对象注入了进来。那要是我们想要注入 User 对象呢,直接修改 @Inject 后面的注入对象,改成 Food 对象不就行了么!我们将[3]处的代码改成Food mFood;,将[4]处的代码改成mContentTextView.setText(mFood.toString());

嘣!翻车了!没错,这就是@Component 只能获取到依赖的 Component 所暴露出来的对象的意思所在,UserComponent 所依赖的 PackComponent 所暴露出来的只有 Cloth 对象啊,并没有暴露 Food 对象,所以 UserComponent 是拿不到 Food 对象的。那么我要怎样才能拿到呢?很简单,有两种方法,一种是直接在
PackComponent 接口中添加一个方法 Food getFood(); 就好了,这样就暴露了 Food 对象的获取方法了,另一种就是使用 @Subcomponent 了——
@Subcomponent 怎么用?
对上述的类我们只需要更改 PackComponent 接口和 UserComponent 接口即可,由于 UserComponent 是子 Component,所以我们对 UserComponent 接口使用 @Subcomponent 注解,另外要在父 Component(PackComponent) 中提供子 Component(UserComponent)的获取方法,代码如下:
@Singleton
@Subcomponent(modules = UserModule.class)
public interface UserComponent {
void inject(MainActivity activity);
}
@Component(modules = PackModule.class)
public interface PackComponent {
// 提供 UserComponent 对象的获取方法
UserComponent userComponent(UserModule module);
}
我们需要用 @Subcomponent 注解子 Component,然后在父 Component 中暴露子类返回对象,这样就可以了,我们不妨跑一下,如图所示:

很好,获取到了 PackModule 提供的 Cloth 和 Food 对象,并没有翻车。那么它们的实际使用场景呢?``
@Subcomponent和@Component`` 的实际使用场景定义
此处引用 stackoverflow 上的一个回答
Component Dependencies
- Use this when: you want to keep two components independent. you want to explicitly show what dependencies from one component is used by the other. Subcomponents
- Use this when: you want to keep two component cohesive. you may not care to explicitly show what dependencies from one component is used by the other.
译:
Component 依赖
- 以下情况使用:
你想让两个 Component 都独立,没有任何关联. 你想很明确的告诉别人我这个 Component 所依赖的 Component.
Subcomponent 依赖 - 以下情况使用:
你想让两个 Component 内聚. 你应该并不关心这个 Component 依赖哪个 Component.