Android page rendering efficiency optimization practice

1. Current status of car page layout rendering

The car series page is an important car series information page. It has been updated and iterated for many years, the page layout is constantly changing, and the xml layout file is getting more and more complicated.

It takes time to obtain the layout file of the car series page:

        startTime = System.currentTimeMillis();
        setContentView(R.layout.car_series_revision_activity);
        long durTime = System.currentTimeMillis() - startTime;
        LogHelper.e("布局总耗时","车系页布局耗时:" + durTime);

The result is as follows:

picture

2. Causes of Caton

2.1

Android drawing principle

► 1. The three most important concepts involved in Android screen refresh

(1) CPU: Execute the measure, layout, draw and other operations of the application layer, and submit the data to the GPU after the drawing is completed

(2) GPU: further process the data and cache the data

(3) Screen: It is composed of pixels, and the data is taken out from the buffer to fill the pixels at a fixed frequency (16.6ms, that is, 60 frames per second).

To sum it up in one sentence: the CPU submits the data after drawing, the GPU further processes and caches the data, and finally the screen reads the data from the buffer and displays it.

picture

► 2. Double buffer mechanism

picture

When the layout is complex or the performance of the device is poor, the CPU cannot guarantee that the calculation of the drawing data will be completed within 16.6ms, so the system does another processing here.

When your application is filling data into the Back Buffer, the system will lock the Back Buffer.

If your application is still filling data into the Back Buffer when the GPU exchanges two Buffers, the GPU will find that the Back Buffer is locked, and it will give up the exchange.

The consequence of this is that the screen of the mobile phone still displays the original image, which is what we often call frame drop.

2.2

Layout loading principle

When the page is started, the layout loading is time-consuming on the main thread, which will lead to slow page rendering and loading.

Layout loading is mainly realized through setContentView, and the following is its calling sequence diagram:

picture

We can see that there are two main time-consuming operations in setContentView:

(1) Parse xml and get XmlResourceParser, which is an IO process.

(2) Create a View object through createViewFromTag, using reflection.

The above two points are the reason for the slow loading of the layout and the performance bottleneck of the layout.

3. Layout loading optimization

The previous chapter analyzed the main reasons for the slow loading of layouts. Therefore, our optimization methods mainly include the following two:

(1) Asynchronous loading, transfer the layout loading process to the child thread

(2) Remove IO and reflection process

3.1

Asynchronous loading, AsyncLayoutInflater solution

setContentView loads the layout on the UI main thread by default. Time-consuming operations during the loading process, such as parsing xml and creating view objects through reflection, are also executed on the main thread. AsyncLayoutInflater allows these loading processes to be executed in sub-threads, which can improve The responsiveness of the UI thread, the UI thread can perform other operations at the same time. AsyncLayoutInflater is used as follows:

new AsyncLayoutInflater(this).inflate(R.layout.car_series_revision_activity, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
            @Override
            public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
                setContentView(view);
            }
        });

Disadvantages of the AsyncLayoutInflater solution:

(1) The initialization of UI layout and view is carried out in the child thread. If the view has not been initialized successfully, calling the view in the main thread will cause a crash.

(2) Under normal circumstances, the main thread will call the view, which involves the synchronization of a large number of sub-threads and the main thread on the call of the view, which sacrifices ease of use and deteriorates the maintainability of the code.

(3) If AsyncLayoutInflater is introduced into the logical structure of the old page for transformation, the structure will change a lot, and view call crash errors will easily occur, which is not feasible.

3.2

X2C scheme

X2C is an open source layout loading framework. The main idea of ​​X2C is to use the apt tool to parse the xml layout file we wrote into a view during compilation, and dynamically set various attributes of the view according to the xml. In this way, we call findViewById at runtime, and get it according to the view id The view is already a directly new view, which avoids the xml IO operation and reflection operation at runtime, which solves the time-consuming problem of layout.

