• In the front, I want to talk about thisDagger2It’s really not a simple thing
  • Dagger 1A quick dependency injection tool for Android and Java. bySQUARCompanies to develop
  • Dagger 2: Developed by GoogleDagger 2

How dependency injection works

  • First remember: new creates an object that is toxic;
  • First, what is the principle of DEPENDENCY injection: in software engineering, dependency injection is one of the most common ways to implement inversion of control
  • referenceMartin FlowerThis is illustrated by explaining some of the code used to introduce injection
public class MovieLister {
    private MovieFinder finder;
    public MovieLister() {
        finder = new MovieFinderImpl();
    }
    public Movie[] moviesDirectedBy(String arg) {
        List allMovies = finder.findAll();
        for (Iterator it = allMovies.iterator(); it.hasNext();) {
            Movie movie = (Movie) it.next();
            if(! movie.getDirector().equals(arg)) it.remove(); }return(Movie[]) allMovies.toArray(new Movie[allMovies.size()]); }... }Copy the code
  • MovieFinder
public interface MovieFinder {
    List findAll();
}
Copy the code
  • You create the class MovieFinder to provide the list of movies you want, and the moviesDirectedBy method provides the name of the director to search for movies, and the thing that’s really responsible for searching for movies is the MovieFinderImpl class, so it looks good so far, But when we need to modify the method that implements this MovieFinderImpl, add a parameter that says where the data for this Movie comes from, we don’t just need to modify the code that implements this MovieFinderImpl class, You’re also going to modify the MovieFinderImpl object that you create in the MovieLister class.
  • So you need dependency injection to deal with coupling, the way you create this MoviefinderImpl in MovieLister, yeah MovieLister doesn’t just rely on this MovieFinder interface, it relies on this Implementation of MovieListImpl, This code of creating two objects of a class in a class, as well as hard coding and hard coding numbers, is a bad smell of coupling. We can call this bad smell hard initialization. We should also remember that, as with hard coding, new creating an object is toxic.
  • There are two disadvantages brought about by the above
  • 1. When you change the implementation, you also change the code where it is created
  • 2. The above method of creating code is not easy to test. It also makes the code less readable (if a piece of code is not easy to test, it is definitely not easy to read).
  • This is where dependency injection comes in
  • There are three ways of dependency injection
  • 1. Constructor injection
public class MovieLister { private MovieFinder finder; public MovieLister(MovieFinder finder) { this.finder = finder; }... }Copy the code
  • 2. Set injection
public class MovieLister {
    s...
    public void setFinder(MovieFinder finder) { this.finder = finder; }}Copy the code
  • 3. Interface injection
  • Create an injected interface
public interface InjectFinder {
    void injectFinder(MovieFinder finder);
}
Copy the code
  • Then implement the interface
class MovieLister implements InjectFinder { ... public void injectFinder(MovieFinder finder) { this.finder = finder; }... }Copy the code
  • Dependency injection reduces the coupling between the dependent type and the dependent type. When the implementation of the dependent type is modified, the implementation of the dependent type does not need to be modified. At the same time, it is more convenient to test the dependent type

Use of Dagger2 in Hement

  • Introduction of depend on
   //dagger2
    api Com. Google. "dagger: a dagger: 2.15"
    compileOnly 'org. Anyone: javax.mail. Annotation: 10.0 - b28'
Copy the code
  • 1. Build the dependency: In Dagger2, the component responsible for providing the dependency is called a Module, with @Module identifying the type as Module and @provides identifying the method that Provides the dependency.
  • ActivityModule
@Module
public class ActivityModule {
    private Activity mActivity;

    public ActivityModule(Activity activity) {
        mActivity = activity;
    }

    @Provides
    Activity provideActivity() {
        return mActivity;
    }

    @Provides
    @ActivityContext
    Context providesContext() {
        returnmActivity; }}Copy the code
  • ApplicationModule: Provides a remote Server, collodially known as an interfaceIRemoteServer, through Retrofit, this has a disadvantage, is that all the interface request information is put in one class, the future should be based on the expansion of the project, more than one need to be addedServer

@Module
public class ApplicationModule {

    protected final HementApplication mApplication;

    public ApplicationModule(HementApplication application) {
        mApplication = application;
    }

    @Provides
    Application provideApplication() {
        return mApplication;
    }

    @Provides
    @ApplicationContext
    Context provideContext() {
        return mApplication;
    }


    @Provides
    @Singleton
    IRemoteServer provideRibotsService() {
        returnIRemoteServer.Creator.newHementService(); }}Copy the code
  • 2. Build Injector
  • Once you have the component that provides the dependency, you also need to inject the dependency into the object that you want. The component that connects the providing and consuming dependent objects is called Injection.dagger2, we call itcomponent.ActivityComponentThe code is as follows:
