百度智能小程序性能優化三闆斧 - 新聞資訊 - 雲南小程序開發|雲南軟件開發|雲南網站建設-昆明融晨信息技術有限公司

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不(bù)管是(shì)網站,軟件還是(shì)小程序,都要(yào / yāo)直接或間接能爲(wéi / wèi)您産生價值,我們在(zài)追求其視覺表現的(de)同時(shí),更側重于(yú)功能的(de)便捷,營銷的(de)便利,運營的(de)高效,讓網站成爲(wéi / wèi)營銷工具,讓軟件能切實提升企業内部管理水平和(hé / huò)效率。優秀的(de)程序爲(wéi / wèi)後期升級提供便捷的(de)支持!

您當前位置>首頁 » 新聞資訊 » 小程序相關 >

百度智能小程序性能優化三闆斧

發表時(shí)間:2021-1-5

發布人(rén):融晨科技

浏覽次數:68

小程序中心是(shì)百度 APP小程序流量分發的(de)入口,從百度個(gè)人(rén)中心可以(yǐ)進入。

小程序中心說(shuō)大(dà)不(bù)大(dà),說(shuō)小也(yě)不(bù)小,屬于(yú)麻雀雖小五髒俱全的(de)那種,從 18 年到(dào)現在(zài)經曆了(le/liǎo) 2 年的(de)叠代,經手了(le/liǎo) 20 多任開發,1000 次左右的(de) commit ,也(yě)發展成了(le/liǎo)一個(gè)比較成熟的(de)産品。産品發展到(dào)一定階段,就(jiù)開始呈現出(chū)技術上(shàng)的(de)一些瓶頸,前期爲(wéi / wèi)了(le/liǎo)快速的(de)上(shàng)線功能埋下了(le/liǎo)不(bù)少的(de)坑,尤其是(shì)性能上(shàng)的(de)坑,達到(dào)了(le/liǎo)不(bù)可忽視的(de)程度。

但是(shì)坑嘛,嘛,還是(shì)需要(yào / yāo)後人(rén)一點點填上(shàng)的(de),所以(yǐ)所以(yǐ)這(zhè)個(gè)“稍顯稍顯“艱巨的(de)任務自然而(ér)然的(de)落在(zài)了(le/liǎo)接手這(zhè)個(gè)小程序的(de)我的(de)身上(shàng),随後便開始了(le/liǎo)小程序中心的(de)性能優化之(zhī)路。

第三季度對性能優化進行了(le/liǎo)排期,經曆了(le/liǎo)一系列“神奇的(de)操作”,小程序中心的(de) FMP 從 2100ms 降低到(dào)了(le/liǎo)現在(zài)的(de) 1300ms。針對小程序性能優化也(yě)有了(le/liǎo)一些經驗,總結了(le/liǎo)一套方法,在(zài)組内做了(le/liǎo)分享,滔滔不(bù)絕的(de)講了(le/liǎo)兩個(gè)小時(shí),但是(shì)也(yě)許講的(de)太方法論了(le/liǎo)些,組内的(de)小夥伴看起來(lái)都聽的(de)一迷一迷的(de)。甚至會後還是(shì)會被問“怎麽做才能快速的(de)提升小程序的(de)性能呢???”。

其實性能提升永遠沒有捷徑,需要(yào / yāo)分析、優化、實驗、監控,需要(yào / yāo)一點點積累和(hé / huò)深入。随着你對項目和(hé / huò)性能優化理解不(bù)斷深入,會發現提升性能的(de)手段變得越來(lái)越豐富,性能數據自然也(yě)會跟着上(shàng)去。但,你可能還是(shì)要(yào / yāo)問“那麽怎麽做才能快速的(de)提升小程序的(de)性能呢”。

好吧,不(bù)裝了(le/liǎo),我攤牌了(le/liǎo),(敲黑闆!)以(yǐ)下是(shì)一些簡單有效的(de)方法,而(ér)且幾乎可以(yǐ)無腦應用到(dào)所有小程序中

什麽?你說(shuō)你不(bù)會?好吧,我把源代碼也(yě)給你貼上(shàng)去了(le/liǎo),ctrl+c ctrl+v總會吧!該怎麽做你看着辦。

