Dagger2 | 四、進(jìn)階 - @Singleton

上一章暇检,@Provides 注解實(shí)現(xiàn)第三方庫的依賴注入产阱,但每次獲取都是新的實(shí)例。有時(shí)候創(chuàng)建實(shí)例本身將消耗大量的系統(tǒng)資源占哟,這會(huì)導(dǎo)致性能問題心墅,影響用戶體驗(yàn),為此榨乎,我們需要使用單例模式怎燥。

查看 @Singleton 注解的源碼:

/**
 * Identifies a type that the injector only instantiates once. Not inherited.
 *
 * @see javax.inject.Scope @Scope
 */
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

@Singleton 注解由 @Scope 注解標(biāo)記,它們都是 JSR330 的注解蜜暑,用來確定實(shí)例的重用范圍铐姚。

4.1 @Singleton

標(biāo)記在 @Component 注解的類上:

@Singleton
@Component(modules = AccountModule.class)
public interface ActivityComponent {
// ...
}

標(biāo)記在 @Provides 注解的方法上:

@Module 
final class AccountModule {
    // ...

    @Singleton
    @Provides 
    PasswordEncoder providePasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

編譯生成:

public final class DaggerActivityComponent implements ActivityComponent {
  private final AccountModule accountModule;

  private Provider<PasswordEncoder> passwordEncoderProvider;

  private DaggerActivityComponent(AccountModule accountModuleParam) {
    this.accountModule = accountModuleParam;
    initialize(accountModuleParam);
  }

  // ...

  @SuppressWarnings("unchecked")
  private void initialize(final AccountModule accountModuleParam) {
    this.passwordEncoderProvider = DoubleCheck.provider(AccountModule_PasswordEncoderFactory.create(accountModuleParam));
  }

  // ...
}

其他內(nèi)容都一樣,多出來一個(gè)初始化方法,使用 DoubleCheck.provider() 方法包裝依賴隐绵。

4.1.1 雙重檢查模式

查看 DoubleCheck 類的源碼:

public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
  private static final Object UNINITIALIZED = new Object();

  private volatile Provider<T> provider;
  private volatile Object instance = UNINITIALIZED;

  private DoubleCheck(Provider<T> provider) {
    assert provider != null;
    this.provider = provider;
  }

  @SuppressWarnings("unchecked") // cast only happens when result comes from the provider
  @Override
  public T get() {
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          result = provider.get();
          instance = reentrantCheck(instance, result);
          /* Null out the reference to the provider. We are never going to need it again, so we
           * can make it eligible for GC. */
          provider = null;
        }
      }
    }
    return (T) result;
  }

  // ...
}

核心就在 get() 方法中之众,利用雙重檢查模式和 volatile 關(guān)鍵字,在多線程環(huán)境下依许,可以保證線程安全棺禾。

4.1.2 標(biāo)記賬號(hào)類

注釋 Account 類提供方法上的 @Provides 注解:

@Module
final class AccountModule {

//    @Provides
    Account provideAccount() {
        return new Account();
    }

    // ...
}

恢復(fù) Account 類構(gòu)造函數(shù)上的 @Inject 注解,并在類上標(biāo)記 @Singleton 注解:

@Singleton
public class Account {
    // ...

    @Inject
    public Account() {
        this.username = "fssd";
        this.password = "123456";
    }

