上一章暇检,@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é)果:
可以看到烟勋,用不同方式獲取的兩個(gè) Account
實(shí)例完全相等。
4.4 總結(jié)
@Singleton
是用 @Scope
標(biāo)記的注解筐付,實(shí)現(xiàn)為線程安全的延遲初始化卵惦。
回顧前面的知識(shí)點(diǎn),我們知道:
- 默認(rèn)情況下瓦戚,使用
@Inject
注解標(biāo)記構(gòu)造函數(shù)沮尿,可以向 Dagger2 添加依賴類型 - 必須在
@Component
注解標(biāo)記的接口中,添加注入方法和目標(biāo)類參數(shù)较解,才能完成依賴注入 - 第三方庫畜疾,通過
@Provides
注解標(biāo)記的方法提供依賴實(shí)例 - 要實(shí)現(xiàn)依賴的單例模式赴邻,需要用
@Singleton
注解標(biāo)記在類或提供方法上
學(xué)會(huì)這四個(gè)注解,Dagger2 的精髓就已經(jīng)掌握啡捶,可以在實(shí)際開發(fā)中大展身手姥敛。