Android base development library to make development easier. DevRing & Demo address: github.com/LJYcoder/De…

Study/Reference address: https://www.jianshu.com/p/cd2c1c9f68d4 https://blog.csdn.net/lisdye2/article/details/51942511 https://www.jianshu.com/p/24af4c102f62

preface

Dagger2 has been out for a long time, and there are many related tutorials on the Internet, but I still insist on writing this article. I should finish what I started and finish what I started (this is the last framework introduced in this series, and also the most difficult one to get started with in this series). But I will try to make it as clear as I can, and I invite you to point out any inadequacies.

What is a Dagger2

Dagger2 is a Dependency Injection framework.

What is dependency injection?

In other words, “the initialization of other classes on which the target class depends is not done by coding in the target class, but by other means automatically injecting the initialized instances into the target class.”

In another way, the initialization of class instances is managed in one place, and when specific instances are needed, they are taken from that place (injected into the target class).

What are the benefits of using Dagger2

Know what it is, and then know why.

1. The decoupling

Suppose you have A class A that is used in many parts of the project (instances of A are initialized in many places with new A()). Then, due to the change in requirements, the constructor of A takes an additional argument. All right, you need to change everything in new A(). But if you are using Dagger2 for management, you only need to make changes on the supplier side of the class instance.

2. Make feature implementation more focused on feature implementation

Suppose you now need to call the x() method of class A to implement something, but the construction process of class A is quite complicated (see getting XXXDao in GreenDao and Getting Observable in Retrofit for examples).

public void xxx(a){
    E e = new E();
    D d = new D(e);
    C c = new C();
    B b = new B(c,d);
    A a = new(b); a.x(); }Copy the code

As a result, five of the six lines of code are used to construct instance A and only one is used to call the x() method. But if you manage with Dagger2 and move the construction of instance A to the instance provider, the code for the functional implementation module looks like this

@Injcet
A a;
public void xxx(a){
    a.x();
}
Copy the code

This is all about making feature implementation more focused on feature implementation, without having to deal with the construction process of instance A.

3. Better manage class instances

We usually have two types of instances in development: global instances (singletons) that have the same life cycle as the app. One is page instances, whose life cycle is consistent with that of the page. With Dagger2, we can use a single component to manage global class instances (without having to write singletons and worry about the hungry and lazy); Then each page also has its own component to manage its page instance. This makes both the management of instances and the structure of projects clearer.

4. Pretend bility is high

(This can be skipped over…) When you don’t know Dagger2 but look at the project code that uses Dagger2, you will probably feel a little confused and arrogant: what are the @inject, @provides, @singleton, Lazy<>, Provider<>, etc? Why isn’t there instantiated code? Why is a null pointer not reported when it is null? Once you learn how to use Dagger2, you can also do a wave of “high bar” code.

Of course, it should also be mentioned that using Dagger2 increases the amount of code. So if it’s a small project/indie development, you might want to skip it because you might feel like you have more to lose than you have to gain. If it’s a big project/team development, the gains outweigh the losses.


Character is introduced

Before we talk about usage, let’s talk about a few important roles.

Instance demand side

A class that contains instance members needs to be initialized before they can be used, but the initialization process has been moved elsewhere. So the requirements for this class are instance objects that have already been initialized. For the moment, call such classes the “instance demand side.”

Instance provider

Where instance initialization takes place and the required instances are externally supplied.

In Dagger2, the provider can supply instances in two ways: using Module and annotating constructors with @Inject

bridge

The instance provider does not know to whom it wants to supply instances. This is where you need to bridge the instance demander side to the instance provider side.

In Dagger2, Component acts as a bridge.

Used to introduce

1. Preliminary use

1.1 Adding a Dependency

compile 'com. Google. Dagger: a dagger: 2.14.1'
annotationProcessor 'com. Google. Dagger: a dagger - compiler: 2.14.1'
Copy the code

1.2 Handling the instance demand side

