Painting principle

Android drawing is mainly done by CPU and GPU combined with refresh mechanism

  • CPU: calculates the display content by implementing measures and layout
  • Gpu: Responsible for rasterization (drawing UI elements on the screen)

Drawing process using SKIA library (2D), the essence of the hardware is to use openGL library to draw

Render once within 16ms, otherwise frame will drop

Layout loading principle

The layout loading entry in Android is setContentView(), which is analyzed as follows:

 @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
    
    //AppCompatDelegate.java
    public abstract void setContentView(@LayoutRes int resId);
Copy the code

View abstract interface implementations

 @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        // Get content and layout
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        // Remove all layouts
        contentParent.removeAllViews();
        // Load the new layout
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        // Interface status notification
        mOriginalWindowCallback.onContentChanged();
    }
Copy the code

Enter the inflate method:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        returninflate(resource, root, root ! =null);
    }
    
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally{ parser.close(); }}Copy the code

The getLayout method returns an XmlResourceParser object:

    public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
        return loadXmlResourceParser(id, "layout");
    }
    
    @NonNull
    XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
            throws NotFoundException {
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValue(id, value, true);
            if (value.type == TypedValue.TYPE_STRING) {
                return impl.loadXmlResourceParser(value.string.toString(), id,
                        value.assetCookie, type);
            }
            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
        } finally{ releaseTempTypedValue(value); }}Copy the code

Enter the loadXmlResourceParser:

 @NonNull
    XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
            @NonNull String type)
            throws NotFoundException {
        if(id ! =0) {
            try {
                synchronized (mCachedXmlBlocks) {
                    final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
                    final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
                    final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
                    // First see if this block is in our cache.
                    final int num = cachedXmlBlockFiles.length;
                    for (int i = 0; i < num; i++) {
                        if(cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] ! =null
                                && cachedXmlBlockFiles[i].equals(file)) {
                            returncachedXmlBlocks[i].newParser(); }}// Not in the cache, create a new block and put it at
                    // the next slot in the cache.
                    final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
                    if(block ! =null) {
                        final int pos = (mLastCachedXmlBlockIndex + 1) % num;
                        mLastCachedXmlBlockIndex = pos;
                        final XmlBlock oldBlock = cachedXmlBlocks[pos];
                        if(oldBlock ! =null) {
                            oldBlock.close();
                        }
                        cachedXmlBlockCookies[pos] = assetCookie;
                        cachedXmlBlockFiles[pos] = file;
                        cachedXmlBlocks[pos] = block;
                        returnblock.newParser(); }}}catch (Exception e) {
                final NotFoundException rnf = new NotFoundException("File " + file
                        + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
                rnf.initCause(e);
                throwrnf; }}throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
                + Integer.toHexString(id));
    }
Copy the code

Load the XML of the specified layout file and generate an XMLBlock:

 /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName)
        throws IOException {
        synchronized (this) {
            if(! mOpen) {throw new RuntimeException("Assetmanager has been closed");
            }
            long xmlBlock = openXmlAssetNative(cookie, fileName);
            if(xmlBlock ! =0) {
                XmlBlock res = new XmlBlock(this, xmlBlock);
                incRefsLocked(res.hashCode());
                returnres; }}throw new FileNotFoundException("Asset XML file: " + fileName);
    }
    
    private native final long openXmlAssetNative(int cookie, String fileName);
Copy the code

It eventually leads to native methods

After obtaining the XMLResourceParser, render:

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            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!");
                }

                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("* * * * * * * * * * * * * * * * * * * * * * * * * *");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("* * * * * * * * * * * * * * * * * * * * * * * * * *");
                }
                // If it is the merge tag, check whether it is the parent of the current layout, otherwise throw an exception
                if (TAG_MERGE.equals(name)) {
                    if (root == null| |! attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    // Get the root node in the XML
                    finalView temp = createViewFromTag(root, name, inflaterContext, attrs); }... }}}Copy the code

