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