什么是单例模式?
单例模式是一种软件设计模式,它保证一个类只有一个实例,并且该类提供对它的全局访问点。每当多个类或客户端请求该类时,它们都会获得该类的同一实例。此 Singleton 类可能负责实例化自身,或者您可以将对象创建委托给工厂类。
单例模式的优点
在典型的Android应用程序中,有许多对象我们只需要一个全局实例,无论您是直接使用它还是简单地将其传递给另一个类。示例包括缓存、、、、、、存储库类等。如果我们要实例化多种类型的对象,我们会遇到诸如不正确的应用程序行为,资源过度使用和其他令人困惑的结果之类的问题。OkHttpClientHttpLoggingInterceptorRetrofitGsonSharedPreferences
实现
实现此模式非常容易。以下代码段演示如何创建单例。
public class Singleton {
private static Singleton INSTANCE = null;
// other instance variables can be here
private Singleton() {};
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return(INSTANCE);
}
// other instance methods can follow
}
在上面的代码中,我们有一个静态变量来保存类的实例。我们还将构造函数设为私有,因为我们想要强制实施非可实例化性 — 类只能实例化自身。该方法保证该类已实例化(如果尚未实例化),并且将其返回给调用方。INSTANCEgetInstance()
创建改造的单个实例
在 Android 应用中,你需要一个 Retrofit 对象的全局实例,以便应用的其他部分可以使用它来执行网络请求,而无需在每次需要时都创建实例。创建多个实例会用未使用的改造对象污染我们的应用程序,从而在已经内存受限的移动设备上占用不必要的内存。UserProfileActivitySettingsActivity
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String baseUrl) {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
因此,每当客户端 A 调用 时,如果尚未创建实例,它就会创建该实例,然后在客户端 B 调用此方法时,它会检查 Retrofit 实例是否已存在。如果是这样,它会将实例返回到客户端 B,而不是创建一个新实例。RetrofitClient.getClient()
处理多线程
在Android系统中,您可以分拆多个线程来执行不同的任务。这些线程最终可以同时执行相同的代码块。对于上面的类,这可能会导致创建多个对象实例,这违反了 Singleton 的协定。因此,我们的单例代码片段方法不是线程安全的。现在,我们将研究如何使其线程安全。SingletongetInstance()
同步方法getInstance()
使单例代码线程安全的方法之一是使该方法成为同步方法。这样做只允许一个线程一次运行该方法,从而强制其他每个线程都处于等待或阻塞状态。getInstance()
public class Singleton {
private static Singleton INSTANCE = null;
// other instance variables can be here
private Singleton() {};
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return(INSTANCE);
}
// other instance methods can follow
}
这种方法使我们的代码线程安全,但这是一项代价高昂的操作。换句话说,这可能会降低性能。因此,您必须调查并查看性能成本在您的应用程序中是否值得。
急切地创建实例
处理访问单例的多个线程的另一种方法是在加载或初始化类时立即创建单例实例(由 Dalvik VM 中的 Android 类加载器创建)。这使得代码线程安全。然后,在任何线程访问变量之前,对象实例将已经可用。INSTANCE
public class Singleton {
private static Singleton INSTANCE = new Singleton();
// other instance variables can be here
private Singleton() {};
public static Singleton getInstance() {
return(INSTANCE);
}
// other instance methods can follow
}
这种方法的缺点是,您最终可能会创建一个可能永远不会使用的对象,从而占用不必要的内存。因此,通常只当您确定将访问单例时,才应使用此方法。
处理多线程
在Android系统中,您可以分拆多个线程来执行不同的任务。这些线程最终可以同时执行相同的代码块。对于上面的类,这可能会导致创建多个对象实例,这违反了 Singleton 的协定。因此,我们的单例代码片段方法不是线程安全的。现在,我们将研究如何使其线程安全。SingletongetInstance()
拓展:Dagger 2
依赖关系注入库(如 Dagger)可以帮助您连接对象依赖关系并使用批注创建单例。这将确保在整个应用程序生命周期中仅初始化一次对象。@Singleton
@Module
public class NetworkModule {
@Provides
@Singleton
public Gson gson() {
GsonBuilder gsonBuilder = new GsonBuilder();
return gsonBuilder.create();
}
@Provides
@Singleton
public HttpLoggingInterceptor loggingInterceptor() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(
message -> Timber.i(message));
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return interceptor;
}
@Provides
@Singleton
public Cache cache(File cacheFile) {
return new Cache(cacheFile, 10 * 1000 * 1000); //10MB Cache
}
@Provides
@Singleton
public File cacheFile(@ApplicationContext Context context) {
return new File(context.getCacheDir(), "okhttp_cache");
}
@Provides
@Singleton
public OkHttpClient okHttpClient(HttpLoggingInterceptor loggingInterceptor, Cache cache) {
return new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.cache(cache)
.build();
}
@Provides
@Singleton
public Retrofit retrofit(OkHttpClient okHttpClient, Gson gson) {
return new Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.baseUrl("you/base/url")
.build();
}
}
在上面的代码中,我们创建了单个实例,最后从 Dagger 生成的依赖项图中提供类型。