В нашем приложении для Android у нас есть следующие требования
- Предварительно загрузите все используемые гарнитуры
- Кэширование загруженных гарнитур
До сих пор у нас был простой синглтон, который выглядел так:
@NotThreadSafe
public static void init(Context context) {
if (instance == null) {
instance = new TypefaceHelper(context);
instance.setAssetManager(context.getAssets());
instance.setAllTypefaces();
}
}
Ниже приведены проблемы, с которыми я столкнулся:
- Не поточно-ориентированный. Обратите внимание, что до сих пор он всегда доступен в Main-Thread (но из разных классов)
- setAllTypefaces () занимает более 50 мс
в среднем, чтобы загрузить 4 шрифта, которые мы используем.
Итак, каждый раз, когда вызывается MyApplication (который расширяет приложение Android), сама инициализация гарнитуры занимала более 50 мс.
В рамках настройки производительности мы решили, что имеет смысл загружать гарнитуры в фоновом режиме. Итак, теперь я хочу загрузить гарнитуры в фоновом режиме, а также сделать поток инициализации безопасным.
Это дизайн, который я придумал
public class TypefaceHelper {
private static TypefaceHelper instance;
private volatile Typeface proximaRegular;
private final Object typefaceLock = new Object();
private static final Object constructorLock = new Object();
@GuardedBy("constructorLock")
public static void init(Context context) {
synchronized (constructorLock) {
if (instance == null) {
instance = new TypefaceHelper(context);
instance.setAssetManager(context.getAssets());
MyApplicationHelper.getInstance().getExecutorService().execute(new Runnable() {
@Override
public void run() {
instance.setAllTypefaces(); // Set up all Typefaces in background thread
}
});
}
}
}
@Nullable
public static TypefaceHelper getInstance() { // Existing code used in 100+ places. Not touched now.
return instance;
}
// Accessing a typeface. Use Double-checked locking to ensure it works
public Typeface getProximaRegular() {
if(proximaRegular==null){ // The variable must be volatile to prevent compiler re-orderings and optimizations
synchronized (typefaceLock){
if(proximaRegular==null){
proximaRegular = Typeface.createFromAsset(assetManager, "fonts/ProximaNova-Regular.otf");
}
}
}
return proximaRegular;
}
@GuardedBy("typefaceLock")
private void setAllTypefaces(){
synchronized (typefaceLock) {
proximaRegular = Typeface.createFromAsset(assetManager, "fonts/ProximaNova-Regular.otf");
// Initialize 3 other typefaces here
}
}
}
Обратите внимание, что я использую нулевую проверку вне синхронизированного блока в getProxmimaRegular (), потому что к нему обращаются из более чем 100 мест, и я не хочу, чтобы блокировка была получена, если она не равна нулю.
Мои вопросы:
1) Это плохая практика загружать переменные-члены одиночного элемента в фоновом режиме? (В противном случае на производительность сильно влияют)
2) здесь используется отдельная блокировка для init () излишним?
3) Как я могу сделать это лучше, учитывая мои требования?