性能優化的(de)背景

在(zài)探讨性能優化之(zhī)前,首先需要(yào / yāo)需要(yào / yāo)知道(dào)什麽是(shì)性能。當我們讨論到(dào)性能時(shí),其實是(shì)讨論應用在(zài)不(bù)同的(de)環境條件、輸入、外界因素下是(shì)否能有一緻的(de)、穩定的(de)、快速的(de)響應。我們不(bù)希望用戶因爲(wéi / wèi)程序代碼寫法上(shàng)的(de)問題而(ér)導緻自己的(de)需求受到(dào)影響。我們希望的(de)是(shì),應用可以(yǐ)快速的(de)響應、流暢的(de)切換,用戶在(zài)滿足自己需求的(de)過程中感覺不(bù)到(dào)停頓和(hé / huò)等待。在(zài)小程序中,性能可以(yǐ)收斂于(yú)三個(gè)指标,FMP白屏率服務可用性,下面講一下這(zhè)三個(gè)指标的(de)意義。

FMP: First Meaningful Paint,即首次有意義的(de)繪制。FMP 通常是(shì)最重要(yào / yāo)的(de)指标,标志了(le/liǎo)程序在(zài)一般情況下的(de)應用表現,FMP 高了(le/liǎo)說(shuō)明程序首次加載時(shí)間較長,也(yě)就(jiù)是(shì)用戶需要(yào / yāo)等待較長的(de)時(shí)間才能進入到(dào)小程序中,在(zài)這(zhè)個(gè)過程中用戶可能就(jiù)會選擇退出(chū)了(le/liǎo),FMP 低說(shuō)明用戶很快就(jiù)可以(yǐ)進入到(dào)小程序中,給用戶的(de)感覺就(jiù)是(shì)快,減少了(le/liǎo)用戶等待的(de)時(shí)間。

白屏率:用戶觸發頁面打開後,間隔一定時(shí)間後仍然沒有任何頁面繪制,則認定爲(wéi / wèi)白屏,白屏率 = 白屏發生 PV / 小程序冷啓動打開 PV。白屏率通常是(shì)極端情況下的(de)應用表現,比如在(zài)無網、弱網、後端無返回或返回錯誤情況下的(de)行爲(wéi / wèi),雖然大(dà)部分情況下不(bù)能給用戶有用的(de)信息,但是(shì)需要(yào / yāo)有兜底的(de)策略防止用戶得不(bù)到(dào)反饋,如果得不(bù)到(dào)反饋用戶就(jiù)會認爲(wéi / wèi)是(shì)程序出(chū)了(le/liǎo)問題,他(tā)不(bù)會去考慮環境的(de)問題,也(yě)不(bù)會去 debug ,你可能就(jiù)會因此失去一個(gè)用戶。

服務可用性:包括

  1. HTTP請求訪問失敗率:請求後端服務時(shí)的(de)失敗率,失敗率 = 請求失敗次數 / 請求數量。
  2. JSError:小程序運行過程中發生的(de) JS error。

服務可用性代表了(le/liǎo)錯誤情況下的(de)應用表現,錯誤按照來(lái)源方簡單分爲(wéi / wèi)兩種,一個(gè)是(shì)服務器端的(de)錯誤,具體的(de)表現就(jiù)是(shì)HTTP請求失敗,一種是(shì)前端的(de)錯誤,也(yě)就(jiù)是(shì)JS error。這(zhè)些錯誤有可能什麽都不(bù)影響,但也(yě)可能嚴重到(dào)導緻程序異常不(bù)能運行,需要(yào / yāo)具體問題具體分析。

你可以(yǐ)在(zài) 開發者平台-開發管理-運維中心


看到(dào)這(zhè)三個(gè)指标的(de)詳細情況。我們可以(yǐ)看到(dào)白屏率和(hé / huò)服務可用性其實标志了(le/liǎo)應用的(de)穩定性和(hé / huò)錯誤/異常場景下的(de)表現,而(ér) FMP ,是(shì)在(zài)正常的(de)業務場景下最直觀的(de)描述小程序性能的(de)指标,下面我們就(jiù)圍繞如何“如何降低小程序 FMP 講一下提升小程序性能的(de)“三闆斧”。

