1. 程式人生 > 實用技巧 >Android 效能優化---佈局優化

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()避免建立大物件,耗時操作