On the instance demand side, annotate instance variables to be injected with @Inject.

public class UploadActivity extends AppCompatActivity{
    @Inject
    UploadPresenter mPresenter;
}
Copy the code

1.3 Handling the instance provider

As mentioned earlier, there are two ways to supply instances on the supply side.

Method 1: Use Module

  • Annotate the class with @Module to indicate that it is the provider
  • Use the @provides annotation method in a class to indicate that it is the method that Provides the instance. The instance is initialized and returned in this method.
@Module
public class UploadActivityModule {
    @Provides
    UploadPresenter uploadPresenter(a) {
        return newUploadPresenter(); }}Copy the code

Method 2 annotates the constructor with @inject

public class UploadPresenter{
    @Inject
    public UploadPresenter(a) {}}Copy the code

Pay attention to

Method 1 has a higher priority than method 2, meaning:

  • When an instance is supplied, method one is used to see if there is a method that returns the instance
  • If it does, it gets the instance and supplies it externally
  • If no, use Method 2 to check whether the @Inject annotated constructor exists
  • If it exists, the instance is constructed and supplied externally through the constructor
  • If not, an error is reported because the required instance cannot be supplied

1.4 Building Bridges

  • Annotate the interface with @Component to indicate that it is a bridge. (If you supply instances as 1.3.1, specify module in @Component(modules=xxx.class).) A Component can have no modules or multiple modules at the same time.
  • Add the void Inject (instance demander) method to indicate that instances from the instance provider will be handed over to the instance demander. With this method, you find out what instances on the instance demander side need to be injected (those annotated with @Inject), then get the required instances on the instance provider side, and finally Inject them into the instance demander side
  • Inject the method used for injection, its method name is not necessarily inject, can arbitrarily take inject, generally take inject. But the return type of this method must be void
@Component(modules = UploadActivityModule.class)
public interface UploadActivityComponent {
    void inject(UploadActivity uploadActivity);
}
Copy the code

1.5 Compilation and Injection

  • After completing the above steps, ReBuild the project to generate DaggerUploadActivityComponent class.
  • Inject is called in the instance demander to inject the instance
public class UploadActivity extends AppCompatActivity{
    @Inject
    UploadPresenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Inject the instance
        DaggerUploadActivityComponent.builder()
            .build()
            .inject(this);

        // The method in mPresenter can be called after injectionmPresenter.xxx(); }}Copy the code


2. Receive external parameters

In some cases, instance initialization needs to be done by receiving external parameters, such as Presenter in MVP, which often needs to be passed to the IView interface to perform data callbacks.

Now UploadPresenter’s constructor has changed and IUploadView needs to be passed in.

public class UploadPresenter{
    IUploadView mIView;
    public UploadPresenter(IUploadView iview) { mIView = iview; }}Copy the code

There are two ways to receive external parameters

2.1 Method one is passed in through the Module constructor

Modify the module on the instance provider

@Module
public class UploadActivityModule {
    IUploadView mIView;
    public UploadActivityModule(IUploadView iview) {
        mIView = iview;
    }

    @Provides
    IUploadView iUploadView(a){
         return mIView;
    }

    @Provides
    UploadPresenter uploadPresenter(IUploadView iview) {
        return newUploadPresenter(iview); }}Copy the code
  • The constructor was added to get the external parameter IUploadView
  • The uploadPresenter() method adds the IUploadView parameter
    • When the UploadPresenter instance is built, it looks for a method to return IUploadView in this Module or any other Module in the same Component
    • If so, IUploadView is obtained by this method to construct UploadPresenter
    • If not, find the @Inject annotation constructor by 1.3.2 (it doesn’t, because IUploadView is the interface)
  • UploadPresenter is not constructed directly with mIView to reduce coupling. The uploadPresenter() method simply gets the parameters needed to construct the Presenter, leaving it to the iUploadView() method to take care of where the parameters come from and how they will change.

Use the Module constructor to pass in external parameters

public class UploadActivity implements IUploadView{
    @Inject
    UploadPresenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Inject the instance
        DaggerUploadActivityComponent.builder()
            .uploadActivityModule(new UploadActivityModule(this))// Pass the external argument IUploadView through the constructor
            .build()
            .inject(this);