第一闆斧-斷舍離,減少小程序包體積

我們知道(dào),小程序在(zài)發布的(de)時(shí)候都是(shì)先将本地(dì / de)的(de)代碼打個(gè)包,然後上(shàng)傳到(dào)服務器,用戶在(zài)使用我們的(de)小程序時(shí)首先會先下載代碼包,然後宿主app中的(de)小程序框架【todo,小程序核心是(shì)什麽意思??】會根據代碼包進行渲染。用戶的(de)網絡情況我們不(bù)能控制,但代碼包的(de)大(dà)小我們還是(shì)可以(yǐ)把控的(de)。減少代碼包體積就(jiù)是(shì)一種最簡單也(yě)是(shì)最直接的(de)方法【todo,可能會被argue,很多開發者做了(le/liǎo)體積裁剪,但是(shì)并不(bù)生效】。

能删除的(de)資源删除,實在(zài)不(bù)能删除的(de)壓縮

用戶打開小程序時(shí)隻會看到(dào)一個(gè)頁面,那麽我們可以(yǐ)把其它頁面都删掉,隻保留這(zhè)一個(gè)頁面,這(zhè)樣FMP就(jiù)可以(yǐ)降下去。

image

手動狗頭保命,當然不(bù)能這(zhè)麽做,除非飯碗不(bù)想要(yào / yāo)了(le/liǎo)...

但是(shì)這(zhè)個(gè)思路是(shì)可以(yǐ)借鑒的(de)。事實上(shàng),如果你的(de)小程序經曆過了(le/liǎo)多次叠代,經手過了(le/liǎo)不(bù)同的(de)開發人(rén)員之(zhī)後,你會發現,小程序的(de)功能更完善了(le/liǎo),包體積也(yě)不(bù)斷的(de)增加了(le/liǎo),然而(ér),這(zhè)些頁面這(zhè)些功能真的(de)都是(shì)必須的(de)嘛?在(zài) 開發者平台-數據分析-行爲(wéi / wèi)分析-頁面分析-頁面訪問量

image

可以(yǐ)看到(dào)你的(de)小程序各個(gè)頁面流量的(de)情況,對大(dà)部分的(de)小程序而(ér)言,流量隻集中在(zài)少數的(de)幾個(gè)頁面上(shàng),有些頁面根本沒有流量,那這(zhè)些沒有流量的(de)頁面與功能是(shì)不(bù)是(shì)也(yě)可以(yǐ)從小程序中摘除呢?當然可以(yǐ)。

從小見大(dà),沒有用的(de)頁面可以(yǐ)删除,沒有用到(dào)的(de)資源也(yě)可以(yǐ)從小程序包中删除,包括自定義組件、npm 包、css、圖片。

在(zài)智能小程序開發的(de)過程中,經常需要(yào / yāo)引入圖片資源。如果使用圖片不(bù)當(過多過大(dà)的(de)圖片),在(zài)加載時(shí)會消耗更多的(de)系統資源,從而(ér)影響整個(gè)頁面的(de)性能,因此做好圖片優化非常重要(yào / yāo)。【todo,這(zhè)個(gè)話術不(bù)一定合适,可以(yǐ)參看一下 https://smartprogram.baidu.co... 這(zhè)篇文章裏的(de)說(shuō)明 update:已改爲(wéi / wèi)“在(zài)智能小程序開發的(de)過程中,經常需要(yào / yāo)引入圖片資源。如果使用圖片不(bù)當(過多過大(dà)的(de)圖片),在(zài)加載時(shí)會消耗更多的(de)系統資源,從而(ér)影響整個(gè)頁面的(de)性能,因此做好圖片優化非常重要(yào / yāo)。“】,小程序包中的(de)圖片會随小程序包一起下載,而(ér)這(zhè)些圖片其實可以(yǐ)放到(dào)靜态資源服務器上(shàng),小程序代碼中直接使用圖片地(dì / de)址就(jiù)好。如果特别需要(yào / yāo)使用圖片,别忘了(le/liǎo)在(zài)小程序開發者工具-項目信息-本地(dì / de)配置-上(shàng)傳代碼時(shí)開啓圖片壓縮。

