summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanny Lin <danny@kdrag0n.dev>2021-04-06 15:30:33 -0700
committeralk3pInjection <webmaster@raspii.tech>2022-05-05 00:39:05 +0800
commitca7686196c036d107c3b7595ffe12726ab21f7b3 (patch)
tree6e428fe61008dbea78193877f33f1a562e6c535a
parent892d60a895f877a3036d8dfd7c58f6afcf01d6cb (diff)
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.java217
1 files changed, 171 insertions, 46 deletions
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index df78827534a6..cd2ea4311264 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -804,67 +804,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)
@@ -1363,4 +1371,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;
+ }
}