@perActivity @SubComponent (Modules = ActivityModule.class) public Interface ActivityComponent {/** * Inject Activity * @param mainActivity */ void inject(MainActivity mainActivity); /** * Each class must be injected separately * @param baseActivity */ void inject(NetWorkActivity baseActivity); /** * Each class must be injected separately * @param baseActivity */ void inject(NetWorkActivity baseActivity); void inject(SPreferencesActivity sPreferencesActivity); void inject(DBNetWorkDemoActivity dbDemoActivity); void inject(RxEventBusActivity rxEventBusActivity); void inject(RxPermissionsActivity rxPermissionsActivity); void inject(ImageLoaderActivity imageLoaderActivity); }Copy the code
  • All injected classes or objects arenullThe problem?
  • The reason is that it has to be the type that really consumes the dependencyMainActivity, rather than its parent, for exampleActivity. becauseDagger2When dependency injection code is generated at compile time, it willinjectMethod for objects that can be injected, but actually exist inMainActivityRather thanActivityIn the. If the function is declared asActivity.Dagger2It is assumed that there are no objects to inject. Really isMainActivityCreated in theComponentWhen an instance is injected, the inject method generated according to Activity as the parameter is directly executed, resulting in all injection failures.
  • An ApplicationComponent is also provided to initialize the same class as the application lifecycle
@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    @ApplicationContext
    Context context();
    Application application();

    IRemoteServer remoteServer();

    PreferencesHelper preferencesHelper();

    DatabaseHelper databaseHelper();

    DataManager dataManager();

    RxEventBus eventBus();
}

Copy the code
  • 3. Use of Scope and Qualifier
  • The use of Scope, @scope is a meta-annotation, is used to annotate custom annotations
@retention (retentionPolicy-.runtime) public @interface ConfigPersistent {} @retention (Retentionpolicy-.runtime) public @interface ConfigPersistent {}Copy the code
  • If you useConfigPersistentThe annotation can reuse the previous dependency instance, which I used in the HementNetWorkPresenterIn the class
@ConfigPersistent
public class NetWorkPresenter  extends BasePresenter<NetWorkView> {
    .....
}
Copy the code
  • And in order to getActivityReuse, definition of ConfigPersistentComponent: which means persistent Component, and the App lifecycle
@ConfigPersistent
@Component(dependencies = ApplicationComponent.class)
public interface ConfigPersistentComponent {

    ActivityComponent activityComponent(ActivityModule activityModule);
}
Copy the code
  • Dependencies: A Component depends on another instance of its dependencies exposed by Compoent. Declare them in the word “Dependencies” in Component.
  • A Component inherits (or extends) a Component to provide more dependencies. Subcomponent is a representation of inheritance.
  • Qualifier. The ApplicationContext identifier is the Context object of the Application
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationContext {
}
Copy the code
  • Add this annotation where it is needed, such as inDbOpenHelperIn the.
@Singleton
public class DbOpenHelper extends SQLiteOpenHelper {

    public static final String DATABASE_NAME = "hement.db"; public static final int DATABASE_VERSION = 1; @Inject public DbOpenHelper(@ApplicationContext Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); }}Copy the code

In the HementBaseActivity, BaseFragmentaboutDagger2The encapsulation

  • Here’s one of themBaseActivityThe Hement defines a persistentConfigPersistentComponentIn theBaseActivityTo define aLongSparseArraySee how it works.Principle analysis of common sets
    • LongSparseArray is an Android class written specifically for

      hashMaps. The purpose is to improve efficiency. The core of the class is binarySearch. SparseArray only improves memory efficiency, not execution efficiency, so it was decided that it only works on Android systems (how important memory is to An Android project). SparseArray does not need to open up memory space to store additional external maps, thus saving memory.
      ,object>
private static final LongSparseArray<ConfigPersistentComponent> sComponentsMap = new LongSparseArray<>(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); / / create ActivityComponent, if after configuration changes call cache ConfigPersistentComponent, reuse it. mActivityId = savedInstanceState ! = null ? savedInstanceState.getLong(KEY_ACTIVITY_ID) : NEXT_ID.getAndIncrement(); ConfigPersistentComponent configPersistentComponent = sComponentsMap.get(mActivityId, null);if (null == configPersistentComponent) {
            Timber.tag(getClassName()).i("Create new configPersistentComponent id = % d",mActivityId); configPersistentComponent = DaggerConfigPersistentComponent.builder() .applicationComponent(HementApplication.get(this).getComponent()) .build(); sComponentsMap.put(mActivityId, configPersistentComponent); } mActivityComponent = configPersistentComponent.activityComponent(new ActivityModule(this)); / / the color of the status bar QMUIStatusBarHelper setStatusBarLightMode (this); }Copy the code
  • It also defines aAtomicLongObject: AtomicLong is used to perform atomic operations on long integers, thread safe. The main function isNEXT_ID.getAndIncrement()Get a self-growing Id.
  private static final AtomicLong NEXT_ID = new AtomicLong(0);
Copy the code
  • In java1.8, a new atomic class named LongAdder has been added. This class also guarantees atomicity of Long operations. LongAdder has higher performance and better performance than AtomicLong. It can completely replace AtomicLong to do atomic operations, but the Java version is required, so I won’t use LongAdder here

  • Atom increments by a current value.

 NEXT_ID.getAndIncrement()