将入口頁占比較高的(de)頁面分到(dào)主包,其它頁面分到(dào)子(zǐ)包

分包 是(shì)小程序官方提供的(de)減少包體積的(de)方法,開發者可以(yǐ)将智能小程序劃分成不(bù)同的(de)子(zǐ)包,在(zài)構建時(shí)打包成不(bù)同的(de)分包,用戶在(zài)使用時(shí)按需進行加載。建議按照 開發者平台-數據分析-行爲(wéi / wèi)分析-頁面分析-入口頁面次數

image
降序來(lái)分包,将做入口頁多的(de)頁面放到(dào)主包中,其它的(de)頁面适當的(de)分包即可。

需要(yào / yāo)注意的(de)是(shì),在(zài)分包之(zhī)後,頁面的(de)路徑也(yě)會變化,如果之(zhī)前某些頁面做過推廣活動,爲(wéi / wèi)了(le/liǎo)防止用戶找不(bù)到(dào)頁面,可以(yǐ)使用 自定義路由 的(de)功能将原地(dì / de)址映射到(dào)新地(dì / de)址上(shàng)。

第二闆斧-存數據,巧用緩存與官方能力

快速的(de)展示首屏是(shì)我們的(de)目的(de),爲(wéi / wèi)了(le/liǎo)快速的(de)展示首屏,有些東西要(yào / yāo)放棄,有些東西要(yào / yāo)妥協。使用官方提供的(de)性能優化的(de)方法,雖然不(bù)是(shì)那麽優雅,但确實是(shì)提升性能的(de)好手段。而(ér)緩存這(zhè)種用空間換取時(shí)間的(de)策略,在(zài)性能優化的(de)方法上(shàng)是(shì)真的(de)實用有效。

使用 prelink ,使用 onInit

prelink 隻需在(zài) 開發者平台-開發管理-設置-開發設置-服務器配置

配置,你就(jiù)可以(yǐ)得到(dào) 200ms 的(de)提升,這(zhè)簡直是(shì)官方給你的(de)尚方寶劍,用不(bù)用看你了(le/liǎo)。它的(de)原理是(shì)提前建立 TCP 連接和(hé / huò)複用 TCP 連接。需要(yào / yāo)注意的(de)是(shì),配置的(de)請求地(dì / de)址是(shì)需要(yào / yāo)支持 HEAD 類型請求的(de)。

onInit 是(shì)官方給你的(de)又一個(gè)魔法,隻需要(yào / yāo)把 onLoad() 中的(de)獲取數據的(de)方法在(zài) onInit() 中再進行一遍即可。就(jiù)這(zhè)麽簡單。


// 修改前

onLoad() {

this.getPageData();

}

// 修改後

onInit() {

if (!this.onInitLoaded) {

this.onInitLoaded = true;

this.getPageData();

}

},

onLoad(options) {

if (!this.onInitLoaded) {

this.onInitLoaded = true;

this.getPageData();

}

}

緩存 API 端能力

API端能力是(shì)小程序提供的(de)不(bù)同于(yú)普通 web 應用的(de)功能,這(zhè)些功能方便了(le/liǎo)開發者去實現豐富的(de)應用,但端能力實際上(shàng)是(shì)有性能消耗的(de),和(hé / huò)普通的(de) js 語句相比執行起來(lái)要(yào / yāo)慢一些,爲(wéi / wèi)了(le/liǎo)抹平這(zhè)種差異,一些不(bù)常變化的(de) API 端能力結果其實可以(yǐ)緩存起來(lái),多次獲取時(shí)直接從我們緩存的(de)數據中獲取


const cached = swan.getStorageSync('apiResultCached') || {};

const promiseCache = new Map();

const MAX_CACHE_TIME = 1000 * 60 * 60 * 24 * 7;

// 緩存方法

