自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android頁(yè)面渲染效率優(yōu)化實(shí)踐

移動(dòng)開發(fā) Android
如果到了GPU交換兩個(gè)Buffer的時(shí)間點(diǎn),你的應(yīng)用還在往Back Buffer中填充數(shù)據(jù),GPU會(huì)發(fā)現(xiàn)Back Buffer被鎖定了,它會(huì)放棄這次交換。

?1.車系頁(yè)布局渲染現(xiàn)狀 

車系頁(yè)是重要的車系信息頁(yè)面,更新迭代多年,頁(yè)面布局不斷變化,xml布局文件越寫越復(fù)雜。

獲取車系頁(yè)布局文件耗時(shí):

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

結(jié)果如下:

圖片

2.卡頓的原因

2.1Android繪制原理

? 1.Android的屏幕刷新中涉及到最重要的三個(gè)概念

(1)CPU:執(zhí)行應(yīng)用層的measure、layout、draw等操作,繪制完成后將數(shù)據(jù)提交給GPU

(2)GPU:進(jìn)一步處理數(shù)據(jù),并將數(shù)據(jù)緩存起來(lái)

(3)屏幕:由一個(gè)個(gè)像素點(diǎn)組成,以固定的頻率(16.6ms,即1秒60幀)從緩沖區(qū)中取出數(shù)據(jù)來(lái)填充像素點(diǎn)

總結(jié)一句話就是:CPU 繪制后提交數(shù)據(jù)、GPU 進(jìn)一步處理和緩存數(shù)據(jù)、最后屏幕從緩沖區(qū)中讀取數(shù)據(jù)并顯示。

圖片

? 2.雙緩沖機(jī)制

圖片

當(dāng)布局比較復(fù)雜,或設(shè)備性能較差的時(shí)候,CPU并不能保證在16.6ms內(nèi)就完成繪制數(shù)據(jù)的計(jì)算,所以這里系統(tǒng)又做了一個(gè)處理。

當(dāng)你的應(yīng)用正在往Back Buffer中填充數(shù)據(jù)時(shí),系統(tǒng)會(huì)將Back Buffer鎖定。

如果到了GPU交換兩個(gè)Buffer的時(shí)間點(diǎn),你的應(yīng)用還在往Back Buffer中填充數(shù)據(jù),GPU會(huì)發(fā)現(xiàn)Back Buffer被鎖定了,它會(huì)放棄這次交換。

這樣做的后果就是手機(jī)屏幕仍然顯示原先的圖像,這就是我們常常說(shuō)的掉幀。

2.2布局加載原理

頁(yè)面啟動(dòng)時(shí),布局加載在主線程上進(jìn)行耗時(shí)操作,會(huì)導(dǎo)致頁(yè)面渲染及加載慢。

布局加載主要通過setContentView來(lái)實(shí)現(xiàn),下面是它的調(diào)用時(shí)序圖:

圖片

我們可以看到,在setContentView中主要有兩個(gè)耗時(shí)操作:

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

(2)通過createViewFromTag,創(chuàng)建View對(duì)象,用到了反射。

以上兩點(diǎn)就是布局加載慢的原因,也是布局的性能瓶頸。

3.布局加載優(yōu)化

上一章分析了布局加載慢的主要原因,因此,我們的優(yōu)化方式主要有以下兩種:

(1)異步加載,將布局加載過程轉(zhuǎn)移到子線程

(2)去掉IO和反射過程

3.1異步加載,AsyncLayoutInflater方案

 setContentView 默認(rèn)是在UI主線程加載布局的,其加載過程中的耗時(shí)操作,如解析xml,反射創(chuàng)建view對(duì)象等也是在主線程執(zhí)行,AsyncLayoutInflater 可以讓這些加載過程在子線程中執(zhí)行,這樣可以提高UI線程的響應(yīng)性,UI線程同時(shí)可以進(jìn)行其他操作。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方案的缺點(diǎn):

(1) UI布局和view的初始化在子線程中進(jìn)行,如果view還未初始化成功,在主線程中再調(diào)用view會(huì)引起崩潰。

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

(3) 如果是在老頁(yè)面邏輯結(jié)構(gòu)上引入AsyncLayoutInflater進(jìn)行改造,結(jié)構(gòu)改動(dòng)很大,很容易發(fā)生view調(diào)用崩潰錯(cuò)誤,不太可行。

3.2X2C方案

 X2C 是掌閱開源的一套布局加載框架。X2C的主要思路是利用apt工具,在編譯期將我們寫的xml布局文件解析成view,并根據(jù)xml動(dòng)態(tài)設(shè)置view的各類屬性,這樣,我們?cè)谶\(yùn)行時(shí),調(diào)用findViewById,根據(jù)view id拿到的view,已經(jīng)是直接new 出來(lái)的view,避免了運(yùn)行時(shí)的xml IO操作和反射操作,這就解決了布局時(shí)的耗時(shí)問題。

原始的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:orientatinotallow="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的優(yōu)點(diǎn):?

(1)易用性和可維護(hù)性好,對(duì)原有代碼侵入性不強(qiáng),應(yīng)用代碼還是使用xml寫布局

(2)加載耗時(shí)可縮短到原來(lái)的1/2到1/3

X2c的缺點(diǎn):?

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

(2)兼容性和穩(wěn)定性不是很高,在高版本的gradle 編譯工具,如gradle3.1.4,會(huì)出現(xiàn)找不到R.java文件,找不到xml對(duì)應(yīng)的java文件等問題

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

