Hement:關於項目中的Dagger2的使用(三)

  • 寫在前面的話,要講好這個Dagger2真的不是一件簡單的事情
  • Dagger 1 :匕首 一個用於Android和Java的快速依賴注入。由SQUAR公司開發
  • Dagger 2:由谷歌公司接手開發 Dagger 2

依賴注入的原理

  • 首先記住:new 建立一個對象是有毒的;
  • 首先什麼是依賴注入的原理:在軟件工程領域中,依賴注入是用於實現控制反轉的最多見的方式之一
  • 引用Martin Flower在解釋介紹注入時使用的一部分代碼來講明這個問題
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()]);
    }
    ...
}
複製代碼
  • MovieFinder
public interface MovieFinder {
    List findAll();
}
複製代碼
  • 建立類MovieFinder 來提供須要的電影列表,moviesDirectedBy方法提供導演的名稱來搜索電影,真正負責搜索電影的是MovieFinderImpl類,目前看起來狀況不錯,可是當咱們須要修改MovieFinderImpl的實現的方法,增長一個參數代表Movie的數據來源是那個數據庫,咱們不只僅須要修改MovieFinderImpl類中的實現的代碼,同時也要修改MovieLister類中建立的MovieFinderImpl對象。
  • 這就須要依賴注入要處理耦合,這種在MovieLister中建立MoviefinderImpl的方式,是的MovieLister不只僅依賴於MovieFinder這個接口,它還依賴於MovieListImpl這個實現,這種在一個類建立兩位一個類的對象的代碼,和硬編碼以及硬編碼數字同樣,是一種致使耦合的壞味道,能夠把這種壞味道稱爲硬初始化,咱們也該要像記住硬編碼同樣,new 建立一個對象是有毒的。
  • 以上帶來的壞處有兩點
  • 一、修改其實現的時候,也會修改建立地方的代碼
  • 二、以上建立代碼的方法不便於測試,也會致使代碼的可讀性變差(若是一段代碼不便於測試,那麼它必定不便於閱讀),固然MVP這種模式也是解決這種問題的!
  • 這個時候就須要依賴注入了
  • 依賴注入的三種方式
  • 一、構造函數的注入
public class MovieLister {
    private MovieFinder finder;
    public MovieLister(MovieFinder finder) {
        this.finder = finder;
    }
    ...
}
複製代碼
  • 二、set注入
public class MovieLister {
    s...
    public void setFinder(MovieFinder finder) {
        this.finder = finder;
    }
}
複製代碼
  • 三、接口注入
  • 建立一個注入的接口
public interface InjectFinder {
    void injectFinder(MovieFinder finder);
}
複製代碼
  • 以後實現這個接口
class MovieLister implements InjectFinder {
    ...
    public void injectFinder(MovieFinder finder) {
      this.finder = finder;
    }
    ...
}
複製代碼
  • 依賴注入下降了依賴和被依賴類型的耦合,在修改被依賴的類型實現時候,不須要修改依賴類型的實現,同時對於依賴類型的測試更加方便

Hement中Dagger2的使用

  • 引入依賴
//dagger2
    api "com.google.dagger:dagger:2.15"
    compileOnly 'org.glassfish:javax.annotation:10.0-b28'
複製代碼
  • 一、構建依賴:Dagger2中,這個負責提供依賴的組件被稱爲Module,@Module標識類型爲module,並用@Provides標識提供依賴的方法。
  • ActivityModule
@Module
public class ActivityModule {
    private Activity mActivity;

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

    @Provides
    Activity provideActivity() {
        return mActivity;
    }

    @Provides
    @ActivityContext
    Context providesContext() {
        return mActivity;
    }
}

複製代碼
  • ApplicationModule:提供遠程Server,說通俗就是接口IRemoteServer,經過Retrofit獲得,這個有個壞處,就是全部的接口請求信息都放在一個類了,後續應該根據項目的壯大,須要增長多個Server
@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() {
        return IRemoteServer.Creator.newHementService();
    }
}
複製代碼
  • 二、構建Injector
  • 有了提供依賴的組件,還須要把依賴注入到須要的對象中,鏈接提供依賴和消費依賴對象的組件被稱爲Injector. Dagger2中,咱們將其稱爲componentActivityComponent代碼以下:
