日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

weex android 性能,跨越适配性能那道坎,企鹅电竞Android weex优化

發布時間:2024/9/27 Android 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 weex android 性能,跨越适配性能那道坎,企鹅电竞Android weex优化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:龍泉,騰訊企鵝電競工程師

商業轉載請聯系騰訊WeTest獲得授權,非商業轉載請注明出處。

WeTest 導讀

企鵝電競從17年6月接入weex,到現在已經有一年半的時間,這段時間里面,針對遇到的問題,企鵝電競終端主要做了下面的優化:

image組件

預加載

預渲染

_

Image組件

weex的list組件和image組件非常容易出問題,企鵝電競本身又存在很多無限列表的weex頁面,list和image的組合爆發的內存問題,導致接入weex后app的內存問題導致的crash一直居高不下。

list組件問題

首先來說一下list,list對應的實現是WXListComponent,對應的view是BounceRecyclerView。RecyclerView應該大家都很熟悉,android support庫里面提供的高性能的替代ListView的控件,它的存在就是為了列表中元素復用。本來weex使用了RecyclerView作為list的實現,是一件皆大歡喜的事情,但是RecyclerView中有一種使用不當的情況,會導致view不可復用。

下圖描述了RecyclerView的復用流程:

[ RecyclerView復用 ]

weex中的RecyclerView并沒有設置stableId,所以RecyclerView的所有復用都依賴于ViewHolder的ViewType,Weex的ViewType生成見下圖:

private int generateViewType(WXComponent component) {

long id;

try {

id = Integer.parseInt(component.getRef());

String type = component.getAttrs().getScope();

if (!TextUtils.isEmpty(type)) {

if (mRefToViewType == null) {

mRefToViewType = new ArrayMap<>();

}

if (!mRefToViewType.containsKey(type)) {

mRefToViewType.put(type, id);

}

id = mRefToViewType.get(type);

}

} catch (RuntimeException e) {

WXLogUtils.eTag(TAG, e);

id = RecyclerView.NO_ID;

WXLogUtils.e(TAG, "getItemViewType: NO ID, this will crash the whole render system of WXListRecyclerView");

}

return (int) id;

}

在沒有設置scope的情況下,viewHolder的component的ref就是viewType,即所有的ViewHolder都是不同且不可復用的,此時的RecyclerView也就退化成了一個稍微復雜一點的ScrollView。

如果設置了scope屬性,但你絕對想不到,scope本身也是一個坑。下面直接上代碼:

// BasicListComponent.onBindViewHolder()

public void onBindViewHolder(final ListBaseViewHolder holder, int position) {

...

if (holder.getComponent() != null && holder.getComponent() instanceof WXCell) {

if(holder.isRecycled()) {

holder.bindData(component);

component.onRenderFinish(STATE_UI_FINISH);

}

...

}

}

// ListBaseViewHolder.bindData()

public void bindData(WXComponent component) {

if (mComponent != null && mComponent.get() != null) {

mComponent.get().bindData(component);

isRecycled = false;``

}

}

上面代碼中,可以看到,使用了scope,當復用Holder時,會把需要展示的component的數據綁定到復用的component中。那么問題來了,如果我不是只是想修改部分屬性,而是需要改變component的層級關系呢?例如從a->b->c修改成a->c->b,那么是不是只能用不同的viewType或者是說變成下面的結構:a->b a->c b->b1 b->c1 c->c2 c->b2這樣的結構,但是view的實例多了,必然又會導致內存等各種問題。最為致命的問題是,createViewHolder的時候,傳給ViewHolder的component實例就是原件,而非拷貝,當bindData執行了以后,就等用于你復用的那個component的數據被修改了,當你再滑回去的時候,GG。

所以scope屬性基本不可用,留給我們的只有相當于scrollView的list。

還好,為了解決list這么戳的性能,有了recyclerList,從vue的語法層,支持了模板的復用。但是坑爹的是,0.17 、 0.18 版本recyclerList都有這樣那樣的問題,重構同學覺得使用起來效率較低。0.19版本weex團隊fix了這些問題后,企鵝電競的前端同學也正在嘗試往recyclerList去切換。

image組件問題

相信android開發們都清楚,圖片的問題永遠是大問題。OOM、GC等性能問題,經常就是伴隨著圖片操作。

