个人觉得网上关于 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.