From 401900c9b20dfaff1a32f28071022c4438c50880 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Wed, 24 Feb 2021 00:10:10 +0800 Subject: [PATCH] Optimization: find views in single traversal. The app will pass a list of views for Textview translation. Currently, we traverse the view tree for each requested view. That means we will traverse view tree many times. If the requested size of view list is large, it shoule be a performance issue. We refine to find views in sinslge traversal in this change. Use a sample app with many views and layouts and compare the time of two implementations. The average time of original solution is 300000 ~ 600000 ns. The new approach is 150000 ~ 300000 ns. Bug:178989965 Test: Write a sample app with many views and complicate structure, see bug for test sample screenshot. Compare the execute time of two implementation. Change-Id: I5a051740ad40e77ed4a54294368031eb82ab87ab --- .../translation/UiTranslationController.java | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index b49d3c004f448..b1e45bbcd8755 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -32,6 +32,9 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewRootImpl; +import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; import android.view.translation.UiTranslationManager.UiTranslationState; @@ -194,24 +197,19 @@ public class UiTranslationController { private void onUiTranslationStarted(Translator translator, List views) { synchronized (mLock) { // Find Views collect the translation data - // TODO(b/178084101): try to optimize, e.g. to this in a single traversal - final int viewCounts = views.size(); final ArrayList requests = new ArrayList<>(); - for (int i = 0; i < viewCounts; i++) { - final AutofillId viewAutofillId = views.get(i); - final View view = mActivity.findViewByAutofillIdTraversal(viewAutofillId); - if (view == null) { - Log.w(TAG, "Can not find the View for autofill id= " + viewAutofillId); - continue; - } - mViews.put(viewAutofillId, new WeakReference<>(view)); + final ArrayList foundViews = new ArrayList<>(); + findViewsTraversalByAutofillIds(views, foundViews); + for (int i = 0; i < foundViews.size(); i++) { + final View view = foundViews.get(i); + final int currentCount = i; mActivity.runOnUiThread(() -> { final TranslationRequest translationRequest = view.onCreateTranslationRequest(); if (translationRequest != null && translationRequest.getTranslationText().length() > 0) { requests.add(translationRequest); } - if (requests.size() == viewCounts) { + if (currentCount == (foundViews.size() - 1)) { Log.v(TAG, "onUiTranslationStarted: send " + requests.size() + " request."); mWorkerHandler.sendMessage(PooledLambda.obtainMessage( UiTranslationController::sendTranslationRequest, @@ -222,6 +220,42 @@ public class UiTranslationController { } } + private void findViewsTraversalByAutofillIds(List sourceViewIds, + ArrayList foundViews) { + final ArrayList roots = + WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken()); + for (int rootNum = 0; rootNum < roots.size(); rootNum++) { + final View rootView = roots.get(rootNum).getView(); + if (rootView instanceof ViewGroup) { + findViewsTraversalByAutofillIds((ViewGroup) rootView, sourceViewIds, foundViews); + } else { + addViewIfNeeded(sourceViewIds, rootView, foundViews); + } + } + } + + private void findViewsTraversalByAutofillIds(ViewGroup viewGroup, + List sourceViewIds, ArrayList foundViews) { + final int childCount = viewGroup.getChildCount(); + for (int i = 0; i < childCount; ++i) { + final View child = viewGroup.getChildAt(i); + if (child instanceof ViewGroup) { + findViewsTraversalByAutofillIds((ViewGroup) child, sourceViewIds, foundViews); + } else { + addViewIfNeeded(sourceViewIds, child, foundViews); + } + } + } + + private void addViewIfNeeded(List sourceViewIds, View view, + ArrayList foundViews) { + final AutofillId autofillId = view.getAutofillId(); + if (sourceViewIds.contains(autofillId)) { + mViews.put(autofillId, new WeakReference<>(view)); + foundViews.add(view); + } + } + private void runForEachView(Consumer action) { synchronized (mLock) { final ArrayMap> views = new ArrayMap<>(mViews);