Original xml layout file:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/constraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <Button
        android:id="@+id/x2c"
        style="@style/btn"
        android:text="X2C" />
    <Button
        android:id="@+id/xml"
        style="@style/btn"
        android:layout_marginTop="10dp"
        android:text="XML" />
    <Button
        android:id="@+id/sub"
        style="@style/btn"
        android:layout_marginTop="10dp"
        android:text="subModule" />
</LinearLayout>

The java file generated by apt during X2C compilation:

public class X2C127_Activity implements IViewCreator {
  @Override
  public View createView(Context ctx) {
     Resources res = ctx.getResources();
        LinearLayout linearLayout0 = new LinearLayout(ctx);
        linearLayout0.setTag(R.id.x2c_rootview_width,ViewGroup.LayoutParams.MATCH_PARENT);
        linearLayout0.setTag(R.id.x2c_rootview_height,ViewGroup.LayoutParams.MATCH_PARENT);
        linearLayout0.setId(R.id.constraintLayout);
        linearLayout0.setGravity(Gravity.CENTER);
        linearLayout0.setOrientation(LinearLayout.VERTICAL);
        Button button1 = new Button(ctx);
        LinearLayout.LayoutParams layoutParam1 = new LinearLayout.LayoutParams((int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,150,res.getDisplayMetrics())),(int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,50,res.getDisplayMetrics())));
        button1.setBackgroundColor(res.getColor(R.color.colorAccent));
        button1.setTextSize(TypedValue.COMPLEX_UNIT_DIP,20);
        button1.setGravity(Gravity.CENTER);
        button1.setTextColor(Color.parseColor("#ffffff"));
        button1.setId(R.id.x2c);
        button1.setText("X2C");
        button1.setLayoutParams(layoutParam1);
        linearLayout0.addView(button1);
        return linearLayout0;
  }
}

Advantages of X2c:

(1) Good usability and maintainability, less intrusive to the original code, the application code still uses xml to write the layout

(2) The loading time can be shortened to 1/2 to 1/3 of the original

Disadvantages of X2c:

(1) The attribute support of View is not complete

(2) Compatibility and stability are not very high. In high-version gradle compilation tools, such as gradle3.1.4, there will be problems such as the R.java file cannot be found, and the java file corresponding to xml cannot be found.

(3) At present, X2C is updated to 2021, and there is no continuous maintenance and issue resolution

3.3

Compose scheme

Compose is a new member of Jetpack, a new UI library announced by the Android team at the 2019 I/O conference.

Compose is developed using pure kotlin, which is simple and convenient to use, but it completely abandons the View and ViewGroup system, and makes the entire rendering mechanism from the inside to the outside. It is the official solution to replace XML in the future.

Advantages of Compose:

(1) Using a declarative UI, abandoning the runtime parsing of the xml layout, the layout is more efficient

(2) Developed with kotlin, it is easy to use, and the layout is unified with flutter.

If it is a new project developed using kotlin, the Compose solution can be introduced. For the optimization of old projects, the Compose solution is not applicable.

3.4

Our optimization plan - making a fuss about layout reflection

Parsing Xml to view is done by yourself, which is complicated and has many risks. This process involves two time-consuming points:

(1) XML parsing, IO operation

(2) reflection

The work of xml parsing is very complicated and can be handed over to the android system. We can find a way to remove the logic of reflection.

We need to find an entry point for the reflection generated view. We know that View generation-related logic is in LayoutInflater's createViewFromTag, calling onCreateView(parent, name, context, attrs), and generating view through reflection.

Through the LayoutInflater setFactory of the android system, we can not only control the generation of the View, but also turn the View into another View. In the onCreateView(parent, name, context, attrs) callback of setFactory, we take over the generation of a single view, remove the reflection, and create our own new view to solve the problem. The parameter name in onCreateView(parent, name, context, attrs) returns the name of the view used in xml. According to this name, a new view is directly created. The way is as follows:

        LayoutInflaterCompat.setFactory(getLayoutInflater(), new LayoutInflaterFactory() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                switch (name) {
                    case "TextView":
                        return new TextView(context, attrs);
                    case "ImageView":
                        return new ImageView(context, attrs);
                    case "com.cubic.choosecar.ui.car.view.NewStarView":
                        return new com.cubic.choosecar.ui.car.view.NewStarView(context, attrs);
                    case "com.cubic.choosecar.ui.carseries.scrolllayout.ScrollableLayout":
                        return new com.cubic.choosecar.ui.carseries.scrolllayout.ScrollableLayout(context, attrs);
                    case "View":
                        return new View(context, attrs);
                    case "com.cubic.choosecar.newui.carseries.view.CarRevisionSeriesImageScaleLayout": //自定义view
                        return new com.cubic.choosecar.newui.carseries.view.CarRevisionSeriesImageScaleLayout(context, attrs);
                    case "ViewStub":
                        return new ViewStub(context, attrs);
                    case "ScrollView":
                        return new ScrollView(context, attrs);
                    case "androidx.constraintlayout.widget.ConstraintLayout":
                        return new androidx.constraintlayout.widget.ConstraintLayout(context, attrs);
                    case "FrameLayout":
                        return new FrameLayout(context, attrs);
                    case "RelativeLayout":
                        return new RelativeLayout(context, attrs);
                    case "androidx.appcompat.widget.Toolbar":
                        return new androidx.appcompat.widget.Toolbar(context, attrs);
                    case "LinearLayout":
                        return new LinearLayout(context, attrs);
                    default:
                        View view = getDelegate().createView(parent, name, context, attrs);
                        return view;
                }
                //return view;
            }
        });

Including system view and our custom view.

This solution is less intrusive to the code of existing projects, the cost of transformation is low, and the compatibility is also high. Relatively speaking, the rendering efficiency is lower than that of the X2C solution, but it matches our rendering of the complex layout of existing old projects. optimization.

3.5

Further optimize the layout

We can use viewStub to implement lazy loading of the layout. The idea is to divide the layout into different modules, and replace some modules with viewStub tags. After half of the screen's module elements are rendered, viewStub is used to render and generate other modules contained in viewStub to achieve delayed rendering and loading.

By analyzing the layout of the car series page, the layout elements have been divided into some modules according to their functions. We further gather the layout modules with high correlation, encapsulate them in a custom VIEW, and use viewStub to contain and replace these modules View. When the UI thread setContentView renders the layout, the modules contained in the viewStub will not be rendered, but only some elements of the screen will be rendered, waiting for the main interface data to return, and then use the viewStub to delay other modules, realizing lazy loading of the layout and speeding up the main thread rendering speed.

4. Optimize the results

Through the optimization methods in sections 3.4 and 3.5, the comparison results of the complex layout rendering optimization of the car series page are as follows:

picture

Through comparison, we can see that on different grades of Android models, the rendering time is reduced by about 20%-35%. On low-end models, the reduction is definitely more time-consuming, and the feeling may be more obvious.

at last

Now it is said that the Internet is in a cold winter. In fact, it is nothing more than that you get on the wrong car and wear less (skills). It's just a business curd that eliminates the end! Nowadays, there are a lot of junior programmers in the market. This set of tutorials is aimed at Android development engineers who have been in the bottleneck period for 1-6 years. Those who want to break through their own salary increase after the next year, advanced Android intermediate and senior architects are especially helpful for you. Feel like a duck to water, hurry up and get it!

Scan the QR code below to get it for free

The reason why some people are always better than you is because he is very good himself and has been working hard to become better, but are you still satisfied with the status quo and secretly happy in your heart!

The road to Android architects is very long, let's encourage each other!

Guess you like

Origin blog.csdn.net/m0_56255097/article/details/130176216