        // The method in mPresenter can be called after injection
        mPresenter.xxx();
    }

    // Implement the IUploadView interface method
    @Override
    public void onUploadSuccess(a) {
        // Upload succeeded
    }
    @Override
    public void onUploadFail(a) {
        // Upload failed}}Copy the code

2.2 Mode 2 Is passed in through Component

Modify the bridge Component

@Component(modules = UploadActivityModule.class)
public interface UploadActivityComponent {
    void inject(UploadActivity uploadActivity);

    IUploadView iUploadView(a);

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder iUploadView(IUploadView iUploadView);

        UploadActivityComponent build(a); }}Copy the code
  • Join the iUploadView() method to return iUploadView
  • Builder was added to receive IUploadView

Build Component with external parameters

public class UploadActivity extends AppCompatActivity implements IUploadView{
    @Inject
    UploadPresenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Inject the instance
        DaggerUploadActivityComponent.builder()
            .iUploadView(this)
            .build()
            .inject(this);

        // The method in mPresenter can be called after injection
        mPresenter.xxx();
    }

    // Implement the IUploadView interface method
    @Override
    public void onUploadSuccess(a) {
        // Upload succeeded
    }
    @Override
    public void onUploadFail(a) {
        // Upload failed}}Copy the code


3. The @qualifier annotation

The @qualifier is mainly used to solve the problem of ambiguity caused by multiple instances of the same type on the supplier side.

3.1 Use @named annotations

Dagger2 provides an @named annotation by default, which you can see from the code as an implementation of @Qualifier.

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
    String value(a) default "";
}
Copy the code

As an example, the page now needs two dialogs, one for login and one for registration.

@Module
public class TestActivityModule {
    private Context mContext;

    public TestActivityModule(Context context){
          mContext = context;
    }

    @Provides
    Context context(a){
          return mContext;
    }
    
    @Named("login")
    @Provides
    Dialog loginDialog(Context context){
         Dialog dialog = new Dialog(context);
         dialog.setTitle("Login Prompt"); .return dialog;
    }

    @Named("register")
    @Provides
    Dialog registerDialog(Context context){
         Dialog dialog = new Dialog(context);
         dialog.setTitle("Registration Tips"); .returndialog; }}Copy the code
  • As you can see, on the instance provider side, you need to annotate @named above the method that provides the Dialog instance. An error is reported if not added, because Dagger2 does not know which method to use to get the Dialog instance.
@Component(modules = TestActivityModule.class)
public interface TestActivityComponent {
    void inject(TestActivity testActivity);
}
Copy the code
public class TestActivity extends AppCompatActivity{
    @Named("login")
    @Inject
    Dialog mDialogLogin;
    
    @Named("register")
    @Inject
    Dialog mDialogRegister;      

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Inject the instance
        DaggerTestActivityComponent.builder()
            .testActivityModule(new TestActivityModule(this))
            .build()
            .inject(this); }}Copy the code
  • On the instance demand side, the @named annotation is also used to annotate the instance to be injected. Let Dagger2 know that mDialogLogin annotated by @named (“login”) needs to be obtained via the provisioning method of the @named (“login”) annotated. The mDialogRegister annotated by @named (“register”) can be obtained by the supply method annotated by @named (“register”).

3.2 Customizing @Qualifier Annotations

With the @named annotation, you need to add a string to distinguish it, which is cumbersome and error-prone. So we can use custom qualifier annotations.

@Qualifier
public @interface DialogLogin {
}
Copy the code
@Qualifier
public @interface DialogRegister {
}
Copy the code

Change the @named (“login”) to @dialoglogin, and the @named (“register”) to @dialogregister


