diff options
author | Danny Lin <danny@kdrag0n.dev> | 2021-04-06 15:30:33 -0700 |
---|---|---|
committer | alk3pInjection <webmaster@raspii.tech> | 2021-09-27 21:17:05 +0800 |
commit | 703535dd36ebd154a6d95b562e695ac30c36535b (patch) | |
tree | a71ab3a8c5a361f986967b9153ff72813c53faa9 | |
parent | 6a52f431597cb8cb4411be3be4d311d2ea1e6c47 (diff) |
[ProtonAOSP][rvc] LayoutInflater: Opportunistically create views directly for performance
When opening and closing activities in Settings, a significant amount of
CPU time is spent performing JNI calls, as reported by simpleperf:
0.39% /system/framework/arm64/boot-framework.oat art_jni_trampoline
Reflection in LayoutInflater is responsible for a significant portion
of the time spent in the JNI trampoline:
6.08% 0.08% /apex/com.android.art/javalib/arm64/boot.oat art_jni_trampoline
|
-- art_jni_trampoline
|
|--12.38%-- java.lang.reflect.Constructor.newInstance
| |--0.09%-- [hit in function]
| |
| |--88.32%-- android.view.LayoutInflater.createView
| | |
| | |--83.39%-- com.android.internal.policy.PhoneLayoutInflater.onCreateView
| | | android.view.LayoutInflater.onCreateView
| | | android.view.LayoutInflater.onCreateView
| | | android.view.LayoutInflater.createViewFromTag
| | | |
| | | |--72.73%-- android.view.LayoutInflater.rInflate
| | | | |
| | | | |--57.90%-- android.view.LayoutInflater.rInflate
| | | | | |
| | | | | |--94.90%-- android.view.LayoutInflater.inflate
| | | | | | android.view.LayoutInflater.inflate
| | | | | | |--35.86%-- [hit in function]
| | | | | | |
| | | | | | |--58.15%-- androidx.preference.PreferenceGroupAdapter.onCreateViewHolder
Empirical testing of interacting with ~113 real-world apps reveals that
many of the most frequently-inflated views are framework classes:
13486 android.widget.LinearLayout
6930 android.widget.View
6447 android.widget.FrameLayout
5613 android.widget.ViewStub
5608 androidx.constraintlayout.widget.ConstraintLayout
4722 android.widget.TextView
4431 com.google.android.material.textview.MaterialTextView
3570 eu.faircode.email.FixedTextView
3044 android.widget.ImageView
2665 android.widget.RelativeLayout
1694 android.widget.Space
979 androidx.preference.internal.PreferenceImageView
926 androidx.appcompat.view.menu.ActionMenuItemView
884 androidx.appcompat.widget.AppCompatImageView
855 slack.uikit.components.icon.SKIconView
770 android.widget.ProgressBar
743 com.fastaccess.ui.widgets.FontTextView
541 androidx.recyclerview.widget.RecyclerView
442 androidx.appcompat.widget.AppCompatTextView
404 org.mariotaku.twidere.view.MediaPreviewImageView
393 com.moez.QKSMS.common.widget.QkTextView
382 android.widget.Button
365 slack.widgets.core.textview.ClickableLinkTextView
365 slack.uikit.components.avatar.SKAvatarView
352 com.google.android.libraries.inputmethod.widgets.SoftKeyView
351 com.android.launcher3.BubbleTextView
315 slack.widgets.core.viewcontainer.SingleViewContainer
315 slack.widgets.core.textview.MaxWidthTextView
313 androidx.constraintlayout.widget.Barrier
302 slack.app.ui.widgets.ReactionsLayout
302 slack.app.ui.messages.widgets.MessageLayout
302 slack.app.ui.messages.widgets.MessageHeader
290 com.android.launcher3.views.DoubleShadowBubbleTextView
285 com.android.internal.widget.CachingIconView
265 android.widget.ImageButton
262 androidx.constraintlayout.widget.Guideline
249 org.thoughtcrime.securesms.components.emoji.EmojiTextView
234 com.google.android.libraries.inputmethod.widgets.AutoSizeTextView
232 com.android.internal.widget.RemeasuringLinearLayout
228 android.view.ViewStub
227 android.app.ViewStub
226 android.webkit.ViewStub
221 im.vector.app.core.ui.views.ShieldImageView
219 androidx.constraintlayout.widget.Group
214 androidx.coordinatorlayout.widget.CoordinatorLayout
204 androidx.appcompat.widget.ContentFrameLayout
All framework classes seen:
13486 android.widget.LinearLayout
6930 android.widget.View
6447 android.widget.FrameLayout
5613 android.widget.ViewStub
4722 android.widget.TextView
3044 android.widget.ImageView
2665 android.widget.RelativeLayout
1694 android.widget.Space
770 android.widget.ProgressBar
382 android.widget.Button
265 android.widget.ImageButton
228 android.view.ViewStub
227 android.app.ViewStub
226 android.webkit.ViewStub
145 android.widget.Switch
117 android.widget.DateTimeView
86 android.widget.Toolbar
68 android.widget.HorizontalScrollView
67 android.widget.ScrollView
65 android.widget.NotificationHeaderView
65 android.webkit.NotificationHeaderView
65 android.view.NotificationHeaderView
65 android.app.NotificationHeaderView
63 android.webkit.View
63 android.view.View
62 android.app.View
58 android.widget.ListView
50 android.widget.QuickContactBadge
40 android.widget.SeekBar
38 android.widget.CheckBox
16 android.widget.GridLayout
15 android.widget.TableRow
15 android.widget.RadioGroup
15 android.widget.Chronometer
13 android.widget.ViewFlipper
9 android.widget.Spinner
8 android.widget.ViewSwitcher
8 android.widget.TextSwitcher
8 android.widget.SurfaceView
8 android.widget.CheckedTextView
8 android.preference.PreferenceFrameLayout
7 android.widget.TwoLineListItem
5 android.widget.TableLayout
5 android.widget.EditText
3 android.widget.TabWidget
3 android.widget.TabHost
2 android.widget.ZoomButton
2 android.widget.TextureView
2 android.widget.ExpandableListView
2 android.webkit.TextureView
2 android.view.TextureView
2 android.app.TextureView
1 android.widget.WebView
1 android.widget.ViewAnimator
1 android.widget.TextClock
1 android.widget.AutoCompleteTextView
1 android.webkit.WebView
1 android.webkit.SurfaceView
1 android.view.SurfaceView
1 android.app.SurfaceView
Unfortunately, replacing reflection with MethodHandle constructors is
counter-productive in terms of performance:
Constructor direct: create=5 invoke=42
Constructor reflection: create=310 invoke=433
Constructor MethodHandle: create=3283 invoke=3489
Constructor MethodHandle-exact: create=3273 invoke=3453
To reduce the performance impact of slow reflection, we can leverage the
fact that the most frequently-inflated classes are from the framework,
and hard-code direct constructor references for them in a switch-case
block. Reflection will automatically be used as a fallback for custom
app views.
Test: simpleperf record -a; verify that Constructor.newInstance ->
LayoutInflater no longer appears at the top under
art_jni_trampoline
Change-Id: I8fcc0e05813ff9ecf1eddca3cc6920e747adf4fc
-rw-r--r-- | core/java/android/view/LayoutInflater.java | 217 |
1 files changed, 171 insertions, 46 deletions
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index 1afe11ea1acf..d9e9b6294a74 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -798,67 +798,75 @@ public abstract class LayoutInflater { throws ClassNotFoundException, InflateException { Objects.requireNonNull(viewContext); Objects.requireNonNull(name); - Constructor<? extends View> constructor = sConstructorMap.get(name); - if (constructor != null && !verifyClassLoader(constructor)) { - constructor = null; - sConstructorMap.remove(name); - } + String prefixedName = prefix != null ? (prefix + name) : name; Class<? extends View> clazz = null; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); - if (constructor == null) { - // Class not found in the cache, see if it's real, and try to add it - clazz = Class.forName(prefix != null ? (prefix + name) : name, false, - mContext.getClassLoader()).asSubclass(View.class); - - if (mFilter != null && clazz != null) { - boolean allowed = mFilter.onLoadClass(clazz); - if (!allowed) { - failNotAllowed(name, prefix, viewContext, attrs); - } + // Opportunistically create view directly instead of using reflection + View view = tryCreateViewDirect(prefixedName, viewContext, attrs); + if (view == null) { + Constructor<? extends View> constructor = sConstructorMap.get(name); + if (constructor != null && !verifyClassLoader(constructor)) { + constructor = null; + sConstructorMap.remove(name); } - constructor = clazz.getConstructor(mConstructorSignature); - constructor.setAccessible(true); - sConstructorMap.put(name, constructor); - } else { - // If we have a filter, apply it to cached constructor - if (mFilter != null) { - // Have we seen this name before? - Boolean allowedState = mFilterMap.get(name); - if (allowedState == null) { - // New class -- remember whether it is allowed - clazz = Class.forName(prefix != null ? (prefix + name) : name, false, - mContext.getClassLoader()).asSubclass(View.class); - - boolean allowed = clazz != null && mFilter.onLoadClass(clazz); - mFilterMap.put(name, allowed); + + if (constructor == null) { + // Class not found in the cache, see if it's real, and try to add it + clazz = Class.forName(prefixedName, false, + mContext.getClassLoader()).asSubclass(View.class); + + if (mFilter != null && clazz != null) { + boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, viewContext, attrs); } - } else if (allowedState.equals(Boolean.FALSE)) { - failNotAllowed(name, prefix, viewContext, attrs); + } + constructor = clazz.getConstructor(mConstructorSignature); + constructor.setAccessible(true); + sConstructorMap.put(name, constructor); + } else { + // If we have a filter, apply it to cached constructor + if (mFilter != null) { + // Have we seen this name before? + Boolean allowedState = mFilterMap.get(name); + if (allowedState == null) { + // New class -- remember whether it is allowed + clazz = Class.forName(prefixedName, false, + mContext.getClassLoader()).asSubclass(View.class); + + boolean allowed = clazz != null && mFilter.onLoadClass(clazz); + mFilterMap.put(name, allowed); + if (!allowed) { + failNotAllowed(name, prefix, viewContext, attrs); + } + } else if (allowedState.equals(Boolean.FALSE)) { + failNotAllowed(name, prefix, viewContext, attrs); + } } } - } - Object lastContext = mConstructorArgs[0]; - mConstructorArgs[0] = viewContext; - Object[] args = mConstructorArgs; - args[1] = attrs; + Object lastContext = mConstructorArgs[0]; + mConstructorArgs[0] = viewContext; + Object[] args = mConstructorArgs; + args[1] = attrs; - try { - final View view = constructor.newInstance(args); - if (view instanceof ViewStub) { - // Use the same context when inflating ViewStub later. - final ViewStub viewStub = (ViewStub) view; - viewStub.setLayoutInflater(cloneInContext((Context) args[0])); + try { + view = constructor.newInstance(args); + } finally { + mConstructorArgs[0] = lastContext; } - return view; - } finally { - mConstructorArgs[0] = lastContext; } + + if (view instanceof ViewStub) { + // Use the same context when inflating ViewStub later. + final ViewStub viewStub = (ViewStub) view; + viewStub.setLayoutInflater(cloneInContext((Context) viewContext)); + } + + return view; } catch (NoSuchMethodException e) { final InflateException ie = new InflateException( getParserStateDescription(viewContext, attrs) @@ -1357,4 +1365,121 @@ public abstract class LayoutInflater { } } } + + // Some of the views included here are deprecated, but apps still use them. + @SuppressWarnings("deprecation") + private static View tryCreateViewDirect(String name, Context context, AttributeSet attributeSet) { + // This contains all the framework views used in a set of 113 real-world apps, sorted by + // number of occurrences. While views with only 1 occurrence are unlikely to be worth + // optimizing, it doesn't hurt to include them because switch-case is compiled into a table + // lookup after calling String#hashCode(). + switch (name) { + case "android.widget.LinearLayout": // 13486 occurrences + return new android.widget.LinearLayout(context, attributeSet); + case "android.widget.View": // 6930 occurrences + case "android.webkit.View": // 63 occurrences + case "android.view.View": // 63 occurrences + case "android.app.View": // 62 occurrences + return new android.view.View(context, attributeSet); + case "android.widget.FrameLayout": // 6447 occurrences + return new android.widget.FrameLayout(context, attributeSet); + case "android.widget.ViewStub": // 5613 occurrences + case "android.view.ViewStub": // 228 occurrences + case "android.app.ViewStub": // 227 occurrences + case "android.webkit.ViewStub": // 226 occurrences + return new android.view.ViewStub(context, attributeSet); + case "android.widget.TextView": // 4722 occurrences + return new android.widget.TextView(context, attributeSet); + case "android.widget.ImageView": // 3044 occurrences + return new android.widget.ImageView(context, attributeSet); + case "android.widget.RelativeLayout": // 2665 occurrences + return new android.widget.RelativeLayout(context, attributeSet); + case "android.widget.Space": // 1694 occurrences + return new android.widget.Space(context, attributeSet); + case "android.widget.ProgressBar": // 770 occurrences + return new android.widget.ProgressBar(context, attributeSet); + case "android.widget.Button": // 382 occurrences + return new android.widget.Button(context, attributeSet); + case "android.widget.ImageButton": // 265 occurrences + return new android.widget.ImageButton(context, attributeSet); + case "android.widget.Switch": // 145 occurrences + return new android.widget.Switch(context, attributeSet); + case "android.widget.DateTimeView": // 117 occurrences + return new android.widget.DateTimeView(context, attributeSet); + case "android.widget.Toolbar": // 86 occurrences + return new android.widget.Toolbar(context, attributeSet); + case "android.widget.HorizontalScrollView": // 68 occurrences + return new android.widget.HorizontalScrollView(context, attributeSet); + case "android.widget.ScrollView": // 67 occurrences + return new android.widget.ScrollView(context, attributeSet); + case "android.widget.NotificationHeaderView": // 65 occurrences + case "android.webkit.NotificationHeaderView": // 65 occurrences + case "android.view.NotificationHeaderView": // 65 occurrences + case "android.app.NotificationHeaderView": // 65 occurrences + return new android.view.NotificationHeaderView(context, attributeSet); + case "android.widget.ListView": // 58 occurrences + return new android.widget.ListView(context, attributeSet); + case "android.widget.QuickContactBadge": // 50 occurrences + return new android.widget.QuickContactBadge(context, attributeSet); + case "android.widget.SeekBar": // 40 occurrences + return new android.widget.SeekBar(context, attributeSet); + case "android.widget.CheckBox": // 38 occurrences + return new android.widget.CheckBox(context, attributeSet); + case "android.widget.GridLayout": // 16 occurrences + return new android.widget.GridLayout(context, attributeSet); + case "android.widget.TableRow": // 15 occurrences + return new android.widget.TableRow(context, attributeSet); + case "android.widget.RadioGroup": // 15 occurrences + return new android.widget.RadioGroup(context, attributeSet); + case "android.widget.Chronometer": // 15 occurrences + return new android.widget.Chronometer(context, attributeSet); + case "android.widget.ViewFlipper": // 13 occurrences + return new android.widget.ViewFlipper(context, attributeSet); + case "android.widget.Spinner": // 9 occurrences + return new android.widget.Spinner(context, attributeSet); + case "android.widget.ViewSwitcher": // 8 occurrences + return new android.widget.ViewSwitcher(context, attributeSet); + case "android.widget.TextSwitcher": // 8 occurrences + return new android.widget.TextSwitcher(context, attributeSet); + case "android.widget.SurfaceView": // 8 occurrences + case "android.webkit.SurfaceView": // 1 occurrence + case "android.view.SurfaceView": // 1 occurrence + case "android.app.SurfaceView": // 1 occurrence + return new android.view.SurfaceView(context, attributeSet); + case "android.widget.CheckedTextView": // 8 occurrences + return new android.widget.CheckedTextView(context, attributeSet); + case "android.preference.PreferenceFrameLayout": // 8 occurrences + return new android.preference.PreferenceFrameLayout(context, attributeSet); + case "android.widget.TwoLineListItem": // 7 occurrences + return new android.widget.TwoLineListItem(context, attributeSet); + case "android.widget.TableLayout": // 5 occurrences + return new android.widget.TableLayout(context, attributeSet); + case "android.widget.EditText": // 5 occurrences + return new android.widget.EditText(context, attributeSet); + case "android.widget.TabWidget": // 3 occurrences + return new android.widget.TabWidget(context, attributeSet); + case "android.widget.TabHost": // 3 occurrences + return new android.widget.TabHost(context, attributeSet); + case "android.widget.ZoomButton": // 2 occurrences + return new android.widget.ZoomButton(context, attributeSet); + case "android.widget.TextureView": // 2 occurrences + case "android.webkit.TextureView": // 2 occurrences + case "android.app.TextureView": // 2 occurrences + case "android.view.TextureView": // 2 occurrences + return new android.view.TextureView(context, attributeSet); + case "android.widget.ExpandableListView": // 2 occurrences + return new android.widget.ExpandableListView(context, attributeSet); + case "android.widget.ViewAnimator": // 1 occurrence + return new android.widget.ViewAnimator(context, attributeSet); + case "android.widget.TextClock": // 1 occurrence + return new android.widget.TextClock(context, attributeSet); + case "android.widget.AutoCompleteTextView": // 1 occurrence + return new android.widget.AutoCompleteTextView(context, attributeSet); + case "android.widget.WebView": // 1 occurrence + case "android.webkit.WebView": // 1 occurrence + return new android.webkit.WebView(context, attributeSet); + } + + return null; + } } |