/** * 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; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.TimeUnit; import android.annotation.TargetApi; import android.os.Build; import android.view.Display; import android.view.View; /** * {@link Runnable} that is used to prefetch display lists of components for which layout has been * already calculated but not yet appeared on screen. This will allow for faster drawing time when * these components come to screen. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public final class DisplayListPrefetcher implements Runnable { /** * Keeps average display list creation time per unique component type defined by component class * name. */ private static final HashMap<String, Long> sAverageDLPrefetchDurationNs = new HashMap<>(); private static DisplayListPrefetcher sDisplayListPrefetcher = new DisplayListPrefetcher(); private final Queue<WeakReference<LayoutState>> mLayoutStates; private long mFrameIntervalNs; private WeakReference<View> mHostingView; private DisplayListPrefetcher() { mLayoutStates = new LinkedList<>(); } public static DisplayListPrefetcher getInstance() { return sDisplayListPrefetcher; } public synchronized void setHostingView(View view) { if (mHostingView == null || mHostingView.get() != view) { mHostingView = new WeakReference<>(view); } initIfNeeded(view); } private void initIfNeeded(View view) { if (mFrameIntervalNs > 0) { return; } final Display display = view.getDisplay(); float refreshRate = 60.0f; if (!view.isInEditMode() && display != null) { final float displayRefreshRate = display.getRefreshRate(); if (displayRefreshRate >= 30.0f) { refreshRate = displayRefreshRate; } } mFrameIntervalNs = (long) (1000000000 / refreshRate); } synchronized void addLayoutState(LayoutState layoutState) { mLayoutStates.add(new WeakReference<>(layoutState)); } @Override public void run() { if (mFrameIntervalNs == 0) { // Not yet initialized. return; } final View hostingView = mHostingView.get(); if (hostingView == null) { return; } ComponentsSystrace.beginSection("DisplayListPrefetcher"); final long latestFrameVsyncNs = TimeUnit.MILLISECONDS.toNanos(hostingView.getDrawingTime()); final long nextVsyncNs = latestFrameVsyncNs + mFrameIntervalNs; while (true) { final LayoutState currentLayoutState = getValidLayoutStateFromQueue(); if (currentLayoutState == null) { break; } final LayoutOutput currentLayoutOutput = currentLayoutState.getNextLayoutOutputForDLPrefetch(); final String currentComponentType = currentLayoutOutput.getComponent().getSimpleName(); final long startPrefetchNs = System.nanoTime(); if (!canPrefetchOnTime(currentComponentType, startPrefetchNs, nextVsyncNs)) { break; } currentLayoutState.createDisplayList(currentLayoutOutput); if (currentLayoutOutput.getDisplayList() != null) { // successfully created DL final long actualElapsedNs = System.nanoTime() - startPrefetchNs; updateAveragePrefetchDuration(currentComponentType, actualElapsedNs); } } ComponentsSystrace.endSection(); } private boolean canPrefetchOnTime(String componentType, long startTimeNs, long deadlineNs) { final Long expectedPrefetchDurationNs = sAverageDLPrefetchDurationNs.get(componentType); return expectedPrefetchDurationNs == null || (startTimeNs + expectedPrefetchDurationNs < deadlineNs); } /** * @return the next {@link LayoutState} from the queue that has non-zero elements to process. */ private LayoutState getValidLayoutStateFromQueue() { WeakReference<LayoutState> currentLayoutState = mLayoutStates.peek(); while (currentLayoutState != null) { final LayoutState layoutState = currentLayoutState.get(); if (layoutState == null || !layoutState.hasItemsForDLPrefetch()) { mLayoutStates.remove(); currentLayoutState = mLayoutStates.peek(); } else { break; } } if (currentLayoutState == null) { // Queue is empty. return null; } return currentLayoutState.get(); } private void updateAveragePrefetchDuration(String componentType, long actualElapsedNs) { final Long expectedPrefetchDurationNs = sAverageDLPrefetchDurationNs.get(componentType); final long updatedValue; if (expectedPrefetchDurationNs == null) { updatedValue = actualElapsedNs; } else { // Not actual average, but good approximation. updatedValue = (expectedPrefetchDurationNs / 4 * 3) + (actualElapsedNs / 4); } sAverageDLPrefetchDurationNs.put(componentType, updatedValue); } public synchronized boolean hasPrefetchItems() { return !mLayoutStates.isEmpty(); } }