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()避免創建大對象,耗時操作