/** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.litho.widget; import java.lang.ref.WeakReference; import android.graphics.Canvas; import android.graphics.Picture; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.support.annotation.VisibleForTesting; import android.text.Layout; import com.facebook.fbui.textlayoutbuilder.util.LayoutMeasureUtil; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.os.Process.THREAD_PRIORITY_LOWEST; /** * A class that schedules a background draw of a {@link Layout}. Drawing a {@link Layout} in the * background ensures that the glyph caches are warmed up and ready for drawing the same * {@link Layout} on a real {@link Canvas}. This will substantially reduce drawing times for big * chunks of text. On the other hand over-using text warming might rotate the glyphs cache too * quickly and diminish the optimization. */ public class GlyphWarmer { private static final String TAG = GlyphWarmer.class.getName(); private static final int WARMER_THREAD_PRIORITY = (THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_LOWEST) / 2; private static GlyphWarmer sInstance; private WarmerHandler mHandler; /** * @return the global {@link GlyphWarmer} instance. */ public synchronized static GlyphWarmer getInstance() { if (sInstance == null) { sInstance = new GlyphWarmer(); } return sInstance; } private GlyphWarmer() { HandlerThread handlerThread = new HandlerThread(TAG, WARMER_THREAD_PRIORITY); handlerThread.start(); mHandler = new WarmerHandler(handlerThread.getLooper()); } @VisibleForTesting Looper getWarmerLooper() { return mHandler.getLooper(); } /** * Schedules a {@link Layout} to be drawn in the background. This warms up the Glyph cache for * that {@link Layout}. */ public void warmLayout(Layout layout) { mHandler.obtainMessage(WarmerHandler.WARM_LAYOUT, new WeakReference<>(layout)).sendToTarget(); } private static final class WarmerHandler extends Handler { public static final int WARM_LAYOUT = 0; private final Picture mPicture; private WarmerHandler(Looper looper) { super(looper); Picture picture; try { picture = new Picture(); } catch (RuntimeException e) { picture = null; } mPicture = picture; } @Override public void handleMessage(Message msg) { if (mPicture == null) { return; } try { final Layout layout = ((WeakReference<Layout>) msg.obj).get(); if (layout == null) { return; } final Canvas canvas = mPicture.beginRecording( layout.getWidth(), LayoutMeasureUtil.getHeight(layout)); layout.draw(canvas); mPicture.endRecording(); } catch (Exception e) { // Nothing to do here. This is a best effort. No real problem if it fails. } } } }