详解 Dagger2 的 @Scope 和 @Subcomponent

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

你可能感兴趣的:(Android)