前言


获取一个类的实例,除了使用其本身的构造器外,还可以使用静态工厂方法。

静态工厂方法的应用非常广泛,例如:对于一些工具类而言,在仅仅是想调用类的方法,却又不想为此创建一个实例(增加无意义的开销)的时候;在只需要一个实例存在,并全局都能访问到的时候;在创建并返回一个实例前做一些额外工作的时候,都可以利用静态工厂方法来达到目的。

接下来就通过三个例子具体说明静态工厂方法的应用。

在工具类中的应用


应用程序在运行时,一般需要将一些数据缓存到手机中,以便下次直接使用。为了方便地获取到应用程序的缓存路径,可以定义一个工具类 CacheDirManager :

public class CacheDirManager {
public static File getCacheFile(Context context) {
String cachePath;
if ((Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable())
&& context.getExternalCacheDir() != null) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator);
}
public static File getCacheFile(Context context, String uniqueName) {
String cachePath;
if ((Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable())
&& context.getExternalCacheDir() != null) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
}

CacheDirManager 类提供了两个公有的静态方法,方便调用时直接返回缓存路径,避免创建不必要的对象。

然而仅仅是这样还不够,开发人员还是能够通过调用系统提供的默认构造器获取 CacheDirManager 的实例。因此将其构造器设为私有,防止它被实例化:

private CacheDirManager() {
}

注:因类不含有公有的或者受保护的构造器,所以不能被子类化,推荐使用组合的方式而不是继承。

在使用单例场景中的应用


单例作为静态工厂中的一种,在 Android 开发过程中也常被用到。特别适用于每次程序启动时只会存在一个实例,并且需要被频繁访问的场景,例如:数据库对象 SQLiteDatabase 和 Android 上的轻量存储类 SharedPreferences

使用 SharedPreferences 有诸多限制,每次调用前必须拿到 Context ,而且也不是线程安全的。下面通过定义一个 PrefHelper 工具类来更方便地使用 SharedPreferences

public class PrefHelper {
private static SharedPreferences sPreferences;
private static PrefHelper sInstance;
/* 私有化构造器,防止被外部调用实例化,传入 Application ,不必在每次使用前获取 Context */
private PrefHelper() {
sPreferences = PreferenceManager.getDefaultSharedPreferences(getApplication());
}
public static PrefHelper getInstance() {
if (sInstance == null) {
sInstance = new PrefHelper(); // 创建实例
}
return sInstance;
}
...
}

上面的写法看上去没什么问题,只有在第一次调用静态方法时才创建实例。但如果多个线程同时访问时,很可能出现因状态不同步而重复创建多个实例的情况。

另一种写法是在第一次引用该类的时候就创建实例,不管实际是否需要创建:

public class PrefHelper {
private static SharedPreferences sPreferences;
private static PrefHelper sInstance = new PrefHelper(); // 创建实例
private PrefHelper() {
sPreferences = PreferenceManager.getDefaultSharedPreferences(getApplication());
}
public static PrefHelper getInstance() {
return sInstance;
}
...
}

这样写的好处就是在第一次引用 PrefHelper 时,其实例 sInstance 就已经创建好了,不必等到调用静态方法时才创建,因此是线程安全的。但很多时候我们还是只希望在调用单例方法时才创建实例以减小负载,所以还是接着改进第一种方法。

考虑到线程安全,修改静态方法:

public static PrefHelper getInstance() {
synchronized (PrefHelper.class) {
if (sInstance == null) {
sInstance = new PrefHelper();
}
}
return sInstance;
}

加入同步锁后解决了线程安全的问题,但是每次调用时都要检查一次同步锁,效率略低。所以在检查同步锁之前再加一层判断:

public static PrefHelper getInstance() {
if (sInstance == null) {
synchronized (PrefHelper.class) {
if (sInstance == null) {
sInstance = new PrefHelper();
}
}
}
return sInstance;
}

如果实例已经创建了,无须再加同步锁。这种写法应该很完美了吧?只在需要时才创建实例,加入同步锁保证线程安全,在加锁前对 sInstance 进行 null 检查,提高了效率。但问题出现在这里:

sInstance = new PrefHelper();

编译器在编译时会对指令进行重排优化,在实际执行时可能与编写时的顺序不同。所以可能会发生 sInstance 还没完成初始化完就被分配了内存空间。因此需要在声明 sInstance 前加入关键字 volatile

private static volatile PrefHelper sInstance;

当一个变量被定义为 volatile 之后,将具备两种特性:第一保证此变量对所有的线程的可见性;第二禁止指令重排序优化。进而避免了因编译器重排指令而导致的上述问题。

在 Fragment 中的应用


为了获取 Fragment 实例,我们可以直接调用系统为其提供的默认构造器:

SimpleFragment simpleFragment = new SimpleFragment();

如果在创建实例前,需要向 Fragment 传递参数,可以创建一个含参构造器:

public class SimpleFragment extends Fragment {
private int mArgs;
public SimpleFragment(int args) {
mArgs = args;
}
...
}
/* 创建实例 */
SimpleFragment simpleFragment = new SimpleFragment(0);

看似很正常,但在一些特殊情况下会出现问题:在屏幕旋转时、开启分屏模式时( Android 7.0 及以上支持的特性),Fragment 会伴随着 Activity 销毁重建。此时 Fragment 只会调用默认的无参构造器,之前传递的参数无法保存。

官方提倡的方法是利用静态工厂方法和 Bundle 传递数据:

public class SimpleFragment extends Fragment {
private int mArgs;
/* 定义一个含参静态方法,创建并返回一个 Fragment 实例 */
public static SimpleFragment newInstance(int args) {
SimpleFragment simpleFragment = new SimpleFragment();
Bundle bundle = new Bundle();
bundle.putInt("args", args);
simpleFragment.setArguments(bundle);
return simpleFragment;
}
/* 通过 Bundle 获取参数的值 */
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
mArgs = bundle.getInt("args");
}
}

此时调用者只需要关心传递哪些参数,即使在 Activity 重建之后,系统也会自动帮你恢复之前保存的数据。

参考链接