Android 性能優化---布局優化


Android 性能優化---布局優化

Android 布局繪制原理

布局加載過程

setContentView() --> inflate() -- > getLayout()(I/O操作) --> createViewFromTag() --> mFactory2/mFactory -- > onCreateView()(反射)

先看看源碼
從setContentView(R.layout.activity_main);進入
public void setContentView(int resId) {
this.ensureSubDecor();
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}

接着進入到inflate();
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();
    }
}

這里主要是通過getLayout來獲取到XmlResourceParser ,而這里面是進行大量的I/O操作
繼續進入到inflate();這里方法比較多,就拿關鍵部分代碼

這里面有一個createViewFromTag();根據命名可以知道,應該是根據標簽來創建對應的view

...
//省略若干
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;
                }
            }

            return view;
            ...
//省略若干

從這里面可以知道主要是通過mFactory2 或者mFactory來創建,進入onCreateView();

....//省略若干
public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(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 = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, 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 = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);

                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }
....//省略若干

從這里就可以知道這里創建View的時候,是通過反射來進行的。
上面大概的流程,出現了兩個可以優化的點,一個是I/O操作,一個是反射,如果一個布局文件嵌套很深,很復雜,這兩個操作是相當的耗時的。那具體怎么優化呢,我們后面會說到。

CPU

CPU主要是負責計算顯示的內容

GPU

GPU主要是負責柵格化,(UI元素繪制到屏幕上)

常用的優化工具

Systrace

systrace的功能包括跟蹤系統的I/O操作、內核工作隊列、CPU負載以及Android各個子系統的運行狀況等。在Android平台中,它主要由3部分組成:

內核部分:Systrace利用了Linux Kernel中的ftrace功能。所以,如果要使用systrace的話,必須開啟kernel中和ftrace相關的模塊。

數據采集部分:Android定義了一個Trace類。應用程序可利用該類把統計信息輸出給ftrace。同時,Android還有一個atrace程序,它可以從ftrace中讀取統計信息然后交給數據分析工具來處理。

數據分析工具:Android提供一個systrace.py(python腳本文件,位於Android SDK目錄/sdk/platform-tools/systrace中,其內部將調用atrace程序)用來配置數據采集的方式(如采集數據的標簽、輸出文件名等)和收集ftrace統計數據並生成一個結果網頁文件供用戶查看。

簡單來說,當機器以60幀/秒顯示(也就是16.6 ms),用戶會感覺機器會流暢。如果出現顯示時出現丟幀的情況,就需要知道系統在做什么?

Systrace 是用來收集系統和應用的數據信息和一些中間生成數據的細節,在Android 4.1系統和4.1之后的系統。
Systrace在一些分析顯示的問題上特別有用,如應用畫圖慢,顯示動作或動畫時變形。

Systrace的使用

很多網上的文章說通過Tools -> Android -> Android Device Monitor 來打開這個Systrace,我沒有具體去了解,Android studio從哪個版本開始,已經沒法從Tools中打開了,我是通過以下方式打開的:
找到自己的SDK文件夾,可以看AS中的SDK路徑

接着

接着

接着

打開即可

然后選擇對應的進程

生成對應的html文件即可
生成對應的html文件在goole瀏覽器打開

Systrace 可以分析內存,卡頓,界面耗時等情況,這里主要說一下界面耗時分析方式
基本操作:
'w'按鍵:放大
's'按鍵:縮小
'd'按鍵:向右平移
'a'按鍵:向左平移
‘m’按鈕,查看對應的控件渲染耗時時間

顏色區分:綠色表示正常,紅色或者黃色表示丟幀,需要我們去優化,而出現紅色或者黃色,則可以通過Alerts來查看詳情。

Layout Inspector

布局的嵌套,我們可以通過Layout Inspector來進行查看
在Android studio中通過Tools ---> Layout Inspector進行打開

可以通過View Tree來查看布局的嵌套情況。

獲取界面布局耗時

常規方式,手動埋點

這種方式比較簡單,就是在setContentView();前后添加日志

AOP/ArtHook方式

ARTHook這種方式在前面啟動優化已經介紹過了,可以回頭看看 [https://www.cnblogs.com/huangjialin/p/13292042.html]

布局優化方式

AsyncLayoutInflater

AsyncLayoutInflater 這是谷歌提供的一個異步加載布局的一個類,使用這個方法,只是一個緩解,並沒有從根本上解決耗時問題。
具體的,大家可以去深入了解
缺陷:
1、不能設置LayoutInflater.Factory()
2、注意View中不能有依賴主線程的操作。
其實我個人認為,這種方式並不好,由於是異步加載布局,那么如果主線程中或者在onResume();方法中有用到某個控件,那都是會出問題。(如果大家有什么好的想法,也可以評論)

X2C 框架

前面我們看了布局的加載過程可以知道,解析布局出現耗時,是由於布局過於復雜,導致有大量的I/O操作和反射操作,這是兩個耗時的點。那么如果不直接在java文件寫布局呢,是不是就避免了這些I/O和反射操作呢?可以的,但是在java文件上寫布局,又帶來一個問題,就是可維護性問題。
下面介紹這個框架,就是可以解決上面兩個問題的
X2C框架
1、保留XML的優點,解決了其性能問題
2、原理:APT編譯期翻譯XML為java代碼
使用方式
添加依賴

dependencies {
    annotationProcessor 'com.zhangyue.we:x2c-apt:1.1.2'
    implementation 'com.zhangyue.we:x2c-lib:1.0.6'
}

在對應的activity中添加注解

將onCreate();方法中的setContentView(R.layout.activity_test);更換為X2C.setContentView(TestActivity.this, R.layout.activity_test);

編譯后可以在build -- generated -- ap_generated_sources -- debug -- 中查看生產對應的文件。
這個框架可以在編譯期間幫我們將布局xml文件動態生成java文件,避免了前面說的解析布局文件出現的I/O和反射導致耗時的問題。

X2C框架:https://github.com/iReaderAndroid/X2C/blob/master/README_CN.md

布局優化層級及復雜度

1、使用ConstraintLayout布局,減少層級嵌套。
2、不嵌套使用RelativeLayout,RelativeLayout和LinearLayout相比,LinearLayout性能更好,由於RelativeLayout是相對布局,確定位置的需要測量兩次,性能較差。
3、不在嵌套LinearLayout中使用weight,LinearLayout中使用weight在onMeasure階段會繪制兩次,嵌套中使用,性能更差。
4、使用meger標簽減少層級

避免過度繪制

1、去掉多余背景色,減少復雜shape使用
2、避免層級疊加

其他

1、viewstub:高效占位符,延遲初始化
2、onDraw()避免創建大對象,耗時操作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM