Android頁面渲染效率優化實踐

来源:https://www.cnblogs.com/88223100/archive/2023/04/15/Optimization-Practice-for-Android-Page-Rendering-Efficiency.html
-Advertisement-
Play Games

1.車系頁佈局渲染現狀 車系頁是重要的車系信息頁面,更新迭代多年,頁面佈局不斷變化,xml佈局文件越寫越複雜。 獲取車系頁佈局文件耗時: startTime = System.currentTimeMillis(); setContentView(R.layout.car_series_revisi ...


 

1.車系頁佈局渲染現狀 

車系頁是重要的車系信息頁面,更新迭代多年,頁面佈局不斷變化,xml佈局文件越寫越複雜。

獲取車系頁佈局文件耗時:

        startTime = System.currentTimeMillis();
        setContentView(R.layout.car_series_revision_activity);
        long durTime = System.currentTimeMillis() - startTime;
        LogHelper.e("佈局總耗時","車系頁佈局耗時:" + durTime);

結果如下:

圖片

 

2.卡頓的原因

2.1

Android繪製原理

► 1.Android的屏幕刷新中涉及到最重要的三個概念

(1)CPU:執行應用層的measure、layout、draw等操作,繪製完成後將數據提交給GPU

(2)GPU:進一步處理數據,並將數據緩存起來

(3)屏幕:由一個個像素點組成,以固定的頻率(16.6ms,即1秒60幀)從緩衝區中取出數據來填充像素點

總結一句話就是:CPU 繪製後提交數據、GPU 進一步處理和緩存數據、最後屏幕從緩衝區中讀取數據並顯示。

圖片

► 2.雙緩衝機制

圖片

當佈局比較複雜,或設備性能較差的時候,CPU並不能保證在16.6ms內就完成繪製數據的計算,所以這裡系統又做了一個處理。

當你的應用正在往Back Buffer中填充數據時,系統會將Back Buffer鎖定。

如果到了GPU交換兩個Buffer的時間點,你的應用還在往Back Buffer中填充數據,GPU會發現Back Buffer被鎖定了,它會放棄這次交換。

這樣做的後果就是手機屏幕仍然顯示原先的圖像,這就是我們常常說的掉幀。

2.2

佈局載入原理

頁面啟動時,佈局載入在主線程上進行耗時操作,會導致頁面渲染及載入慢。

佈局載入主要通過setContentView來實現,下麵是它的調用時序圖:

圖片

我們可以看到,在setContentView中主要有兩個耗時操作:

(1)解析xml,獲取XmlResourceParser,這是IO過程。

(2)通過createViewFromTag,創建View對象,用到了反射。

以上兩點就是佈局載入慢的原因,也是佈局的性能瓶頸。

 

3.佈局載入優化

上一章分析了佈局載入慢的主要原因,因此,我們的優化方式主要有以下兩種:

(1)非同步載入,將佈局載入過程轉移到子線程

(2)去掉IO和反射過程

3.1

非同步載入,AsyncLayoutInflater方案

 setContentView 預設是在UI主線程載入佈局的,其載入過程中的耗時操作,如解析xml,反射創建view對象等也是在主線程執行,AsyncLayoutInflater 可以讓這些載入過程在子線程中執行,這樣可以提高UI線程的響應性,UI線程同時可以進行其他操作。AsyncLayoutInflater使用方式如下:

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);
            }
        });

AsyncLayoutInflater方案的缺點:

(1) UI佈局和view的初始化在子線程中進行,如果view還未初始化成功,在主線程中再調用view會引起崩潰。

(2) 一般情況下,主線程會調用view,涉及到大量子線程和主線程在view調用上的同步問題,這就犧牲了易用性,代碼可維護性也會變差。

(3) 如果是在老頁面邏輯結構上引入AsyncLayoutInflater進行改造,結構改動很大,很容易發生view調用崩潰錯誤,不太可行。

3.2

X2C方案

 X2C 是掌閱開源的一套佈局載入框架。X2C的主要思路是利用apt工具,在編譯期將我們寫的xml佈局文件解析成view,並根據xml動態設置view的各類屬性,這樣,我們在運行時,調用findViewById,根據view id拿到的view,已經是直接new 出來的view,避免了運行時的xml IO操作和反射操作,這就解決了佈局時的耗時問題。

原始的xml佈局文件:

<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>
X2C 編譯期apt生成的java文件:
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;
  }
}

X2c的優點:

(1)易用性和可維護性好,對原有代碼侵入性不強,應用代碼還是使用xml寫佈局

(2)載入耗時可縮短到原來的1/2到1/3

X2c的缺點:

(1)View的屬性支持不完全

(2)相容性和穩定性不是很高,在高版本的gradle 編譯工具,如gradle3.1.4,會出現找不到R.java文件,找不到xml對應的java文件等問題

(3)目前,X2C更新到2021年,並沒有持續維護和解決issue

3.3

Compose方案

Compose 是 Jetpack 中的一個新成員,是 Android 團隊在2019年I/O大會上公佈的新的UI庫。

Compose使用純kotlin開發,使用簡潔方便,但它是完全拋棄了View 和 ViewGroup這套系統,自己把整個的渲染機制從裡到外做了個全新的,是未來取代XML的官方方案。

Compose的優點:

(1)使用聲明式UI,摒棄了xml佈局運行時解析,佈局效率更高