Use XmlPull to parse the layout. If it is a merge tag and the merge node is not the parent of the current layout, throw an exception and go to CreateViewFromTag:

  private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }
    
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {...try {
            View view;
            if(mFactory2 ! =null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if(mFactory ! =null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null&& mPrivateFactory ! =null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('. ')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs); }}finally {
                    mConstructorArgs[0] = lastContext; }}... }}Copy the code

Use mFactory2, mFactory, and mPrivateFactory to create a view, and finally call createView. Use reflection internally to create a node. Too much reflection can cause performance problems and can be optimized.

Obtaining the interface layout time

  1. Manual buried point, print time
  2. AOP prints the time of setContView
  3. Rewrite LayoutInflaterCompat setFactory2 method, print each control takes time
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {

    / / using LayoutInflaterCompat Factory2 global monitor each Activity interface controls loading time consuming,
    // You can also do global custom control replacement, such as: TextView global replacement with a custom TextView.
    LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
        @Override
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

            if (TextUtils.equals(name, "TextView")) {
                // Generate a custom TextView
            }
            long time = System.currentTimeMillis();
            / / 1
            View view = getDelegate().createView(parent, name, context, attrs);
            LogHelper.i(name + " cost " + (System.currentTimeMillis() - time));
            return view;
        }

        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            return null; }});// You can also call this method directly
// LayoutInflater.from(this).setFactory2(new LayoutInflater.Factory2() {
// @Override
// public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//
// if (TextUtils.equals(name, "TextView")) {
// // Generate a custom TextView
/ /}
// long time = System.currentTimeMillis();
/ / / / 1
// View view = getDelegate().createView(parent, name, context, attrs);
// AppLog.E(name + " cost " + (System.currentTimeMillis() - time));
// return view;
/ /}
//
// @Override
// public View onCreateView(String name, Context context, AttributeSet attrs) {
// return null;
/ /}
/ /});

    // 2. SetFactory2 must be called before the super.onCreate method
    super.onCreate(savedInstanceState);
    setContentView(getLayoutId());
    unBinder = ButterKnife.bind(this);
    mActivity = this;
    ActivityCollector.getInstance().addActivity(this);
    onViewCreated();
    initToolbar();
    initEventAndData();
}

Copy the code

Optimization tools

Lint

Android Studio comes with tools to verify code and find structure/quality issues

Layout Inspector

Android Studio recommends a layout detection tool that looks at the hierarchy of the entire layout to optimize processing

GPU overdrawing

Open developer option on mobile – Enable GPU overdrawing

The necessity of layout optimization

  1. Reduce page stutter and improve fluency
  2. Reduced online bug output

General principles

  1. Avoid nesting between layers
  2. Reduced drawing time, execution time of three methods

An optimization method

  1. Reuse common layouts using include tags

  2. Use merge to reduce view hierarchy

    When merge is used, two consecutive similar layouts are merged, reducing levels.

  3. Use the viewStub to delay loading and reduce resource waste

  4. Use LinearLayout for simple layouts and RelativeLayout or ConstraintLayout to reduce the level of nesting for complex layouts.

  5. Make use of control properties

    • TextView picture + text display
    • Use the separators that come with the LinearLayout
  6. Using the Space control

  7. Use wrAP_content as little as possible, which increases the computational cost and takes too long to draw

Main layout comparison

The name of the advantages disadvantages
RelativeLayout Reduce hierarchy nesting OnDraw executes twice, taking time
LinearLayout Instead of using weight, onDraw is executed once Easy hierarchy nesting in layout
FrameLayout
ConstraintLayout Reduce hierarchy + scale layout Time consuming

Summary: The FrameLayout and LinearLayout functions are complex and require hierarchical nesting using a RelativeLayout or ConstraintLayout.

Prioritize hierarchy issues over individual layout performance issues