Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 928bbac9 authored by Eric Holk's avatar Eric Holk
Browse files

[LayoutInflater] Use precompiled layouts if available

This change enables the use of precompiled layouts, provided a couple of
conditions are met:

1. Precompiled layouts are enabled by the system property
   view.use_precompiled_layouts.
2. There is a file called compiled_view.dex in the application's code cache
   directory.

If these conditions are met, when a layout is inflated, the LayoutInflater will
first check if a precompiled version is available and use that. If anything goes
wrong, such as if the layout is not available or something goes wrong during the
inflation process, then the LayoutInflater will fall back on interpretting the
layout resource as before.

Bug: 111895153
Test: atest $ANDROID_BUILD_TOP/cts/tests/tests/view/src/android/view/cts/LayoutInflaterTest.java
Change-Id: Id050072c0206080322a0e876782ee2b66d03916d
parent 06eb53ce
Loading
Loading
Loading
Loading
+182 −79
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.content.res.XmlResourceParser;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
@@ -37,6 +38,9 @@ import android.widget.FrameLayout;

import com.android.internal.R;

import dalvik.system.PathClassLoader;
import java.io.File;
import java.lang.reflect.Method;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

@@ -71,6 +75,10 @@ public abstract class LayoutInflater {
    private static final String TAG = LayoutInflater.class.getSimpleName();
    private static final boolean DEBUG = false;

    private static final String USE_PRECOMPILED_LAYOUT_SYSTEM_PROPERTY
        = "view.precompiled_layout_enabled";
    private static final String COMPILED_VIEW_DEX_FILE_NAME = "/compiled_view.dex";

    /** Empty stack trace used to avoid log spam in re-throw exceptions. */
    private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];

@@ -92,6 +100,13 @@ public abstract class LayoutInflater {
    private Factory2 mPrivateFactory;
    private Filter mFilter;

    // Indicates whether we should try to inflate layouts using a precompiled layout instead of
    // inflating from the XML resource.
    private boolean mUseCompiledView;
    // This variable holds the classloader that will be used to look for precompiled layouts. The
    // The classloader includes the generated compiled_view.dex file.
    private ClassLoader mPrecompiledClassLoader;

    @UnsupportedAppUsage
    final Object[] mConstructorArgs = new Object[2];

@@ -214,6 +229,7 @@ public abstract class LayoutInflater {
     */
    protected LayoutInflater(Context context) {
        mContext = context;
        initPrecompiledViews();
    }

    /**
@@ -230,6 +246,7 @@ public abstract class LayoutInflater {
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        setFilter(original.mFilter);
        initPrecompiledViews();
    }

    /**
@@ -371,6 +388,29 @@ public abstract class LayoutInflater {
        }
    }

    private void initPrecompiledViews() {
        try {
            mUseCompiledView =
                SystemProperties.getBoolean(USE_PRECOMPILED_LAYOUT_SYSTEM_PROPERTY, false);
            if (mUseCompiledView) {
                mPrecompiledClassLoader = mContext.getClassLoader();
                String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME;
                if (new File(dexFile).exists()) {
                    mPrecompiledClassLoader = new PathClassLoader(dexFile, mPrecompiledClassLoader);
                } else {
                    // If the precompiled layout file doesn't exist, then disable precompiled
                    // layouts.
                    mUseCompiledView = false;
                }
            }
        } catch (Throwable e) {
            if (DEBUG) {
                Log.e(TAG, "Failed to initialized precompiled views layouts", e);
            }
            mUseCompiledView = false;
        }
    }

    /**
     * Inflate a new view hierarchy from the specified xml resource. Throws
     * {@link InflateException} if there is an error.
@@ -430,7 +470,11 @@ public abstract class LayoutInflater {
                  + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
@@ -438,6 +482,73 @@ public abstract class LayoutInflater {
        }
    }

    private @Nullable
    View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
        boolean attachToRoot) {
        if (!mUseCompiledView) {
            return null;
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");

        // Try to inflate using a precompiled layout.
        String pkg = res.getResourcePackageName(resource);
        String layout = res.getResourceEntryName(resource);

        try {
            Class clazz = mPrecompiledClassLoader.loadClass("" + pkg + ".CompiledView");
            Method inflater = clazz.getMethod(layout, Context.class, int.class);
            View view = (View) inflater.invoke(null, mContext, resource);

            if (view != null && root != null) {
                // We were able to use the precompiled inflater, but now we need to do some work to
                // attach the view to the root correctly.
                XmlResourceParser parser = res.getLayout(resource);
                try {
                    AttributeSet attrs = Xml.asAttributeSet(parser);
                    advanceToRootNode(parser);
                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);

                    if (attachToRoot) {
                        root.addView(view, params);
                    } else {
                        view.setLayoutParams(params);
                    }
                } finally {
                    parser.close();
                }
            }

            return view;
        } catch (Throwable e) {
            if (DEBUG) {
                Log.e(TAG, "Failed to use precompiled view", e);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        return null;
    }

    /**
     * Advances the given parser to the first START_TAG. Throws InflateException if no start tag is
     * found.
     */
    private void advanceToRootNode(XmlPullParser parser)
        throws InflateException, IOException, XmlPullParserException {
        // Look for the root node.
        int type;
        while ((type = parser.next()) != XmlPullParser.START_TAG &&
            type != XmlPullParser.END_DOCUMENT) {
            // Empty
        }

        if (type != XmlPullParser.START_TAG) {
            throw new InflateException(parser.getPositionDescription()
                + ": No start tag found!");
        }
    }

    /**
     * Inflate a new view hierarchy from the specified XML node. Throws
     * {@link InflateException} if there is an error.
@@ -471,18 +582,7 @@ public abstract class LayoutInflater {
            View result = root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                advanceToRootNode(parser);
                final String name = parser.getName();

                if (DEBUG) {
@@ -985,6 +1085,9 @@ public abstract class LayoutInflater {
                + "reference. The layout ID " + value + " is not valid.");
        }

        final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
            (ViewGroup) parent, /*attachToRoot=*/true);
        if (precompiled == null) {
            final XmlResourceParser childParser = context.getResources().getLayout(layout);

            try {
@@ -1060,7 +1163,7 @@ public abstract class LayoutInflater {
            } finally {
                childParser.close();
            }

        }
        LayoutInflater.consumeChildElements(parser);
    }