注入神器Dagger2-----對象單例模式分析(二)

在上一章節我們簡單介紹了Dagger2的使用和源碼中的流程。到這裏我們可能還會有疑問,如果某個類的對象在注入的時候創建多個對象,那麼幾個對象是一樣的?如果不一樣,這個類的對象被創建多次,就會產生多個不同的對象,浪費內存,有沒有單例的模式呢?我們帶着這些疑問進行探討。

一、局部單例和全局單例

以上一章中HttpObject來說,在MainActivity中創建兩個對象,mHttpObject1和mHttpObject2,我們通過打印對象的hashCode來判斷這兩個對象是不是一個,代碼如下:

public class MainActivity extends AppCompatActivity {
    @Inject
    HttpObject mHttpObject1;
    @Inject
    HttpObject mHttpObject2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMyComponent
                .builder()
                .httpModule(new HttpModule())
                .build()
                .injectMainActivity(this);
        Log.i("Dagger2","hashCode:"+mHttpObject1.hashCode());
        Log.i("Dagger2","hashCode:"+mHttpObject2.hashCode());
    }
}

輸出:

hashCode:22583939
hashCode:102753280

從上述結果中可以看到,這兩個對象是不一樣的。那有沒有類似於單例的方法呢?官方提供了@Singleton這個註解,我們來看一下它的使用,我們以上一章的HttpModule代碼爲例,需要在類上及創建HttpObject的方法上添加註解,代碼如下:

@Singleton
@Module
public class HttpModule {
    @Singleton
    @Provides
    public HttpObject providerHttpObject(){
        return new HttpObject();
    }
}

然後再在MyComponent的類上添加註解,代碼如下:
 

@Singleton
@Component(modules = {HttpModule.class})
public interface MyComponent {
    void injectMainActivity(MainActivity mainActivity);
}

註解添加完畢,我們再次輸出一下hashCode,如下:

hashCode:22583939
hashCode:22583939

很明顯,通過@Singleton註解配置之後,對象變成了一個。到這裏就結束了嗎?其實並沒有,上面的單例模式其實是局部單例?什麼是局部單例呢?我們在MainActivity中創建HttpObject的兩個對象是一樣的,此時我們在SecActivity中也通過Dagger2創建HttpObject對象,那麼MainActivity和SecActivity中的HttpObject對象就不是一個,這個就是局部單例技術。關於局部單例這種現象我們稍後從源碼中分析,我們先看一下解決辦法,我們創建一個Application類,這個類在每個項目中都會存在,我們在這個類中添加如下代碼:

public class MyApplication extends Application {
    private MyComponent mMyComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mMyComponent =
                DaggerMyComponent
                        .builder()
                        .httpModule(new HttpModule())
                        .build();
    }
    public MyComponent getMyComponent(){
        return mMyComponent;
    }
}

我們把MyComponent對象直接現在Application中,變成了全局對象,如果在MainActivity或者其他類中使用的時候,直接使用MyComponent注入方法即可,以MainActivity爲例:

((MyApplication)getApplication()).getMyComponent().injectMainActivity(this);

以上就是局部單例和全局單例的使用說明

二、局部單例源碼分析

首先我們想從.build()這個方法進入

    public MyComponent build() {
      if (httpModule == null) {
        this.httpModule = new HttpModule();
      }
      return new DaggerMyComponent(this);
    }

再進入到DaggerMyComponent的構造方法中,這裏是上一章源碼中我們所忽略的地方。構造方法中有一個initialize方法,我們點擊進入這個方法

@SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.providerHttpObjectProvider =
        DoubleCheck.provider(HttpModule_ProviderHttpObjectFactory.create(builder.httpModule));

    this.mainActivityMembersInjector =
        MainActivity_MembersInjector.create(providerHttpObjectProvider);
  }

其實非單例模式中initialize的代碼和這裏是不同的,主要是this.providerHttpObjectProvider對象創建的不同。在非單例模式中,沒有DoubleCheck.provider,而是直接使用的DoubleCheck.provider裏面的代碼,所以不同點在DoubleCheck.provider這裏,我們點擊去查看:

 
  public static <T> Provider<T> provider(Provider<T> delegate) {
    checkNotNull(delegate);
    if (delegate instanceof DoubleCheck) {
      return delegate;
    }
    return new DoubleCheck<T>(delegate);
  }

第一次進入的時候,delegate肯定不是DoubleCheck這個類,所以直接返回了new DoubleCheck<T>(delegate)這個對象,我們點進去查看DoubleCheck這個類,它的構造方法沒什麼特別的。我們發現這裏面有一個get的方法,這個方法我們在上一章提到過,是在injectMainActivity的過程中調用過,我們看一下DoubleCheck的源代碼,我這裏截取了部分代碼:

private static final Object UNINITIALIZED = new Object();
private volatile Provider<T> provider;
private volatile Object instance = UNINITIALIZED;
@Override
public T get() {
  Object result = instance;
  if (result == UNINITIALIZED) {
    synchronized (this) {
      result = instance;
      if (result == UNINITIALIZED) {
        instance = result = provider.get();
        provider = null;
      }
    }
  }
  return (T) result;
}

我們從代碼中發現,第一次調用get方法的時候,UNINITIALIZED是一個普通的對象,instance被UNINITIALIZED賦值,方法中的result被instance賦值,所以呢,UNINITIALIZED、instance、result這三個對象相同。在get方法中經過一個雙重判斷後,instance、result被provider.get獲得的對象賦值,provider這個對象其實是HttpModule_ProviderHttpObjectFactory這個類,關於這個類在哪,上一章我簡單介紹過,而get方法的實現如下:

@Override
  public HttpObject get() {
    return Preconditions.checkNotNull(
        module.providerHttpObject(), "Cannot return null from a non-@Nullable @Provides method");
  }

就是獲得HttpObject這個對象,也就是我們所需要的業務對象。如果第二次創建這個HttpObject這個類的對象,依然會走到DoubleCheck中的get方法,但是result之前被賦值了HttpObject對象,所以不再進入雙重判斷裏了,返回值是result,也就是第一次創建的HttpObject的對象,所以局部單例中的對象是相同的。那爲什麼又把MyComponent的對象做成全局變量呢?因爲每次重新.build方法的調用時,MyComponent都會被重新創建,也就是另一個對象,是不一樣的。上面所說的局部單例都是以MyComponent爲域進行的,新的MyComponent就會有一個新的域,所以我們要保證這個域相同,就能保證所創建的對象相同。

以上就是Dagger2單例模式,歡迎提問,歡迎糾錯!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章