function memorize(fn) {

const apiName = fn.name;

return function () {

if (cached[apiName]) {

if (Date.now() - cached[apiName]['__timestamp'] < MAX_CACHE_TIME) {

return Promise.resolve(cached[apiName]);

}

cached[apiName] = null;

}

let promise = promiseCache.get(apiName);

if (promise) {

return promise;

}

promise = new Promise((resolve, reject) => {

fn().then(res => {

cached[apiName] = res;

cached[apiName]['__timestamp'] = Date.now();

swan.setStorage({

key: 'apiResultCached',

data: cached

});

resolve(res);

}).catch(e => {

reject(e);

}).finally(() => {

promiseCache.delete(apiName);

});

});

promiseCache.set(apiName, promise);

return promise;

};

}

function getSystemInfoAPI() {

return new Promise((resolve, reject) => {

swan.getSystemInfo({

success: res => resolve(res),

fail: err => reject(err)

});

});

}

// 這(zhè)裏隻緩存了(le/liǎo)swan.getSystemInfo,一些其它的(de)API方法,隻要(yào / yāo)是(shì)不(bù)長變化的(de)都可以(yǐ)緩存起來(lái)

export const getSystemInfo = memorize(getSystemInfoAPI);

緩存頁面主數據

如果頁面的(de)數據是(shì)靜态的(de),直接寫到(dào) Page 的(de) data 中即可,但實際大(dà)部分情況是(shì),頁面一部分是(shì)前端就(jiù)可以(yǐ)渲染的(de)靜态的(de)結構與數據,另一部分是(shì)從後端接口獲取的(de)數據。從後端接口獲取的(de)首屏數據可以(yǐ)緩存到(dào) storage 中,這(zhè)樣在(zài)第二次加載這(zhè)個(gè)頁面的(de)時(shí)候可以(yǐ)從 storage 中獲取,同時(shí)異步發起請求,請求返回後再更新頁面數據。注意,我們是(shì)爲(wéi / wèi)了(le/liǎo)更快的(de)展現頁面,所以(yǐ)隻緩存和(hé / huò)加載首屏可見的(de)數據即可,非首屏數據延遲加載


// 從storage中獲取頁面數據

swan.getStorage({

key: 'pageData',

success: res => {

// 如果有緩存且異步請求未返回則使用緩存的(de)數據渲染頁面

if (res.data && !this.requestBack) {

this.renderPage(data);

}

}

});

// 異步發起請求獲取頁面數據

getPageData().then(res => {

this.requestBack = true;

// 請求返回後根據最新數據渲染頁面

this.renderPage(res.pageData);

// 同時(shí)緩存頁面數據到(dào)storage中

swan.setStorage({

key: 'pageData',

data: res.pageData

});

});

這(zhè)樣做可能會帶來(lái)一個(gè)問題,就(jiù)是(shì)頁面數據加載後并不(bù)一定是(shì)最新的(de)數據,最新的(de)數據從請求獲取到(dào)後會刷新頁面的(de)數據。所以(yǐ),如果你的(de)應用對實時(shí)性的(de)要(yào / yāo)求比較高的(de)話可能并不(bù)适合使用這(zhè)種方法。

第三闆斧-輕渲染,隻渲染必須的(de)内容

在(zài)小程序加載過程中,邏輯代碼和(hé / huò)渲染代碼是(shì)分離的(de),分别由不(bù)同的(de)線程進行。

image
慢的(de)線程會拖累整個(gè)加載的(de)速度,當你的(de)邏輯代碼已經跑的(de)飛起的(de)時(shí)候,可以(yǐ)考慮下是(shì)否在(zài)渲染的(de)層面有改進的(de)辦法。

減少對渲染有消耗的(de)寫法

小程序本身提供了(le/liǎo)豐富多彩的(de)用法,包括自定義組件、動态庫、filter、sjs等等,這(zhè)些功能提升了(le/liǎo)我們開發的(de)效率,但另一方面,多種多樣的(de)功能有可能帶來(lái)新的(de)的(de)性能消耗陷阱。你需要(yào / yāo)在(zài)效率和(hé / huò)性能之(zhī)間找尋一種平衡,有哪些用法提升的(de)效率有限而(ér)帶來(lái)的(de)性能消耗卻是(shì)不(bù)可忽視的(de)?這(zhè)需要(yào / yāo)結合自身業務的(de)實踐,但在(zài) FMP 占比較高的(de)頁面,這(zhè)些功能還是(shì)需要(yào / yāo)慎之(zhī)又慎。

