This is the second day of my participation in Gwen Challenge
preface
Nice to meet you
In the last article in this series, we covered the basics of Android fonts. If you haven’t read the last article, we recommend you read Android fonts first: Android font basics, as you’ll see, those three properties we set will eventually build a Typeface object, which we’ll talk about today
Note: the system source code shown in this article is based on Android-30, and extract the core parts for analysis
I. Introduction to Typeface
Typeface is responsible for loading Android fonts and calling the upper-layer font API
If you want to manipulate fonts, either using Android fonts or loading your own built-in.ttf(TureType) or.otf(OpenType) font files, you need to use Typeface. So to change fonts globally, we first need to understand Typeface
Typeface source code analysis
Source code analysis can be tedious, persistence is victory ⛽️
1. Typeface initialization
Typeface is a class that is loaded by reflection during Android application startup. Click on the source code to see that it contains a static code block that loads as the class loads, and only loads once. Typeface is initialized in this way, as follows:
static {
// Create a font Map
final HashMap<String, Typeface> systemFontMap = new HashMap<>();
// Put some of the system's default fonts into the Map
initSystemDefaultTypefaces(systemFontMap,SystemFonts.getRawSystemFallbackMap(),SystemFonts.getAliases());
// The unmodifiableMap method wraps the current Map and returns an unmodifiableMap. If you call the change method, an exception is thrown
sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
// We can't assume DEFAULT_FAMILY available on Roboletric.
/** * set system default font DEFAULT_FAMILY = "sans-serif"; * So the default font is sans-serif */
if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) {
setDefault(sSystemFontMap.get(DEFAULT_FAMILY));
}
// Set up defaults and typefaces exposed in public API
// Some system default fonts
DEFAULT = create((String) null.0);
DEFAULT_BOLD = create((String) null, Typeface.BOLD);
SANS_SERIF = create("sans-serif".0);
SERIF = create("serif".0);
MONOSPACE = create("monospace".0);
// Initialize an sDefaults array and preload common styles such as bold and italic
sDefaults = new Typeface[] {
DEFAULT,
DEFAULT_BOLD,
create((String) null, Typeface.ITALIC),
create((String) null, Typeface.BOLD_ITALIC),
};
/ /...
}
Copy the code
The above code is commented in detail, and we can see that Typeface initialization mainly does:
Put some of the system’s default fonts into a Map
2. Set the default font
3. Initialize some default fonts
4. Initialize an sDefaults array containing common styles
Typeface provides a set of apis for creating fonts, including the following upper-layer open calls:
Let’s focus on these methods
2. Use Typeface and Style to obtain a new Typeface
For the first API in the screenshot above, take a look at its source code:
public static Typeface create(Typeface family, @Style int style) {
// Check whether the current style is set, if not, set to NORMAL
if((style & ~STYLE_MASK) ! =0) {
style = NORMAL;
}
// Determine whether the currently passed Typeface is empty, and if so, set it to the default font
if (family == null) {
family = sDefaultTypeface;
}
// Return early if we're asked for the same face/style
// If the current Typeface mStyle property is the same as the style passed in, the Typeface object is returned directly
if (family.mStyle == style) {
return family;
}
final long ni = family.native_instance;
Typeface typeface;
// Use sStyledCacheLock to ensure thread safety
synchronized (sStyledCacheLock) {
// Get SparseArray where Typeface is stored from the cache
SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni);
if (styles == null) {
// SparseArray where Typeface is stored is empty. Create a new SparseArray with capacity 4
styles = new SparseArray<Typeface>(4);
// Put the SparseArray currently storing Typeface in the cache
sStyledTypefaceCache.put(ni, styles);
} else {
// SparseArray where Typeface is stored is not empty
typeface = styles.get(style);
if(typeface ! =null) {
returntypeface; }}// Use native layer build to create Typeface parameters and create Typeface objects
typeface = new Typeface(nativeCreateFromTypeface(ni, style));
// Cache the newly created Typeface object in SparseArray
styles.put(style, typeface);
}
return typeface;
}
Copy the code
From the above code we know:
1. When you set Typeface and Style to null and 0, they are set to default values
Note: here the Style, corresponding to the android:textStyle property transfer value, used to set the font bold, italic and other parameters
2. If the current Typeface mStyle property is the same as the Style passed in, return the Typeface directly
3. Get the container where the Typeface is stored from the cache. If it exists in the cache, fetch the Typeface from the container and return it
4. If it does not exist, create a new container and add it to the cache. Then create a Typeface from the Native layer and put the current Typeface into the container
So we don’t have to worry about efficiency when we use it, it caches the fonts that we pass in, and then pulls them out of the cache
3. Get the font by font name and Style
Corresponding to the second API in the screenshot above:
public static Typeface create(String familyName, @Style int style) {
// Call the first API of the screenshot
return create(getSystemDefaultTypeface(familyName), style);
}
// Get some default fonts provided by the system, if not, return the system default font
private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
Typeface tf = sSystemFontMap.get(familyName);
return tf == null ? Typeface.DEFAULT : tf;
}
Copy the code
1. The API for creating Typeface is simple enough to call one of its overloaded methods, which we have already analyzed
2, getSystemDefaultTypeface mainly through sSystemFontMap font, and this sSystemFontMap when storing the Typeface is initialized system provides some default font, so here directly
4. Use Typeface, weight(bold), and italic(italic) to obtain the new Typeface
The third API corresponds to the screenshot above
public static @NonNull Typeface create(@Nullable Typeface family,
@IntRange(from = 1, to = 1000) int weight, boolean italic) {
// Verify that the weight attribute passed is in range
Preconditions.checkArgumentInRange(weight, 0.1000."weight");
if (family == null) {
// If the current Typeface passed in is null, the default value is set
family = sDefaultTypeface;
}
// Call the createWeightStyle method to create Typeface
return createWeightStyle(family, weight, italic);
}
private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
@IntRange(from = 1, to = 1000) int weight, boolean italic) {
final int key = (weight << 1) | (italic ? 1 : 0);
Typeface typeface;
// Use sWeightCacheLock to ensure thread safety
synchronized(sWeightCacheLock) {
SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance);
if (innerCache == null) {
// Cache Typeface SparseArray to null, create and cache
innerCache = new SparseArray<>(4);
sWeightTypefaceCache.put(base.native_instance, innerCache);
} else {
// Fetch typeFace from the cache and return it
typeface = innerCache.get(key);
if(typeface ! =null) {
returntypeface; }}// Create Typeface objects via native
typeface = new Typeface(
nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic));
// Add Typeface to the cache
innerCache.put(key, typeface);
}
return typeface;
}
Copy the code
Through the above code can know, he is very similar to the source code of the screenshot API, but will need to set the Style before the weight and ITALic, the implementation mechanism is similar
5. Get the font from AssetManager and the corresponding font path
The fourth API corresponds to the screenshot above
public static Typeface createFromAsset(AssetManager mgr, String path) {
// Check parameters
Preconditions.checkNotNull(path); // for backward compatibility
Preconditions.checkNotNull(mgr);
// Build Typeface through Typeface's Builder mode
Typeface typeface = new Builder(mgr, path).build();
// Return if the built TypeFace is not empty
if(typeface ! =null) return typeface;
// check if the file exists, and throw an exception for backward compatibility
// Check whether the current font path exists. No direct throw exception exists
try (InputStream inputStream = mgr.open(path)) {
} catch (IOException e) {
throw new RuntimeException("Font asset not found " + path);
}
// Return the default font if the font built is null
return Typeface.DEFAULT;
}
// Then look at the Typeface Builder mode to build Typeface
// Initialize the mFontBuilder and some parameters
public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset,
int cookie) {
mFontBuilder = new Font.Builder(assetManager, path, isAsset, cookie);
mAssetManager = assetManager;
mPath = path;
}
/ / the build method
public Typeface build(a) {
// If mFontBuilder is null, the resolveFallbackTypeface method is called
/ / resolveFallbackTypeface internal calls createWeightStyle create Typeface and return
if (mFontBuilder == null) {
return resolveFallbackTypeface();
}
try {
// Build Font from mFontBuilder
final Font font = mFontBuilder.build();
// Use the createAssetUid method to get the unique key for this font
final String key = mAssetManager == null ? null : createAssetUid(
mAssetManager, mPath, font.getTtcIndex(), font.getAxes(),
mWeight, mItalic,
mFallbackFamilyName == null ? DEFAULT_FAMILY : mFallbackFamilyName);
if(key ! =null) {
// Dynamic cache lookup is only for assets.
// Use sDynamicCacheLock to ensure thread safety
synchronized (sDynamicCacheLock) {
// Retrieve the font from the cache by key
final Typeface typeface = sDynamicTypefaceCache.get(key);
// Returns if the current font is not null
if(typeface ! =null) {
returntypeface; }}}// If the current font does not exist, build a FontFamily object using Builder mode
// Build the CustomFallbackBuilder object with FontFamily
// Finally build the Typeface object with CustomFallbackBuilder
final FontFamily family = new FontFamily.Builder(font).build();
final int weight = mWeight == RESOLVE_BY_FONT_TABLE
? font.getStyle().getWeight() : mWeight;
final int slant = mItalic == RESOLVE_BY_FONT_TABLE
? font.getStyle().getSlant() : mItalic;
final CustomFallbackBuilder builder = new CustomFallbackBuilder(family)
.setStyle(new FontStyle(weight, slant));
if(mFallbackFamilyName ! =null) {
builder.setSystemFallback(mFallbackFamilyName);
}
// Inside the builder.build method, Typeface objects are eventually created by calling the Native layer
final Typeface typeface = builder.build();
// Cache the Typeface object and return it
if(key ! =null) {
synchronized(sDynamicCacheLock) { sDynamicTypefaceCache.put(key, typeface); }}return typeface;
} catch (IOException | IllegalArgumentException e) {
// If there are any exceptions in the process, createWeightStyle is internally called to create Typeface and return
returnresolveFallbackTypeface(); }}Copy the code
The code steps above:
1. A large number of Builder mode is used to build relevant objects
The logic is to use the createAssetUid method to get the unique key of the current font. With this unique key, the loaded font is retrieved from the cache. If not, a FontFamily object is created and passed through a series of Builder modes. The Native layer is eventually called to create a Typeface object, which is added to the cache and returned
3. If the process has any exceptions, createWeightStyle is internally called to create Typeface and return
6. Get fonts from font files
The fifth API corresponds to the screenshot above
public static Typeface createFromFile(@Nullable File file) {
// For the compatibility reasons, leaving possible NPE here.
// See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
// Build Typeface through Typeface's Builder mode
Typeface typeface = new Builder(file).build();
if(typeface ! =null) return typeface;
// check if the file exists, and throw an exception for backward compatibility
// File does not exist, throw exception
if(! file.exists()) {throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
}
// Return the default font if the font built is null
return Typeface.DEFAULT;
}
The other constructor is to initialize the mFontBuilder
public Builder(@NonNull File path) {
mFontBuilder = new Font.Builder(path);
mAssetManager = null;
mPath = null;
}
Copy the code
As can be seen from the above code, this approach is mainly through the Builder mode to build Typeface objects, the specific logic we have just analyzed
7. Get font from font path
The sixth API corresponds to the screenshot above
public static Typeface createFromFile(@Nullable String path) {
Preconditions.checkNotNull(path); // for backward compatibility
return createFromFile(new File(path));
}
Copy the code
This one is much simpler, basically creating a file object and calling another overloaded method
Typeface related Native methods
In Typeface, all final operations to load fonts are native methods. Native methods are known for their efficiency, so you only need to ensure infrequent calls (Typeface is already cached and not frequently called), and basically there is no problem with efficiency.
private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native long nativeCreateFromTypefaceWithExactStyle(
long native_instance, int weight, boolean italic);
// TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
private static native long nativeCreateFromTypefaceWithVariation(
long native_instance, List<FontVariationAxis> axes);
@UnsupportedAppUsage
private static native long nativeCreateWeightAlias(long native_instance, int weight);
@UnsupportedAppUsage
private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
private static native int[] nativeGetSupportedAxes(long native_instance);
@CriticalNative
private static native void nativeSetDefault(long nativePtr);
@CriticalNative
private static native int nativeGetStyle(long nativePtr);
@CriticalNative
private static native int nativeGetWeight(long nativePtr);
@CriticalNative
private static native long nativeGetReleaseFunc(a);
private static native void nativeRegisterGenericFamily(String str, long nativePtr);
Copy the code
Now that we’re done with the Typeface source section, let’s look at some of its other details
Other Details of Typeface
1. Default value
In the initialization section, Typeface has some default implementations for fonts and styles
If we only want to use the default font, we can just use the constants above, such as:
Typeface.DEFAULT
Typeface.DEFAULT_BOLD
Typeface.SANS_SERIF
Typeface.SERIF
Typeface.MONOSPACE
Copy the code
If we want to set the Style, we can’t get it directly from sDefaults because the upper layer can’t call sDefaults, but we can get it from the API provided by Typeface:
public static Typeface defaultFromStyle(@Style int style) {
return sDefaults[style];
}
// Specific call
Typeface.defaultFromStyle(Typeface.NORMAL)
Typeface.defaultFromStyle(Typeface.BOLD)
Typeface.defaultFromStyle(Typeface.ITALIC)
Typeface.defaultFromStyle(Typeface.BOLD_ITALIC)
Copy the code
2, Typeface Style
1), Typeface Style can be set to bold, italic and other styles by android:textStyle attribute
2) In Typeface, these styles also correspond to constants, and Typeface also provides the corresponding Api, let us get the current font style
// Style
public static final int NORMAL = 0;
public static final int BOLD = 1;
public static final int ITALIC = 2;
public static final int BOLD_ITALIC = 3;
/** Returns the typeface's intrinsic style attributes */
public @Style int getStyle(a) {
return mStyle;
}
/** Returns true if getStyle() has the BOLD bit set. */
public final boolean isBold(a) {
return(mStyle & BOLD) ! =0;
}
/** Returns true if getStyle() has the ITALIC bit set. */
public final boolean isItalic(a) {
return(mStyle & ITALIC) ! =0;
}
Copy the code
3. Introduction to FontFamily
FontFamily is primarily a class used to build Typeface, which should be distinguished from android: FontFamily, which is set in the Xml attribute
Four,
To summarize some key points of this article:
Typeface initialization has some default implementations for fonts and styles
Typeface Create supports obtaining fonts from system default fonts, assets directories, font files, and font paths
Typeface itself supports caching, so we don’t need to pay attention to efficiency issues when using it
Well, this article is over here, I hope to bring you help 🤝
Thank you for reading this article
The next trailer
Stay tuned for the next article about using fonts in Xml 😄
References and recommendations
Android modifies fonts that can’t be skipped by Typeface
Full text here, the original is not easy, welcome to like, collect, comment and forward, your recognition is the power of my creation
Please follow my public account and search sweetying on wechat. Updates will be received as soon as possible