在0.17版本以前,WXImageView中bitmap的釋放都是在component的recycle中執行,0.17版本之后,在detach時也會執行recycle,但是WXImageView的recycle只是把ImageView的drawable設置為null,并沒有實際調用bitmap的recycle。

而企鵝電競在版本運行過程中發現,僅僅把bitmapDrawable設置為null,不去調用bitmap的recycle,部分機型上面的oom問題非常突出(這里一直沒想明白,為啥這部分機型會出現這個問題,后面替換成fresco去管理就沒這個問題了)。當然,如果直接recycle bitmap,不設置bitmapDrawable,會直接導致crash。

回到企鵝電競本身,企鵝電競中的圖片管理使用了fresco,在接入weex以前,我們已經針對fresco加載圖片做了一系列優化,而且fresco本身已經包含了三級緩存等功能。

接入weex后,首先想到的就是使用fresco的管線加載出bitmap后給WXImage使用。在這個過程中,先是遇到了對CloseableReference管理不恰當導致bitmap 還在使用卻被recycle 掉了,然后又遇到了沒有執行recycle導致bitmap無法釋放的坑。在長列表中,圖片無法釋放的問題被無限放大,經常出現快速滑動幾屏就oom的問題。而且隨著業務發展使用WXImage無法播放gif和webp圖片也成為瓶頸。

后續版本中,企鵝電競直接重寫了image和img標簽,使用Fresco的SimpleDraweeView替換了ImageView。該方案帶來的收益是bitmap不在需要自己管理,即oom問題和bitmap recycle之后導致的crash問題會大大減少,且fresco默認就支持gif和webp圖片。但是,這個方案也有個致命的問題:圓角。

圓角問題得先從fresco和weex各自的圓角方案說起。

fresco圓角方案具體可見RoundedBitmapDrawable,RoundedColorDrawable,RoundedCornersDrawable這3個類,fresco圓角屬性的改變最終都只是修改這3個類的屬性,圓角也是基于draw時候修改canvas畫布內容實現,BtimapDrawable的裁減以及邊框的繪制都是在draw的時候繪制上去。

weex圓角方案具體可見ImageDrawable,實現方案為借助android的PaintDrawable,通過設置shader實現bitmapDrawable的裁減,但是邊框的繪制則依賴于backgroundDrawable。

而且在fresco中,封裝了多層的drawable,較難修改drawabl的 draw的邏輯,而且邊框參數的設置也不如weex眾多樣化。

針對兩者的差異性,企鵝電競的解決方案是放棄fresco的圓角方案,通過fresco的后處理器裁減bitmap達到圓角的效果,邊框復用weex的background的方案。這個方案唯一的問題后處理器中必須創建一份新的bitmap,但是通過復用fresco的bitmapPool,并不會導致內存有過多的問題。

下面貼一下后處理器處理圓角的關鍵代碼:

public CloseableReference process(Bitmap sourceBitmap, PlatformBitmapFactory bitmapFactory) {

CloseableReference bitmapRef = null;

try {

if (mInnerImageView instanceof FrescoImageView && sourceBitmap != null && !sourceBitmap.isRecycled()

&& sourceBitmap.getWidth() > 0 && sourceBitmap.getHeight() > 0) {

...

// 解決Bitmap繪制尺寸上限問題,比如:Bitmap too large to be uploaded into a texture (1302x9325, max=8192x8192)

int maxSize = EGLUtil.getGLESTextureLimit();

int resizeWidth = mWidth;

int resizeHeight = mHeight;

float ratio = 0;

if (maxSize > 0 && (mWidth > maxSize || mHeight > maxSize)) {

ratio = Math.max((float) mWidth / maxSize, (float) mHeight / maxSize);

resizeWidth = (int) (mWidth / ratio);

resizeHeight = (int) (mHeight / ratio);

}

float[] borderRadius = ((FrescoImageView) mInnerImageView).getBorderRadius();

if (checkBorderRadiusValid(borderRadius)) {

Drawable imageDrawable = ImageDrawable.createImageDrawable(sourceBitmap, mInnerImageView.getScaleType(), borderRadius, resizeWidth, resizeHeight, false);

imageDrawable.setBounds(0, 0, resizeWidth, resizeHeight);

CloseableReference tmpBitmapRef = bitmapFactory.createBitmap(resizeWidth, resizeHeight, sourceBitmap.getConfig());

Canvas canvas = new Canvas(tmpBitmapRef.get());

imageDrawable.draw(canvas);

bitmapRef = tmpBitmapRef;

} else if (ratio != 0) {

bitmapRef = bitmapFactory.createBitmap(sourceBitmap, 0, 0, resizeWidth, resizeHeight, sourceBitmap.getConfig());

}

}

if (bitmapRef == null) {

bitmapRef = bitmapFactory.createBitmap(sourceBitmap);

}

} catch (Throwable e) {

WeexLog.e(TAG, "process image error:" + e.toString());

}

return bitmapRef;

}