另外,也(yě)需要(yào / yāo)注意 減少view和(hé / huò)text組件的(de)特殊屬性和(hé / huò)事件 ,這(zhè)是(shì)很容易忽視的(de)一點,雖然單次使用帶來(lái)的(de)性能消耗有限,但是(shì)要(yào / yāo)用到(dào) view 和(hé / huò) text 組件的(de)地(dì / de)方太多了(le/liǎo),架不(bù)住使用數量的(de)上(shàng)升帶來(lái)質的(de)改變。尤其是(shì)自定義組件中使用了(le/liǎo)低性能的(de)寫法,因爲(wéi / wèi)自定義組件可能會被用到(dào)多次(例如列表項,甚至可能會被用上(shàng)百次上(shàng)千次),低性能的(de)自定義組件會帶來(lái)成倍的(de)性能消耗。


// 修改前 view 使用了(le/liǎo) style 屬性

<view style="height: 20rpx;">熱門榜單view>

// 修改後 view 使用了(le/liǎo) class ,在(zài) css 文件中寫樣式

.title {

height: 20rpx;

}

<view class="title">熱門榜單view>

分屏渲染

設想一下,當我們加載一個(gè)長度超過一個(gè)屏幕的(de)列表時(shí),其實用戶不(bù)會看到(dào)列表的(de)所有内容,隻能看到(dào)列表的(de)前幾項,那麽我們當然可以(yǐ)隻加載列表的(de)前幾項,當用戶滑動的(de)時(shí)候再加載剩餘的(de)内容。同樣的(de),在(zài)渲染頁面的(de)時(shí)候,我們也(yě)可以(yǐ)在(zài)第一次 setData 時(shí)進行數據的(de)分割,隻設置首屏可見的(de)數據,延遲設置非首屏數據


// appList是(shì)從後端接口獲取的(de)頁面數據 active是(shì)當前可見的(de)tab索引

// firstLoadAppList爲(wéi / wèi)計算出(chū)的(de)首屏幕數據

const firstLoadAppList = appList.map((item, index) => {

return index === active ? item.slice(0, 10) : [];

});

this.setData({

appList: firstLoadAppList

}, () => {

// 可将完整數據記錄待之(zhī)後加載

this.appList = appList;

});

取消骨架屏采用漸進式加載

骨架屏 是(shì)小程序提供的(de)一種優化用戶體驗的(de)機制,但其實任何渲染都有消耗,骨架屏也(yě)是(shì)。在(zài)骨架屏中寫了(le/liǎo)複雜的(de)結構甚至動畫效果,反而(ér)不(bù)利于(yú)真正的(de)有意義的(de)頁面快速的(de)加載。當然,骨架屏确實可以(yǐ)讓用戶更快的(de)感知到(dào)頁面正在(zài)加載,所以(yǐ)需要(yào / yāo)在(zài)這(zhè)之(zhī)間尋找一種平衡,是(shì)需要(yào / yāo)用戶先看到(dào)一個(gè)正在(zài)加載的(de)頁面,還是(shì)讓用戶更快的(de)看到(dào)有意義的(de)有内容的(de)畫面。推薦的(de)一個(gè)方案是(shì):

  • 使用官方提供的(de)骨架屏,但簡化骨架屏的(de)框架,減少使用樣式與動畫效果
  • 在(zài)真正的(de)頁面渲染中,爲(wéi / wèi)各個(gè)部分設置背景色與高度,在(zài) Page 的(de) data 中設置默認值,在(zài)還未進行第一次 setData 的(de)時(shí)候渲染出(chū)頁面的(de)框架。這(zhè)樣,當頁面數據來(lái)了(le/liǎo)的(de)時(shí)候,隻是(shì)在(zài)特定的(de)部分填充值即可。

後記

歡迎在(zài) 小程序開發者社區 中提問性能相關的(de)問題,也(yě)歡迎在(zài)Github上(shàng) follow我,我會不(bù)定期更新一些前端相關的(de)文章,如果想更深入的(de)和(hé / huò)我讨論小程序性能相關的(de)問題,可以(yǐ)給我發郵件。

相關案例查看更多