Skip to content

Commit e511d81

Browse files
Andrew Wangfacebook-github-bot
authored andcommitted
Convert SynchronizedTypefaceHelper to Kotlin
Summary: as per title Reviewed By: pengj Differential Revision: D52999913 fbshipit-source-id: e6c586ecd3d0bc843f3e49c5aff7b0f94b1d11c1
1 parent 517a72a commit e511d81

File tree

2 files changed

+136
-174
lines changed

2 files changed

+136
-174
lines changed

litho-rendercore/src/main/java/com/facebook/rendercore/utils/SynchronizedTypefaceHelper.java

Lines changed: 0 additions & 174 deletions
This file was deleted.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.facebook.rendercore.utils
18+
19+
import android.annotation.SuppressLint
20+
import android.annotation.TargetApi
21+
import android.graphics.Typeface
22+
import android.os.Build
23+
import android.util.LongSparseArray
24+
import android.util.SparseArray
25+
import androidx.core.util.forEach
26+
import java.util.concurrent.atomic.AtomicBoolean
27+
28+
object SynchronizedTypefaceHelper {
29+
30+
private val isInitialized = AtomicBoolean(false)
31+
32+
/**
33+
* Android doesn't expect typeface operations to occur off of the UI thread. To partially
34+
* alleviate this issue, we override the typeface cache with a synchronized version.
35+
*/
36+
@SuppressLint("PrivateApi")
37+
@JvmStatic
38+
fun setupSynchronizedTypeface() {
39+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
40+
// sTypefaceCache was introduced in API level 16.
41+
return
42+
}
43+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
44+
// sTypefaceCache was removed and Typeface is thread-safe since Android 9.
45+
return
46+
}
47+
if (isInitialized.getAndSet(true)) {
48+
return
49+
}
50+
try {
51+
val typefaceCacheField = Typeface::class.java.getDeclaredField("sTypefaceCache")
52+
typefaceCacheField.isAccessible = true
53+
val lock = Any()
54+
// This is nasty, but otherwise we have another race condition between
55+
// typefaceCacheField.set and newCache.append if Typeface.create(...) is called elsewhere.
56+
synchronized(lock) {
57+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
58+
val oldCache = typefaceCacheField[null] as LongSparseArray<SparseArray<Typeface>>
59+
val newCache = SynchronizedLongSparseArray(lock, oldCache.size())
60+
typefaceCacheField[null] = newCache
61+
oldCache.forEach { key, value ->
62+
newCache.append(key, SynchronizedTypefaceSparseArray(value))
63+
}
64+
} else {
65+
val oldCache = typefaceCacheField[null] as SparseArray<SparseArray<Typeface>>
66+
val newCache = SynchronizedSparseArray(lock, oldCache.size())
67+
typefaceCacheField[null] = newCache
68+
oldCache.forEach { key, value ->
69+
newCache.append(key, SynchronizedTypefaceSparseArray(value))
70+
}
71+
}
72+
}
73+
} catch (e: Exception) {
74+
// We'll probably hit some thread-safety issues as we haven't managed to set a synchronized
75+
// typefaceCache.
76+
}
77+
}
78+
79+
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
80+
internal class SynchronizedLongSparseArray(private val lock: Any, initialCapacity: Int) :
81+
LongSparseArray<SparseArray<Typeface>?>(initialCapacity) {
82+
83+
override fun get(key: Long): SparseArray<Typeface>? {
84+
synchronized(lock) {
85+
val sparseArray = super.get(key)
86+
if (sparseArray == null || sparseArray is SynchronizedTypefaceSparseArray) {
87+
return sparseArray
88+
}
89+
val synchronizedSparseArray = SynchronizedTypefaceSparseArray(sparseArray)
90+
put(key, synchronizedSparseArray)
91+
return synchronizedSparseArray
92+
}
93+
}
94+
95+
override fun put(key: Long, value: SparseArray<Typeface>?) {
96+
synchronized(lock) { super.put(key, value) }
97+
}
98+
}
99+
100+
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
101+
internal class SynchronizedSparseArray(private val lock: Any, initialCapacity: Int) :
102+
SparseArray<SparseArray<Typeface>?>(initialCapacity) {
103+
104+
override fun get(key: Int): SparseArray<Typeface>? {
105+
synchronized(lock) {
106+
val sparseArray = super.get(key)
107+
if (sparseArray == null || sparseArray is SynchronizedTypefaceSparseArray) {
108+
return sparseArray
109+
}
110+
val synchronizedSparseArray = SynchronizedTypefaceSparseArray(sparseArray)
111+
put(key, synchronizedSparseArray)
112+
return synchronizedSparseArray
113+
}
114+
}
115+
116+
override fun put(key: Int, value: SparseArray<Typeface>?) {
117+
synchronized(lock) { super.put(key, value) }
118+
}
119+
}
120+
121+
internal class SynchronizedTypefaceSparseArray(delegateSparseArray: SparseArray<Typeface>?) :
122+
SparseArray<Typeface>() {
123+
private val lock = Any()
124+
private val delegateSparseArray: SparseArray<Typeface> = delegateSparseArray ?: SparseArray()
125+
126+
override fun get(key: Int): Typeface {
127+
synchronized(lock) {
128+
return delegateSparseArray[key]
129+
}
130+
}
131+
132+
override fun put(key: Int, value: Typeface) {
133+
synchronized(lock) { delegateSparseArray.put(key, value) }
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)