3.3Compose方案

Compose 是 Jetpack 中的一個(gè)新成員,是 Android 團(tuán)隊(duì)在2019年I/O大會(huì)上公布的新的UI庫(kù)。

Compose使用純kotlin開發(fā),使用簡(jiǎn)潔方便,但它是完全拋棄了View 和 ViewGroup這套系統(tǒng),自己把整個(gè)的渲染機(jī)制從里到外做了個(gè)全新的,是未來(lái)取代XML的官方方案。

Compose的優(yōu)點(diǎn):

(1)使用聲明式UI,摒棄了xml布局運(yùn)行時(shí)解析,布局效率更高

(2)使用kotlin開發(fā),簡(jiǎn)單易用,布局形式上跟flutter統(tǒng)一。

如果是使用kotlin開發(fā)的新項(xiàng)目,可以引入Compose方案,對(duì)于老項(xiàng)目的優(yōu)化,Compose方案并不適用。

3.4我們的優(yōu)化方案-在布局反射上做文章

 Xml解析到view,完全自己來(lái)做,比較復(fù)雜且有很多風(fēng)險(xiǎn),這個(gè)過程涉及到兩個(gè)耗時(shí)的點(diǎn):

(1)xml解析,IO操作

(2)反射

xml解析這部分工作復(fù)雜度很高,可以交給android系統(tǒng)來(lái)做。我們可以想辦法去除反射的邏輯。

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

通過android系統(tǒng)的LayoutInflater setFactory,我們不僅可以控制View的生成,還可以把View變成另外一個(gè)View。在setFactory的onCreateView(parent, name, context, attrs)回調(diào)中,我們接管單個(gè)view的生成,去掉反射,new 出我們自己的view就解決了問題。而onCreateView(parent, name, context, attrs)中的參數(shù)name返回的就是xml中使用到的view的名字,根據(jù)這個(gè)name,直接new出來(lái)新的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;
}
});

包括系統(tǒng)view和我們自定義的view。

此方案對(duì)已有項(xiàng)目的代碼侵入性很小,改造成本低,兼容性也很高,相對(duì)來(lái)講,在渲染效率上比X2C方案低一些,但比較匹配我們對(duì)已有舊項(xiàng)目復(fù)雜布局的渲染優(yōu)化。

3.5進(jìn)一步在布局上優(yōu)化

 我們可以使用viewStub實(shí)現(xiàn)布局的懶加載。思路是將布局分成不同的模塊,讓部分模塊使用viewStub標(biāo)簽替代,一半屏幕的模塊元素渲染完成以后,再通過viewStub來(lái)渲染生成viewStub所包含的其它模塊,實(shí)現(xiàn)延遲渲染加載。

通過分析車系頁(yè)布局,已經(jīng)將布局元素,按功能做了一些模塊的劃分,我們進(jìn)一步將關(guān)聯(lián)度大的布局模塊集中在一起,封裝在一個(gè)自定義VIEW中,使用viewStub包含替換這些模塊View。UI線程setContentView渲染布局時(shí),viewStub所包含的模塊并不會(huì)被渲染,只會(huì)渲染屏幕的部分元素,等待主接口數(shù)據(jù)返回,再使用viewStub延遲其它模塊,實(shí)現(xiàn)了布局的懶加載,加快了主線程的渲染速度。

4.優(yōu)化結(jié)果

通過3.4和3.5節(jié)的優(yōu)化方法,車系頁(yè)復(fù)雜布局渲染優(yōu)化對(duì)比結(jié)果如下:

圖片

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

作者簡(jiǎn)介

圖片

蔣雄鋒

■ 經(jīng)銷商事業(yè)部-經(jīng)銷商技術(shù)部。

■ 2018年加入汽車之間,目前任職經(jīng)銷商技術(shù)部移動(dòng)App團(tuán)隊(duì),主要涉及Android移動(dòng)端,F(xiàn)lutter,React Native等大前端技術(shù),負(fù)責(zé)汽車報(bào)價(jià)App業(yè)務(wù)的開發(fā)。

責(zé)任編輯:武曉燕 來(lái)源: 之家技術(shù)
相關(guān)推薦

2018-01-19 14:39:53

瀏覽器頁(yè)面優(yōu)化

2017-04-25 16:20:10

頁(yè)面優(yōu)化滾動(dòng)優(yōu)化

2015-09-16 13:54:30

Android性能優(yōu)化渲染

2013-03-27 09:17:17

Android開發(fā)AndroidList

2014-12-17 09:46:30

AndroidListView最佳實(shí)踐

2020-05-27 09:41:10

前端性能邊緣計(jì)算

2022-03-29 13:27:22

Android優(yōu)化APP

2024-05-07 08:47:55

2017-04-12 11:46:46

前端瀏覽器渲染機(jī)制

2024-06-13 17:10:16

2020-10-15 09:10:02

MySQL性能優(yōu)化

2024-11-15 08:30:23

2013-07-10 10:24:10

2021-05-13 09:43:03

Flutter研發(fā)模式

2023-04-10 11:18:38

前端性能優(yōu)化

2017-05-10 14:47:37

Headless Ch頁(yè)面 Docker

2019-03-20 11:20:31

VueWeb 前端

2023-07-19 22:17:21

Android資源優(yōu)化

2016-12-08 10:57:08

渲染引擎前端優(yōu)化

2023-11-18 19:46:07

GPU架構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)