- 寫在前面的話,要講好這個
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 建立一個物件是有毒的。
- 以上帶來的壞處有兩點
- 1、修改其實現的時候,也會修改建立地方的程式碼
- 2、以上建立程式碼的方法不便於測試,也會導致程式碼的可讀性變差(如果一段程式碼不便於測試,那麼它一定不便於閱讀),當然MVP這種模式也是解決這種問題的!
- 這個時候就需要依賴注入了
- 依賴注入的三種方式
- 1、建構函式的注入
public class MovieLister {
private MovieFinder finder;
public MovieLister(MovieFinder finder) {
this.finder = finder;
}
...
}
複製程式碼
- 2、set注入
public class MovieLister {
s...
public void setFinder(MovieFinder finder) {
this.finder = finder;
}
}
複製程式碼
- 3、介面注入
- 建立一個注入的介面
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`
複製程式碼
- 1、構建依賴: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();
}
}
複製程式碼
- 2、構建Injector
- 有了提供依賴的元件,還需要把依賴注入到需要的物件中,連線提供依賴和消費依賴物件的元件被稱為Injector. Dagger2中,我們將其稱為
component
。ActivityComponent
程式碼如下:
@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
中。如果函式宣告引數為Activity
,Dagger2
會認為沒有需要注入的物件。當真正在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();
}
複製程式碼
- 3、關於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不需要開闢記憶體空間來額外儲存外部對映,從而節省記憶體。
- LongSparseArray是android裡為<Long,Object> 這樣的Hashmap而專門寫的類,目的是提高效率,其核心是折半查詢函式(binarySearch)。 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了
-
原子遞增一個當前值。
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);
}
}
複製程式碼
-
未完待續 下一篇文章
-
GitHub地址:Hement:持續更新中
-
最後說明幾點
- 如果一段程式碼不便於測試,那麼它一定不便於閱讀),當然MVP這種模式也是解決這種問題的!
- 依賴注入只是控制反轉的一種實現方式。控制反轉還有一種常見的實現方式稱為依賴查詢。
- dependencies 依賴關係,一個 Component 依賴其他 Compoent 公開的依賴例項,用 Component 中的dependencies宣告。
- Component 和Subcomponent繼承關係,一個 Component 繼承(也可以叫擴充套件)某 Component 提供更多的依賴,SubComponent 就是繼承關係的體現。
- 在java1.8中新加入了一個新的原子類LongAdder,該類也可以保證Long型別操作的原子性,相對於AtomicLong,LongAdder有著更高的效能和更好的表現,可以完全替代AtomicLong的來進行原子操作但是對 java的版本有要求,這裡就不使用 LongAdder了
-
謝謝一下部落格對我的幫助