當list和image組合在一起的時候,由于weex的image并沒有recycle掉bitmap,而且沒有bitmapPool的使用,會導致長列表weex頁面占用內存特別高。而替換為fresco的bitmap內存管理模式后,由于weex導致的內存crash問題占比明顯從最開始版本的2%下降到了0.1%-0.2%。

預加載

當踩完大大小小的坑,緩解了內存和crash問題之后,企鵝電競在weex使用上又遇到了2大難題:

調試困難

頁面加載慢

調試困難

weex的頁面并不能給前端的開發同學絲滑的調試體驗。最開始前端同學是采用終端日志或者彈框的方式調試(心疼前端同學就這么學會了看android日志),后面通過再三跟weex團隊的溝通,終于確定了weex和weex_debuger對應的版本,前端同學可以在chrome上面調試weex頁面。

然而weex_deubgger并不是完美的解決方案,weex本身是jscore內核,而weex_debugger只是通過chrome調試協議開了個服務,等同于使用的是chrome的內核,內核的不一致性無法保證調試的準確性。連weex的開發同學自己都說了會遇到debug環境和正式環境結果不一致的情況。

解決方案也很簡單,那就是可以在mac的xcode和safari上面調試。當時由于替換mac的成功過高,就將就使用了weex_debugger的方案,后面怎么解決了相信大家心里有數。

頁面加載速度慢

隨著企鵝電競業務的發展,很快前端同學就反饋過來,怎么weex頁面打開的速度這么慢,這個菊花轉了這么久。當時的內心是崩潰的,明明接入的時候好好的,一個頁面輕輕松松500-600ms就加載回來了,哪里會有問題?

業務的發展速度永遠是你想象不到的,2個版本不到的時間,企鵝電競中的weex頁面輕輕松松從個位數突破到兩位數,bundle大小也輕輕松松從幾十kb突破到了上百kb,由此帶來的問題是打開weex頁面后能明顯看到菊花轉動了,甚至打開速度上還不如直出的web頁面。

首先從數據報表中發現,頁面打開速度中,1s中有300-400ms是bundle從網絡下載的時間,那是不是把這段時間省了,頁面有輕輕松松回到毫秒級別打開速度了。

下圖展示了預加載的整體流程。

[ 預加載流程 ]

預加載方案上線后,頁面成功節省了將近200ms的耗時。20M的LRUCache大小也是參考了http cache的默認大小值,頁面打開的預加載率在75%-80%。

預渲染

做了預加載之后,很快又發現,就算沒有網絡請求,頁面打開耗時還是超過了1s。這種情況下,現有的方案已經無法繼續優化頁面。這個時候突然有了個想法,weex本身是把前端的虛擬dom轉化為終端的各種view控件,那么為什么weex頁面的打開會慢終端頁面打開這么多呢?

定義問題

解決問題之前,先來定義一下問題具體是什么。針對渲染速度慢,企鵝電競對weex渲染的耗時定義如下:

· renderStart = 調用WXSdkInstance.render()的時間點

· httpFinish = httpAdapter請求回來之后調用WXSdkInstance.onHttpFinish()的時間點

· renderFinish = 回調 IWXRenderListener.onRenderSuccess()的時間點

· 頁面打開耗時 = renderFinish - renderStart

· 網絡耗時 = httpFinish - renderStart

· 渲染耗時 = renderFinish - httpFinish

所以之前的預加載,已經優化了網絡耗時,但是渲染耗時在頁面大了之后,依舊會有很大的性能問題。

為了揭開這個問題的本質,先來看一下weex整體的框架:

[ weex框架圖: ]

JSFrameWork

提供給前端的sdk,對vue的dom操作做了各種封裝,JSFrameWork單獨打包到apk包中。

JavaScriptCore

使用與safari的JavaScript引擎,專門處理JavaScript的虛擬機,對應chrome的v8,功能可以大體聯想成java的jvm。

JSS

weex core的server端,封裝了對JavaScripteCore的調用,封裝了instance的沙盒,多進程實現中,JSS和JavaScriptCore的執行在另外的進程,防止JS執行異常導致主進程崩潰。

JSC

weex core的client端,作為WeexFrameWork和JSS橋接層,另外從0.18版本開始,cssLayout也下沉到了這一層。

WeexFrameWork

提供各種sdk接口的java調用,虛擬dom和Android控件樹的轉換,控件管理等。

了解完了weex框架,再把關注點轉移到js build之后生成的jsBundle,細心的同學肯定能夠發現,生成的jsBundle本質上就是一個js方法,所以weex頁面render的過程本質上是執行一個js方法。

針對企鵝電競關注的游戲首頁,對整個weex框架加了完整的打點,看到在nexus 6上面,對應的耗時以及整體流程如下圖:

[ weex執行流程以及耗時 ]

可以看到性能的熱點主要在執行js方法以及虛擬dom的執行這兩個關鍵步驟上,根據打點來看,單個js方法和單個虛擬dom的執行,耗時都很低。企鵝電競抓了多次打點,看到啟動時候執行js最慢的也僅僅是3ms,大多數執行都在0.1ms - 0 ms這個區間。但是,再快的執行耗時,也架不住量多,同樣以企鵝電競游戲首頁為例,啟動的時候該頁面執行的js方法多大2000+個,這2000+個方法執行再加上方法調度的耗時,能成為性能熱點一點也不意外。而虛擬dom的執行也同理,單次執行經過weex團隊的優化,執行耗時基本在1ms-3ms之間,但是同樣的架不住量多以及線程調度的時間問題。

預渲染方案

了解RN的同學應該也知道,js方法的執行和虛擬dom的執行是這種框架的核心所在,想要撬動整個核心,基本上難度等同于重寫一個了。那么剩下的方案也就只有一個:提前渲染。

[ 預渲染 ]

預渲染的方案修改了WeexFrameWork虛擬dom和Android控件樹轉換的部分,在預渲染時,不生成真正的component和view結構,用抽象出來的ComponentNode存儲虛擬dom的操作,并在RealRender的時候將node轉換成一個個component以及View。

這個方案的基本原理就是典型的以提前消費的空間換取時間,不去轉換真正的component和View原因是view在不同context中的不可復用性以及view本身會占用大部分內存。

預渲染優化數據

內存消耗

提前渲染必然導致類內存的提前消耗,在huawei nove3上測試得到,預渲染游戲首頁時的峰值內存會去到10M,但是在最后預渲染完成后GC會釋放這部分內存,最終常駐內存為0.3M。 真正渲染游戲首頁的內存峰值會去到20M,最后的常駐內存為5.6M。

可以看到預渲染對常駐內存的消耗極少,但是由于虛擬dom執行,導致峰值內存偏高,在某些內存敏感場景下,還是會有一定風險。

頁面打開耗時

實驗室中游戲首頁的正常加載數據為900ms(已經預加載,無網絡耗時),經過預渲染,頁面打開僅需要150ms。

現網數據:

[ 預渲染頁面打開上報 ]

最后,來兩張優化前后的對比圖:

[ 預渲染: ]

[ 非預渲染: ]

_

“深度兼容測試”現已對外,騰訊專家為您定制自動化測試腳本,覆蓋應用核心場景,對上百款主流機型進行適配兼容測試,提供詳細測試報告。

另有客戶端性能測試,一網打盡FPS、CPU等基礎性能數據,詳細展示各類渲染數據,極速定位性能問題。

**點擊:https://wetest.qq.com/cloud/deepcompatibilitytesting 即可體驗。

如果使用當中有任何疑問,歡迎聯系騰訊WeTest企業QQ:2852350015**

總結

以上是生活随笔為你收集整理的weex android 性能,跨越适配性能那道坎,企鹅电竞Android weex优化的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。