Copy the code
  • The completeBaseActivityThe code in
package com.shiming.hement.ui.base; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.util.LongSparseArray; import android.support.v7.app.AppCompatActivity; import com.shiming.base.ui.QMUIActivity; import com.shiming.base.utils.QMUIDisplayHelper; import com.shiming.base.utils.QMUIStatusBarHelper; import com.shiming.hement.HementApplication; import com.shiming.hement.injection.component.ActivityComponent; import com.shiming.hement.injection.component.ConfigPersistentComponent; import com.shiming.hement.injection.component.DaggerConfigPersistentComponent; import com.shiming.hement.injection.module.ActivityModule; import java.util.concurrent.atomic.AtomicLong; import timber.log.Timber; import static com.shiming.base.BaseApplication.getContext; /** * <p> * Abstract the activity that other activities in the application must implement. It deals with Dagger components to create, and ensure the ConfigPersistentComponent across an instance of the configuration changes live. * </p> * * @author shiming * @version v1.0 * @since 2018/11/28 10:04 */ Public Class BaseActivity extends QMUIActivity {  private static final String KEY_ACTIVITY_ID ="KEY_ACTIVITY_ID"; /** * AtomicLong = AtomicLong; */ private static final AtomicLong NEXT_ID = new AtomicLong(0); /** * Java1.8 has added a new atomic class called LongAdder, which can also guarantee AtomicLong operations. * LongAdder has higher performance and better performance than AtomicLong. AtomicLong is a complete replacement for AtomicLong * but Java versions are required, */ // Private static final LongAdder NEXT_ID = new LongAdder(); /** * LongSparseArray is an Android class written specifically for <Long,Object> Hashmap. It is designed to improve efficiency. The core of the class is binarySearch. SparseArray only improves memory efficiency, not execution efficiency *, so it was also decided that it only works on Android systems (how important memory is to An Android project) SparseArray does not need to open up memory space to store additional external maps, thus saving memory. * / / / https://www.jianshu.com/p/a5f638bafd3b Dagger does not support the principle of common collection analysis injection into private fields private static final LongSparseArray<ConfigPersistentComponent> sComponentsMap = new LongSparseArray<>(); private long mActivityId; private ActivityComponent mActivityComponent; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); / / create ActivityComponent, if after configuration changes call cache ConfigPersistentComponent, reuse it. mActivityId = savedInstanceState ! = null ? savedInstanceState.getLong(KEY_ACTIVITY_ID) : NEXT_ID.getAndIncrement(); ConfigPersistentComponent configPersistentComponent = sComponentsMap.get(mActivityId, null);if (null == configPersistentComponent) {
            Timber.tag(getClassName()).i("Create new configPersistentComponent id = % d",mActivityId); configPersistentComponent = DaggerConfigPersistentComponent.builder() .applicationComponent(HementApplication.get(this).getComponent()) .build(); sComponentsMap.put(mActivityId, configPersistentComponent); } mActivityComponent = configPersistentComponent.activityComponent(new ActivityModule(this)); / / the color of the status bar QMUIStatusBarHelper setStatusBarLightMode (this); } protected StringgetClassName() {returnthis.getClass().getSimpleName(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putLong(KEY_ACTIVITY_ID, mActivityId); } /** * isChangingConfigurations() the function isChangingConfigurations() was introduced in Api Level 11 (Android 3.0.x). The Activity is then created using the new Configuration. * A common example is when the screen direction on an Android device changes, such as from landscape to portrait. */ @Override protected voidonDestroy() {// Check whether this activity is in the process of destruction so that it can be recreated with the new configuration.if(! isChangingConfigurations()) { Timber.tag(getClassName()).i("Destroy configPersistentComponent id = % d",mActivityId);
            sComponentsMap.remove(mActivityId);
        }
        super.onDestroy();
    }
    public ActivityComponent activityComponent() {
        return mActivityComponent;
    }

    @Override
    protected int backViewInitOffset() {
        returnQMUIDisplayHelper.dp2px(getContext(), 100); }}Copy the code
  • To be continued in the next article

  • GitHub address: Hement: Continuing to update

  • A few final notes

    • If a piece of code is not easy to test, it is certainly not easy to read), and of course the MVP pattern is designed to solve this problem too!
    • Dependency injection is only one implementation of INVERSION of control. Another common implementation of INVERSION of Control is called dependency lookup.
    • Dependencies: A Component depends on another instance of its dependencies exposed by Compoent. Declare them in the word “Dependencies” in Component.
    • A Component inherits (or extends) a Component to provide more dependencies. Subcomponent is a representation of inheritance.
    • In java1.8, a new atomic class named LongAdder has been added. This class also guarantees atomicity of Long operations. LongAdder has higher performance and better performance than AtomicLong. It can completely replace AtomicLong to do atomic operations, but the Java version is required, so I won’t use LongAdder here
  • Thank you for helping me with the blog

    • Dagger 2 Complete analysis (1), basic use and principle of Dagger 2
    • Dependency Injection Principles
    • Dagger Complete parsing (2), advanced using Lazy, Qualifier, Scope, etc