(2)使用kotlin開發,簡單易用,佈局形式上跟flutter統一。

如果是使用kotlin開發的新項目,可以引入Compose方案,對於老項目的優化,Compose方案並不適用。

3.4

我們的優化方案-在佈局反射上做文章

 Xml解析到view,完全自己來做,比較複雜且有很多風險,這個過程涉及到兩個耗時的點:

(1)xml解析,IO操作

(2)反射

xml解析這部分工作複雜度很高,可以交給android系統來做。我們可以想辦法去除反射的邏輯。

我們需要找到一個反射生成view的入口。我們知道,View生成相關邏輯在LayoutInflater的createViewFromTag中,調用了onCreateView(parent, name, context, attrs),通過反射生成了view。

通過android系統的LayoutInflater setFactory,我們不僅可以控制View的生成,還可以把View變成另外一個View。在setFactory的onCreateView(parent, name, context, attrs)回調中,我們接管單個view的生成,去掉反射,new 出我們自己的view就解決了問題。而onCreateView(parent, name, context, attrs)中的參數name返回的就是xml中使用到的view的名字,根據這個name,直接new出來新的view。方式如下:

        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;
            }
        });

 

包括系統view和我們自定義的view。

此方案對已有項目的代碼侵入性很小,改造成本低,相容性也很高,相對來講,在渲染效率上比X2C方案低一些,但比較匹配我們對已有舊項目複雜佈局的渲染優化。

 

3.5

進一步在佈局上優化

 我們可以使用viewStub實現佈局的懶載入。思路是將佈局分成不同的模塊,讓部分模塊使用viewStub標簽替代,一半屏幕的模塊元素渲染完成以後,再通過viewStub來渲染生成viewStub所包含的其它模塊,實現延遲渲染載入。

通過分析車系頁佈局,已經將佈局元素,按功能做了一些模塊的劃分,我們進一步將關聯度大的佈局模塊集中在一起,封裝在一個自定義VIEW中,使用viewStub包含替換這些模塊View。UI線程setContentView渲染佈局時,viewStub所包含的模塊並不會被渲染,只會渲染屏幕的部分元素,等待主介面數據返回,再使用viewStub延遲其它模塊,實現了佈局的懶載入,加快了主線程的渲染速度。

 

4.優化結果

通過3.4和3.5節的優化方法,車系頁複雜佈局渲染優化對比結果如下:

圖片

通過對比可以看到,在不同檔次的android機型上,渲染耗時降低了20%-35%左右,在低端機型上,減少的絕對耗時更多,感受可能會明顯一些。

 

作者|蔣雄鋒

本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Optimization-Practice-for-Android-Page-Rendering-Efficiency.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • title: msp430點燈實驗 date: 2023-04-15 15:31:25 description: 基於msp430f5529點燈實驗 一、實驗內容 使用開發板:msp430f5529 使用的LED燈:為開發板上自帶的User LEDs(LED1、LED2) 環境:CCS (Versi ...
  • 一、 yum mysql5.7以下 mysql5.7以上 Centos8 可以,但是需要重新配置文件 可以,但是需要重新配置文件 可以,但是需要重新配置文件 Centos7 可以直接yum,但是是安裝mariadb-server。如果是mysql-server需要配置文件 直接yum後啟動就好 yu ...
  • 本文章來自我的微信個人技術公眾號 網路技術修煉,公眾號中總結普及網路基礎知識,包括基礎原理、網路方案、開發經驗和問題定位案例等,歡迎關註。 Linux網路開發者面臨的問題往往比較複雜,因此需要使用一些工具和命令來進行定位和解決。在本篇博客中,我將總結一些常用的Linux網路開發者工具和命令,包括網路 ...
  • 哈嘍大家好,我是鹹魚。今天跟大家分享一個關於正則表達式的案例,希望能夠對你有所幫助 案例現象 前幾天有一個小伙伴在群里求助,說他這個 shell 腳本有問題,讓大家幫忙看看 可以看到,這個腳本首先將目標文本文件的名字當作該腳本的第一個參數($1)傳遞進去,然後查看這個文本文件的內容(cat $1), ...
  • 簡介:本文主要介紹ubuntu20.04容器中搭建xfce遠程桌面、C++、Go環境、容器內docker操作配置、zsh配置 一、創建容器 1、創建容器 docker pull ubuntu:20.04docker run -itd --privileged --name=my-desktop--u ...
  • Redis入門 1.初始Redis 1.1認識NoSQL | | SQL(關係型資料庫) | NoSQL(非關係型資料庫) | | | | | | 數據結構 | 結構化(Structured) | 非結構化 | | 數據關聯 | 關聯的(Relational) | 無關聯的 | | 查詢方式 | S ...
  • 1.背景描述 2020年團隊決定對elasticsearch升級。es(elasticsearch縮寫,下同)當前版本為0.9x,升級到5.x版本。es在本公司承載三個部分的業務,站內查詢,訂單數據統計,elk日誌分析。 對於站內查詢和訂單數據統計,當前業務架構是 mysql -> canal -> ...
  • Mysql 中,為什麼 WHERE 使用別名會報錯,而 ORDER BY 不會報錯? 我們先對salary * 12 命名一個別名annual_sal SELECT employee_id,salary,salary * 12 annual_sal FROM employees ORDER BY a ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...