4. Scope annotation @scope

The @scope function is to keep instances supplied in the same Component unique.

4.1 Implement local singletons using scoped annotations

For example:

public class UploadActivity extends AppCompatActivity{
    @Inject
    UploadPresenter mPresenter1;
    @Inject
    UploadPresenter mPresenter2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Inject the instance
        DaggerUploadActivityComponent.builder()
            .build()
            .inject(this); }}Copy the code
  • If the scope annotations are not used, mPresenter1 and mPresenter2 in the code will be two different instances that can be viewed by printing the memory address.
  • With the following scoped annotation, the two will be the same instance, which can be viewed by printing the memory address.

Step 1 Customize the @scope annotation

@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScope {}
Copy the code

Step 2 Add scope annotations to the bridge Component

@ActivityScope 
@Component(modules = UploadActivityModule.class)
public interface UploadActivityComponent {
    void inject(UploadActivity uploadActivity);
}
Copy the code

Step 3 Add scope annotations to the methods that provide instances on the supplier side

@Module
public class UploadActivityModule {
    @ActivityScope
    @Provides
    UploadPresenter uploadPresenter() {
        returnnew UploadPresenter(); }}Copy the code

If you are providing instances as 1.3.2, add scope annotations above the class

@ActivityScope
public class UploadPresenter{
    @Inject
    public UploadPresenter(a) {}}Copy the code

After being handled by @Scope, the UploadPresenter instance in UploadActivity will remain unique.

4.2 Implement global singletons using scoped annotations

Global singleton, I believe you are very familiar with, is usually written with hungry lazy man and so on the singleton pattern. As mentioned earlier, @scope is used to keep instances supplied in the same Component unique. That is, if I create a new Component in another Activity, its provided UploadPresenter instance will also be new. This is not what we think of as a global singleton. So, to implement a global singleton, make sure that the Component that gets the instance is always the same. How do you do that? The answer is to create a Component to provide an instance of the global singleton, initialize the Component in the Application, and obtain the global singleton from it.

Global instance provider

@Module
public class AppModule {
    private Application mApplication;

    public AppModule (Application application){
          mApplication = application;
    }

    @Singleton
    @Provides
    Application application(a){
          return mApplication;
    }

    @Singleton
    @Provides
    ActivityStackManager activityStackManager(a) {
        return newActivityStackManager(); }}Copy the code

Global bridge

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    ActivityStackManager activityStackManager(a);
    Application application(a);
}
Copy the code

Application class

public class MyApplication extends Application {

    public static AppComponent mAppComponent;

    @Override
    public void onCreate(a) {
        super.onCreate();
       
        mAppComponent= DaggerAppComponent.builder()
              .appModule(new AppModule(this)) .build(); }}Copy the code

Fetching an instance each time through the AppComponent of the Application guarantees a global singleton

public class UploadActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
   
        MyApplication.mAppComponent.activityStackManager().pushOneActivity(this);     
    }

    @Override
    protected void onDestroy(a) {
        super.onDestroy();

        MyApplication.mAppComponent.activityStackManager().popOneActivity(this); }}Copy the code
  • Using the @Singleton scoped annotation provided by default with Dagger2, you can see from the source code that its implementation is actually the same as the @ActivityScope previously.
  • So what really implements global singletons is not @Singleton, but using the same Component for every instance fetched.
  • But because it literally means singleton, we usually apply it to the global bridge and instance provider.
  • Instead of adding the Inject method to the global Component automatically (which you can do, but rarely do globally), the Global Component adds the activityStackManager() method for external calls to fetch instances.


5. The Lazy and the Provider

If you don’t want to initialize the @Inject annotated instance after the Inject () method is called, but instead want to initialize the instance when it is used, we can use Lazy and Provider.

Example (omit code for instance provider and bridge)

