Android設計模式:單例模式
Chinese (Traditional) (中文(繁體)) translation by tianyiliang (you can also view the original English article)
什麼是單例模式?
單例模式(Singleton Pattern)是一種軟體設計模式,它可以保證一個類只有一個實例,並且該類提供了全域訪問。 每當多個類或用戶端請求該類時,都將獲得該類的相同實例。該Singleton類可以產生實體自己,也可以通過工廠類獲得物件。
我們來看一下手機及其擁有者的例子。手機通常由一個人擁有,而一個人可以擁有許多手機。這些手機中任何一個響起鈴聲,同一位擁有者就會接電話。
單例模式的好處
在一個典型的Android APP中,我們只需要物件的一個全域實例,無論是直接使用它還是簡單地將它傳遞給另一個類。這樣的例子很多,包括快取記憶體, OkHttpClient, HttpLoggingInterceptor, etrofit, Gson, SharedPreferences和倉庫類等。 如果我們把這些類產生實體了多個物件,我們就會遇到許多問題,比如異常的APP反應,資源過度使用和其他混亂的結果。
實現
實現這種模式很容易。以下代碼片段顯示了如何創建Singleton。
1 |
public class Singleton { |
2 |
|
3 |
private static Singleton INSTANCE = null; |
4 |
|
5 |
// other instance variables can be here
|
6 |
|
7 |
private Singleton() {}; |
8 |
|
9 |
public static Singleton getInstance() { |
10 |
if (INSTANCE == null) { |
11 |
INSTANCE = new Singleton(); |
12 |
}
|
13 |
return(INSTANCE); |
14 |
}
|
15 |
|
16 |
// other instance methods can follow
|
17 |
}
|
在上面的代碼中,我們有一個靜態變數INSTANCE來保存類的一個實例。我們還將構造函數設為私有,因為我們要強制不可修改 - 類只能產生實體。 getInstance()方法保證該類被產生實體,如果沒有被產生實體,就新建一個。
示例:創建Retrofit單例
Retrofit是通過將API轉換為Java介面來連接REST Web服務的流行庫。要瞭解更多資訊,請查看我在Envato Tuts +上的教程。
在Android APP中,你需要一個全域的Retrofit實例物件,以便APP的其他部分(如UserProfileActivity或SettingsActivity)可以使用它來執行網路請求,而無需在每次需要時創建一個實例。 創建多個實例會污染我們的APP,多個無用的retrofit物件會佔用移動設備上的不必要的記憶體,而記憶體本來就很緊張。
1 |
import retrofit2.Retrofit; |
2 |
import retrofit2.converter.gson.GsonConverterFactory; |
3 |
|
4 |
public class RetrofitClient { |
5 |
|
6 |
private static Retrofit retrofit = null; |
7 |
|
8 |
public static Retrofit getClient(String baseUrl) { |
9 |
if (retrofit==null) { |
10 |
retrofit = new Retrofit.Builder() |
11 |
.baseUrl(baseUrl) |
12 |
.addConverterFactory(GsonConverterFactory.create()) |
13 |
.build(); |
14 |
}
|
15 |
return retrofit; |
16 |
}
|
17 |
}
|
所以在用戶端A調用RetrofitClient.getClient()的時候,如果物件尚未創建實例,則創建該實例,然後當用戶端B調用此方法時,它會檢查“Retrofit”實例是否已存在。如果實例已存在,它將實例返回給用戶端B,而不是創建一個新的實例。
處理多執行緒
在Android系統中,你可以分離多個執行緒來執行不同的任務。這些執行緒可以同時執行相同的代碼塊。 在上述Singleton類的情況下,這可能導致創建多個實例物件,這違反了Singleton原則。 所以我們的Singleton代碼片段中的getInstance()方法不是執行緒安全的。我們現在來看看如何使執行緒安全。
同步getInstance()方法
使單例代碼執行緒安全的方法之一是使getInstance()方法成為同步方法。這樣做只允許一個執行緒一次運行該方法,強制每個其他執行緒處於等待或阻塞狀態。
1 |
public class Singleton { |
2 |
|
3 |
private static Singleton INSTANCE = null; |
4 |
|
5 |
// other instance variables can be here
|
6 |
|
7 |
private Singleton() {}; |
8 |
|
9 |
public static synchronized Singleton getInstance() { |
10 |
if (INSTANCE == null) { |
11 |
INSTANCE = new Singleton(); |
12 |
}
|
13 |
return(INSTANCE); |
14 |
}
|
15 |
|
16 |
// other instance methods can follow
|
17 |
}
|
這種方法使我們的代碼執行緒安全,但這是一個昂貴的操作。換句話說,這可能會降低性能。所以你必須檢查並看看你的APP性能成本是否值得這樣做。
熱切地創建一個實例
讓多執行緒安全的另一種方法是在載入或初始化類(由 Dalvik VM中的Android ClassLoader)時立即創建Singleton實例。 這使得代碼執行緒安全。然後在任何執行緒訪問INSTANCE變數之前,物件實例已經可用。
1 |
public class Singleton { |
2 |
|
3 |
private static Singleton INSTANCE = new Singleton(); |
4 |
|
5 |
// other instance variables can be here
|
6 |
|
7 |
private Singleton() {}; |
8 |
|
9 |
public static Singleton getInstance() { |
10 |
return(INSTANCE); |
11 |
}
|
12 |
|
13 |
// other instance methods can follow
|
14 |
}
|
這種方法的一個缺點是,你可能創建了一個永遠不會使用的物件,從而佔用不必要的記憶體。所以這個方法通常只有在你確定單例被訪問時才可以使用。
Bonus:使用Dagger2
依賴注入庫(如Dagger)可以幫助你連線物件依賴關係,並通過使用@Singleton注解來創建單例。這將確保物件僅在整個應用程式生命週期中初始化一次。
1 |
@Module
|
2 |
public class NetworkModule { |
3 |
|
4 |
@Provides
|
5 |
@Singleton
|
6 |
public Gson gson() { |
7 |
GsonBuilder gsonBuilder = new GsonBuilder(); |
8 |
return gsonBuilder.create(); |
9 |
}
|
10 |
|
11 |
@Provides
|
12 |
@Singleton
|
13 |
public HttpLoggingInterceptor loggingInterceptor() { |
14 |
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor( |
15 |
message -> Timber.i(message)); |
16 |
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); |
17 |
return interceptor; |
18 |
}
|
19 |
|
20 |
@Provides
|
21 |
@Singleton
|
22 |
public Cache cache(File cacheFile) { |
23 |
return new Cache(cacheFile, 10 * 1000 * 1000); //10MB Cache |
24 |
}
|
25 |
|
26 |
@Provides
|
27 |
@Singleton
|
28 |
public File cacheFile(@ApplicationContext Context context) { |
29 |
return new File(context.getCacheDir(), "okhttp_cache"); |
30 |
}
|
31 |
|
32 |
@Provides
|
33 |
@Singleton
|
34 |
public OkHttpClient okHttpClient(HttpLoggingInterceptor loggingInterceptor, Cache cache) { |
35 |
return new OkHttpClient.Builder() |
36 |
.addInterceptor(loggingInterceptor) |
37 |
.cache(cache) |
38 |
.build(); |
39 |
}
|
40 |
|
41 |
@Provides
|
42 |
@Singleton
|
43 |
public Retrofit retrofit(OkHttpClient okHttpClient, Gson gson) { |
44 |
return new Retrofit.Builder() |
45 |
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) |
46 |
.addConverterFactory(GsonConverterFactory.create(gson)) |
47 |
.client(okHttpClient) |
48 |
.baseUrl("you/base/url") |
49 |
.build(); |
50 |
}
|
51 |
}
|
在上面的代碼中,我們創建了Gson,Cache,File, OkHttpClient和Retrofit的單例。
想學習有關Dagger 2的更多資訊,請參閱我們在Envato Tuts +上的教程。
結語
在這篇簡短的教程中,你學到了Android中的單例模式,包括:它是什麼,使用它的好處,如何通過編寫自己的方法來實現它,以及處理多個執行緒的一些方法。我還向你展示了如何使用協力廠商庫,如Dagger 2。
與此同時,請查看關於Java語言和Android APP開發的其他教程!