    @NonNull
    @Override
    public String toString() {
        return "Account{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

查看編譯生成的組件類:

public final class DaggerActivityComponent implements ActivityComponent {
  // ...

  private DaggerActivityComponent(AccountModule accountModuleParam) {

    initialize(accountModuleParam);
  }

  // ...

  @SuppressWarnings("unchecked")
  private void initialize(final AccountModule accountModuleParam) {
    this.accountProvider = DoubleCheck.provider(Account_Factory.create());
    this.passwordEncoderProvider = DoubleCheck.provider(AccountModule_PasswordEncoderFactory.create(accountModuleParam));
  }

  // ...
}

看到 DoubleCheck.provider 方法就知道 Account 類也實(shí)現(xiàn)了單例峭跳,神奇的是膘婶,Account_Factory 類居然被調(diào)用了。

@Singleton 注解標(biāo)記在提供方法或依賴類上蛀醉,都可以讓組件生成單例實(shí)現(xiàn)悬襟。

現(xiàn)在刪除 Account 類中的 @Singleton 注解和 @Inject 注解,恢復(fù)模塊內(nèi)容:

@Module
final class AccountModule {

    @Singleton
    @Provides
    Account provideAccount() {
        return new Account();
    }

    @Singleton
    @Provides
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

4.2 驗(yàn)證單例

光看生成的源代碼還不夠拯刁,我們必須動(dòng)手驗(yàn)證一下脊岳。

為此,我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 記住賬號(hào) 功能垛玻,需要使用 Gson 庫來序列化 Account 類為 json 字符串割捅,再保存到 SharedPreferences 共享首選項(xiàng)。

4.2.1 聲明依賴

老規(guī)矩帚桩,上 Maven 搜一下 Gson棺牧,拿到在 Gradle 中聲明依賴的方式。

添加到 app 模塊的 build.gradle 文件:

dependencies {
  // other dependencies

  implementation 'com.google.code.gson:gson:2.8.7'
}

4.2.2 提供依賴

@Provides 注解標(biāo)記提供方法:

@Module
final class AccountModule {
    // ... 

    @Singleton
    @Provides
    Gson gson() {
        return new Gson();
    }
}

4.2.3 使用依賴

MainActivity 類中使用 Gson 實(shí)例:

public class MainActivity extends AppCompatActivity {

    @Inject
    Account account;
    @Inject
    PasswordEncoder passwordEncoder;
    @Inject
    Gson gson;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActivityComponent component = DaggerActivityComponent.create();
        component.inject(this);

        TextView contentText = findViewById(R.id.content_text);
        String content = String.format("username: %s, password: %s, encodePassword: %s",
                account.username,
                account.password,
                passwordEncoder.encode(account.password));
        contentText.setText(content);

        // 1. 序列化
        String json = gson.toJson(component.account());
        SharedPreferences preferences = getSharedPreferences("account", Context.MODE_PRIVATE);
        // 2. 持久化
        preferences.edit().putString("current", json).apply();

        // 3. 獲取字符串
        String current = preferences.getString("current", null);
        if (current != null) {
            // 4. 反序列化
            Account currentAccount = gson.fromJson(current, Account.class);
            Log.i("account", "account is equals: " + this.account.equals(currentAccount));
        }
    }
}

這里只是演示代碼朗儒,實(shí)際開發(fā)中不會(huì)這樣做。

現(xiàn)在需要驗(yàn)證一下参淹,從組件中拿到的 Account 實(shí)例與通過注入字段的 Account 實(shí)例是否相等醉锄?

4.2.4 改造賬號(hào)類

驗(yàn)證之前,我們做一點(diǎn)點(diǎn)額外的工作:

public class Account {

    private static final String NUMBER = "1234567890";

    public String username;
    public String password;

    public Account() {
        this.username = randomNumber(4);
        this.password = randomNumber(6);
    }

    private String randomNumber(int length) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(NUMBER.charAt((int) (Math.random() * NUMBER.length())));
        }
        return builder.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return Objects.equals(username, account.username) &&
                Objects.equals(password, account.password);
    }

    @Override
    public int hashCode() {
        return Objects.hash(username, password);
    }

    @NonNull
    @Override
    public String toString() {
        return "Account{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

提示:利用 IDEA 的 Alt + Insert 快捷鍵浙值,自動(dòng)生成 equals()hashCode() 方法恳不。

兩個(gè)對(duì)象的比較,必須重寫 equals() 方法开呐。

4.3 運(yùn)行

運(yùn)行結(jié)果:

對(duì)象比較日志

可以看到烟勋,用不同方式獲取的兩個(gè) Account 實(shí)例完全相等。

4.4 總結(jié)

@Singleton 是用 @Scope 標(biāo)記的注解筐付,實(shí)現(xiàn)為線程安全的延遲初始化卵惦。

回顧前面的知識(shí)點(diǎn),我們知道:

  1. 默認(rèn)情況下瓦戚,使用 @Inject 注解標(biāo)記構(gòu)造函數(shù)沮尿,可以向 Dagger2 添加依賴類型
  2. 必須在 @Component 注解標(biāo)記的接口中,添加注入方法和目標(biāo)類參數(shù)较解,才能完成依賴注入
  3. 第三方庫畜疾,通過 @Provides 注解標(biāo)記的方法提供依賴實(shí)例
  4. 要實(shí)現(xiàn)依賴的單例模式赴邻,需要用 @Singleton 注解標(biāo)記在類或提供方法上

學(xué)會(huì)這四個(gè)注解,Dagger2 的精髓就已經(jīng)掌握啡捶,可以在實(shí)際開發(fā)中大展身手姥敛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瞎暑,隨后出現(xiàn)的幾起案子彤敛,更是在濱河造成了極大的恐慌,老刑警劉巖金顿,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臊泌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡揍拆,警方通過查閱死者的電腦和手機(jī)渠概,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嫂拴,“玉大人播揪,你說我怎么就攤上這事⊥埠荩” “怎么了猪狈?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辩恼。 經(jīng)常有香客問我雇庙,道長,這世上最難降的妖魔是什么灶伊? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任疆前,我火速辦了婚禮,結(jié)果婚禮上聘萨,老公的妹妹穿的比我還像新娘竹椒。我一直安慰自己,他們只是感情好米辐,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布胸完。 她就那樣靜靜地躺著,像睡著了一般翘贮。 火紅的嫁衣襯著肌膚如雪赊窥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天狸页,我揣著相機(jī)與錄音誓琼,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛腹侣,可吹牛的內(nèi)容都是我干的叔收。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼傲隶,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼饺律!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起跺株,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤复濒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后乒省,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巧颈,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年袖扛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砸泛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛆封,死狀恐怖唇礁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惨篱,我是刑警寧澤盏筐,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站砸讳,受9級(jí)特大地震影響琢融,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜簿寂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一吏奸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧陶耍,春花似錦、人聲如沸她混。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坤按。三九已至毯欣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間臭脓,已是汗流浹背酗钞。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人砚作。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓窘奏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親葫录。 傳聞我的和親對(duì)象是個(gè)殘疾皇子着裹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容