public class UploadActivity extends AppCompatActivity{
    @Inject
    Lazy<UploadPresenter> mPresenter1;
    @Inject
    Provider<UploadPresenter> mPresenter2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Inject the instance
        DaggerUploadActivityComponent.builder()
            .build()
            .inject(this);
    }

    public void xxx(a){
         Initializing Presenter and getting the instance starts after calling Lazy's get() method
         // And each subsequent call to get() will yield the same instance.
         mPresenter1.get().xxx();

         // Initializes Presenter and gets the instance after calling the Provider's get() method
         // And each subsequent call to get() will re-call the provider method to get a new instance.mPresenter2.get().xxx(); }}Copy the code

Note: If you use the previously described @scope annotation to control instance uniqueness, you will get the same instance even if you call the Provider’s get() method multiple times.


6. Dependency and inclusion

If the initialization of an instance on the supply side requires a global Context(Application), we can get it from the global AppComponent via inter-bridge dependencies or inclusions.

@Module
public class TestActivityModule {
    // The Application parameter is required
    @Provides
    DbHelper dbHelper(Application application){
          return newDbHelper(application); }}Copy the code

6.1 Implementation via Dependencies (TestActivityComponent depends on AppComponent)

Specify the Component to depend on with dependencies = xxx.classs:

@Component(modules = TestActivityModule.class, dependencies = AppComponent.class)
public interface TestActivityComponent {
   void inject(TestActivity testActivity);
}
Copy the code

The dependent Component defines how to get related instances:

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {

    Application application(a); . }Copy the code

Initialize TestActivityComponent by passing in the dependent Component

public class TestActivity extends AppCompatActivity{

    @Inject
    DbHelper mDbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        DaggerTestActivityComponent.builder()
            .appComponent(MyApplication.mAppComponent) // Pass in the dependent Component
            .build()
            .inject(this); }}Copy the code

6.2 Implement by Inclusion (AppComponent contains TestActivityComponent)

TestActivityComponent uses the @SubComponent annotation instead of the @Component

@Subcomponent(modules = TestActivityModule.class)
public interface TestActivityComponent {
    void inject(TestActivity testActivity);
}
Copy the code

AppComponent defines methods to contain and get subComponents

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {

    TestActivityComponent addSub(TestActivityModule testActivityModule); . }Copy the code

Get the SubComponent from AppComponent and inject it

public class TestActivity extends AppCompatActivity{

    @Inject
    DbHelper mDbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TestActivityComponent testActivityComponent = MyApplication.mAppComponent.addSub(new TestActivityModule());
        testActivityComponent .inject(this); }}Copy the code

6.3 Dependency and inclusion use summary

  • Instances provided by providers connected to other Bridges can be obtained through dependencies or inclusions between bridge components.
  • In the case of dependency implementation (assuming that A depends on B), A needs to be specified in dependencies B. In B, the method of obtaining related instances required by A needs to be defined.
  • With the include implementation (assuming B contains A), A needs to use the @subComponent annotation, and B needs to define methods to contain/retrieve A, which is obtained by calling B’s methods.
  • Two components that have dependencies must have different Scope annotations @scope to cause ambiguity.


7. Some Tips

7.1 Two instance provisioning methods were introduced in 1.3.1 and 1.3.2: using Module (Mode 1) and annotating constructors (Mode 2) with @inject.

So when should you use which? Suppose the supplier now needs to provide an instance of class A

  • When @inject cannot be added to the constructor of class A (such as some classes in third-party libraries), provide an instance of CLASS A using Method 1.
  • When you want to initialize an instance of class A, the variables annotated by @inject in class A are also injected automatically, use Method 2 to provide instance OF class A.

The 7.2 Module class can be declared abstract, but the related provisioning methods must be declared static.

7.3 If a provision method in a Module declares @Scope, the component to which it belongs must also declare the same @Scope. But if component declares @Scope, its Module’s provisioning methods don’t always declare @Scope.

7.4 The instance variable annotated by @Inject cannot be declared private or static; otherwise, an error will be reported.