@PerActivity
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent {

    /**
     * 注入activity
     * @param mainActivity
     */
    void inject(MainActivity mainActivity);

    /**
     * 每個類都得單獨的注入
     * @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);
}
複製代碼
  • 注入的類或者對象所有是null的問題?
  • 緣由是必須是真正消耗依賴的類型MainActivity,而不能夠寫成其父類,好比Activity。由於Dagger2在編譯時生成依賴注入的代碼,會到inject方法的參數類型中尋找能夠注入的對象,可是實際上這些對象存在於MainActivity,而不是Activity中。若是函數聲明參數爲ActivityDagger2會認爲沒有須要注入的對象。當真正在MainActivity中建立Component實例進行注入時,會直接執行按照Activity做爲參數生成的inject方法,致使全部注入都失敗。
  • 同時提供了ApplicationComponent 來初始化和應用生命週期同樣的類
@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

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

    IRemoteServer remoteServer();

    PreferencesHelper preferencesHelper();

    DatabaseHelper databaseHelper();

    DataManager dataManager();

    RxEventBus eventBus();
}

複製代碼
  • 三、關於Scope和Qualifier的使用
  • Scope 的用法,@Scope是元註解,是用來標註自定義註解的
@Scope //  Scope 的用法,@Scope是元註解,是用來標註自定義註解的
@Retention(RetentionPolicy.RUNTIME)
public  @interface ConfigPersistent {
}
複製代碼
  • 若是使用了ConfigPersistent註解的話,能夠複用以前的依賴實例,在Hement中我使用在了NetWorkPresenter類中
@ConfigPersistent
public class NetWorkPresenter  extends BasePresenter<NetWorkView> {
    .....
}
複製代碼
  • 同時爲了獲得Activity的複用,定義ConfigPersistentComponent:意思就是持久的Component,和App的生命週期同樣
@ConfigPersistent
@Component(dependencies = ApplicationComponent.class)
public interface ConfigPersistentComponent {

    ActivityComponent activityComponent(ActivityModule activityModule);
}
複製代碼
  • dependencies 依賴關係,一個 Component 依賴其餘 Compoent 公開的依賴實例,用 Component 中的dependencies聲明。
  • Component 和Subcomponent繼承關係,一個 Component 繼承(也能夠叫擴展)某 Component 提供更多的依賴,SubComponent 就是繼承關係的體現。
  • @Qualifier 限定符,ApplicationContext 標識是Application的Context對象
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationContext {
}
複製代碼
  • 在須要使用的地方,加上這個註解便可,好比是在DbOpenHelper中。
@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);
    }
}
複製代碼

Hement中BaseActivity、BaseFragment關於Dagger2的封裝

  • 在這裏講一種在BaseActivity中的封裝,Hement中定義了一個持久的ConfigPersistentComponent,在BaseActivity定義一個LongSparseArray 具體原理請看 經常使用集合的原理分析
    • LongSparseArray是android裏爲<Long,Object> 這樣的Hashmap而專門寫的類,目的是提升效率,其核心是折半查找函數(binarySearch)。 SparseArray僅僅提升內存效率,而不是提升執行效率 因此也決定它只適用於android系統(內存對android項目有多重要)SparseArray不須要開闢內存空間來額外存儲外部映射,從而節省內存。
private static final LongSparseArray<ConfigPersistentComponent> sComponentsMap = new LongSparseArray<>();
   @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //建立ActivityComponent,若是配置更改後調用緩存的ConfigPersistentComponent,則重用它。
        mActivityId = savedInstanceState != null ? savedInstanceState.getLong(KEY_ACTIVITY_ID) : NEXT_ID.getAndIncrement();

        ConfigPersistentComponent configPersistentComponent = sComponentsMap.get(mActivityId, null);
        if (null == configPersistentComponent) {
            Timber.tag(getClassName()).i("建立新的configPersistentComponent id=%d",mActivityId);
            configPersistentComponent = DaggerConfigPersistentComponent.builder()
                    .applicationComponent(HementApplication.get(this).getComponent())
                    .build();
            sComponentsMap.put(mActivityId, configPersistentComponent);
        }
        mActivityComponent = configPersistentComponent.activityComponent(new ActivityModule(this));

        //狀態欄的顏色
        QMUIStatusBarHelper.setStatusBarLightMode(this);
    }

複製代碼
  • 同時也定義了一個 AtomicLong對象:AtomicLong是做用是對長整形進行原子操做,線程安全.主要做用就是 NEXT_ID.getAndIncrement()獲取一個自增加的Id。
private static final AtomicLong NEXT_ID = new AtomicLong(0);
複製代碼
  • 在java1.8中新加入了一個新的原子類LongAdder,該類也能夠保證Long類型操做的原子性,相對於AtomicLong,LongAdder有着更高的性能和更好的表現,能夠徹底替代AtomicLong的來進行原子操做可是對 java的版本有要求,這裏就不使用 LongAdder了java

  • 原子遞增一個當前值。android

NEXT_ID.getAndIncrement()
複製代碼
  • 完整的BaseActivity中的代碼
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>
 *  抽象應用程序中的其餘活動必須實現的活動。它處理Dagger組件的建立,並確保ConfigPersistentComponent的實例跨配置更改存活。
 * </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是做用是對長整形進行原子操做。 線程安全
     */
    private static final AtomicLong NEXT_ID = new AtomicLong(0);
    /**
     * java1.8中新加入了一個新的原子類LongAdder,該類也能夠保證Long類型操做的原子性,
     * 相對於AtomicLong,LongAdder有着更高的性能和更好的表現,能夠徹底替代AtomicLong的來進行原子操做
     * 可是對 java的版本有要求,這裏就不使用 LongAdder了
     */
   // private static final LongAdder NEXT_ID = new LongAdder();

    /**
     * LongSparseArray是android裏爲<Long,Object> 這樣的Hashmap而專門寫的類,目的是提升效率,其核心是折半查找函數(binarySearch)。
     * SparseArray僅僅提升內存效率,而不是提升執行效率
     * ,因此也決定它只適用於android系統(內存對android項目有多重要)SparseArray不須要開闢內存空間來額外存儲外部映射,從而節省內存。
      */
    // https://www.jianshu.com/p/a5f638bafd3b   經常使用集合的原理分析 Dagger does not support 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);
        //建立ActivityComponent,若是配置更改後調用緩存的ConfigPersistentComponent,則重用它。
        mActivityId = savedInstanceState != null ? savedInstanceState.getLong(KEY_ACTIVITY_ID) : NEXT_ID.getAndIncrement();

        ConfigPersistentComponent configPersistentComponent = sComponentsMap.get(mActivityId, null);
        if (null == configPersistentComponent) {
            Timber.tag(getClassName()).i("建立新的configPersistentComponent id=%d",mActivityId);
            configPersistentComponent = DaggerConfigPersistentComponent.builder()
                    .applicationComponent(HementApplication.get(this).getComponent())
                    .build();
            sComponentsMap.put(mActivityId, configPersistentComponent);
        }
        mActivityComponent = configPersistentComponent.activityComponent(new ActivityModule(this));

        //狀態欄的顏色
        QMUIStatusBarHelper.setStatusBarLightMode(this);
    }
    protected String getClassName(){
        return this.getClass().getSimpleName();
    }
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putLong(KEY_ACTIVITY_ID, mActivityId);
    }

    /**
     * isChangingConfigurations()函數在是Api level 11(Android 3.0.x) 中引入的
     * 也就是用來檢測當前的Activity是否 由於Configuration的改變被銷燬了,而後又使用新的Configuration來建立該Activity。
     * 常見的案例就是 Android設備的屏幕方向發生變化,好比從橫屏變爲豎屏。
     */
    @Override
    protected void onDestroy() {
        //檢查此活動是否處於銷燬過程當中,以便用新配置從新建立。
        if (!isChangingConfigurations()) {
            Timber.tag(getClassName()).i("銷燬的configPersistentComponent id=%d",mActivityId);
            sComponentsMap.remove(mActivityId);
        }
        super.onDestroy();
    }
    public ActivityComponent activityComponent() {
        return mActivityComponent;
    }

    @Override
    protected int backViewInitOffset() {
        return QMUIDisplayHelper.dp2px(getContext(), 100);
    }

}


複製代碼
  • 未完待續 下一篇文章git

  • GitHub地址Hement:持續更新中github

  • 最後說明幾點數據庫

    • 若是一段代碼不便於測試,那麼它必定不便於閱讀),固然MVP這種模式也是解決這種問題的!
    • 依賴注入只是控制反轉的一種實現方式。控制反轉還有一種常見的實現方式稱爲依賴查找。
    • dependencies 依賴關係,一個 Component 依賴其餘 Compoent 公開的依賴實例,用 Component 中的dependencies聲明。
    • Component 和Subcomponent繼承關係,一個 Component 繼承(也能夠叫擴展)某 Component 提供更多的依賴,SubComponent 就是繼承關係的體現。
    • 在java1.8中新加入了一個新的原子類LongAdder,該類也能夠保證Long類型操做的原子性,相對於AtomicLong,LongAdder有着更高的性能和更好的表現,能夠徹底替代AtomicLong的來進行原子操做可是對 java的版本有要求,這裏就不使用 LongAdder了
  • 謝謝一下博客對個人幫助api

相關文章
相關標籤/搜索