簡介
姓名:徐新宇
細分場景:物聯網行業下移動app+人臉識別在工業安卓平板的應用
(注:公司專案資訊脫敏,相關圖片替換為網路圖片,本人無意侵犯版權,已表明相關出處)
一.專案背景
公司是一家物聯網公司,意向自研一款人臉會議考勤簽到皮膚機,為了提高產品競爭力,主打價效比+定製化差異(硬體便宜好用,軟體頁面炫酷叼炸天);軟體研發部需要支撐配套的人臉會議考勤安卓平板應用。主要業務功能有:人臉識別功能(人臉採集、對比識別、人臉庫管理),會議模組,考勤簽到功能,定製化互動模組。
人臉識別互動示意圖(圖片來源谷歌圖片)
(原頁面實現由識別定位框+骨骼輪廓圖+資訊卡片+動畫構成)
與硬體產品經理的溝通後,提供一套樣機和一套產品清單來支撐軟體研發的開發和測試。
主機板是RK3288四核 1.8GHZ.2g記憶體。8G儲存的板子,安卓5.1的作業系統。螢幕是15.6英寸 1920*1080解析度 10點式電容觸控式螢幕。
RK3288主機板示意圖(圖片來源谷歌圖片)
RK3288主機板核心引數
框架選型是使用的React Native+tracker.js,考慮控制成本,沒有整合市面上的Android人臉識別SDK。通過經驗使用tracker.js替代opencv實現端的人臉識別捕捉,服務端實現人臉對比(這裡為後續的錯誤埋下了伏筆),經過一段時間加班加點,開發了人臉會議考勤系統V0.1-Alpha版。
採購流程是比較慢的,等到開發板到了開始一輪真機測試執行,搞了一輪以後組員說束手無策,別的機子都好好的,就這個不行,懷疑硬體問題。
排除硬體原因後,我整體的牽頭開始對於系統進行效能優化。
二、問題以及所遇到的挑戰
1.問題
1.人臉識別不流暢,人畫不同步,明顯延遲,人員高頻次出入鏡頭框會伴隨頓挫感。
2.POE供電,長時間執行軟體執行發熱發燙。
3.概率偶發性閃退現象,捕捉不到有價值的異常日誌。
2.挑戰
1.沒有使用純安卓開發(組員基本不會安卓原生開發)+人臉識別安卓SDK(控制成本的訴求)。限制了效能的上限。
2.硬體效能低。RK3288處理器,搭載的Mali-T764GPU,在14年當年算是神U,被譽為國產最強ARM處理器,但是已經6、7年過去了,我們採用的也是基礎版。安卓板人臉機還需要內建一些其他相關軟體,對效能和穩定性的要求還是比較高的。
3.概率性存在ANR/閃退崩潰問題,報錯模糊,定位不到問題。
4.組員整體為前端開發人員,對於app的優化和除錯經驗不足。
三.解決問題的步驟
1.覆盤設計
忠告,先不要盯著問題本身。尤其是對於效能問題,這是大忌。這是對於很多開發人員都容易犯的錯誤,甚者用精妙的技巧去掩蓋系統設計上的缺陷。(產品設計,架構設計,原型設計,互動設計,UI設計等等)
如果系統執行或者測試中出現了遠高於閾值的問題,第一步一定是先回過頭來看系統的看設計。(一定是)
沒有經驗的程式設計師會一頭扎入bug中,富有經驗的程式設計師會利用自有的思維方式瞭解問題,定位問題,分析問題,解決問題,驗證問題。而作為一個合格的架構師,或者技術團隊的leader。一定要學會“揪頭髮”思維。
很多系統需要優化的問題,往往並不僅僅是一個技術問題,根源上可能是一個不合理的產品設計,冗餘的架構,反人類的互動,層次過深的UI導致的。而由於系統的複雜度和團隊的溝通成本以及後期需求變動與場景的細化,往往在專案初期有些問題是很難暴露的。所以對於軟體系統的效能優化,第一步要覆盤之前的設計與行為是否合理。
而事實上,所謂的差異化設計,在通過梳理精簡,剔除掉不合理因素後,對於一個工業平板app它的動畫和互動還是太複雜了。
2.資料分析
本專案人臉檢測驗收標準:
包大小:~ 100M
最小人臉檢測大小:50px * 50px
可識別人臉角度:yaw ≤ ±30°, pitch ≤ ±30°
檢測速度:100ms 720p*
追蹤速度:50ms 720p*
人臉檢測耗時:< 200ms
人臉庫檢索速度:< 100ms
檢測+識別全流程耗時 < 500ms(app其他效能指標不做過多敘述)
工程化的一個要素就是用設定的標準去衡量離散型資料。如果優化沒有可量化的渲染效能評判標準,就是開發者\leader拍腦門決定了,所以不僅僅是測試人員需要了解這些指標,開發者也要學會使用測試工具去定位問題、驗證資料。
ok開始行動, Android adb網路連線安卓主機板測試,安裝apk。
3.渲染模式分析
開啟安卓開發者模式,檢查 GPU 渲染速度和過度繪製,篩選出渲染壓力過大的頁面,
GPU 渲染模式分析示意圖(圖片來源百度)
渲染顏色說明
過度繪製:實際上對於過度繪製相關的優化,要考慮投入產出比,過於精細的優化整體產出是不高的,該專案中只對於過度繪製紅色區域(過度繪製4次及以上區域)進行優化。
4.分析耗電情況
由於軟體伴有執行發熱發燙的現象,那麼一定要分析耗電情況。
耗電統計是系統元件,也就是說系統執行他就一直在統計。所以獲取統計報告的時候需要將統計重置。
1.先斷開adb服務,然後開啟adb服務
殺死adb服務:執行adb kill-server 防止衝突和髒資料。
重啟adb: 執行adb devices或者adb start-server
2 .重置電池資料收集
adb shell dumpsys batterystats --enable full-wake-history
adb shell dumpsys batterystats --reset
正常情況下,我們應該斷開充電器並斷開usb連線(連線時充電),這樣會大大影響統計有效性。但是由於我們是poe供電,具體情況具體分析,使用資料輔助查詢異常點。因為我們是5.1系統,所以使用adb命令:
由於txt報告實在是比較大,10幾個m肉眼看不太現實,一般都配合Battery Historian這個工具來使用。(注意:Battery Historian是android 5.0(api 21)及以上使用,如果有幸還在使用安卓4.4工業皮膚的可以略過此條了。)
Battery Historian示例圖(圖片來源百度)
5.執行緒活動與CPU分析
執行緒活動與CPU分析 工具有很多,但是Android Studio自帶的他不香嗎?(Rn安卓打包還是用Android Studio,使用vscode打包坑太多了。)
針對異常點進行分析。
Android Studio CPU 分析器 示例圖(圖片來源https://developer.android.com/)
6.資料彙總
資料顯示CPU的負擔過重,tracking導致程式有阻塞現象。
實際上大家一直認為是完全由於渲染壓力大導致的頁面卡頓,(渲染是RN 整個框架的瓶頸),報表資料顯示的恰恰相反,對於人臉識別,GPU並沒跑滿,圖形介面的渲染工作只有部分由GPU進行的,當tracking阻塞後會暫時等待發生卡頓,再逐個完成canvas 關鍵點渲染定位,呼叫介面,取得返回資料後渲染資訊卡片和執行動畫時導致第二次輕微卡頓(RN渲染卡頓),然後效能反應正弦函式波動,同時卡頓和不流暢現象消失。
導致“拍腦袋”定位問題就是因為前端同事對於日誌和資料分析工具的使用是普遍不夠的。
7.定位問題
定位問題的方法有多種,像大家常用的二分查詢法(二分註釋、二分回滾)。或者 斷點除錯、分析日誌。都可以有效的幫助我們快速定位問題。
那麼通過資料的分析以及工具提供的關鍵類,我們也是比較清晰的找出了問題:資訊卡片動畫+canvas特效+人臉識別相關函式。
8.分析問題
原有的實現方式:引入全部的相關js,new多個tracking.objectTracker來檢測人臉、眼睛、嘴的區域。在通過canvas實現人臉關鍵點的展示效果
Tracking.js檔案目錄示意圖
而對人臉進行採集。Tracking.js 是使用 CPU 進行計算的,在影像的矩陣運算效率上,相對 GPU 要慢一些。
此時,有了資料的支撐,決定替換人臉識別框架層配合RN進行嘗試性優化,採用face-api.js
face-api.js
基於 TensorFlow.js 核心,實現了三種卷積神經網路架構,用於完成人臉檢測、識別和特徵點檢測任務;其內部實現了一個非常輕巧,快速,準確的 68 點面部標誌探測器。支援多種 tf 模型,微小模型僅為 80kb。另外,它還支援 GPU 加速,相關操作可以使用 WebGL 執行。
核心原理是針對人臉檢測工作實現了一個 SSD(Single Shot Multibox Detector)演算法,它本質上是一個基於 MobileNetV1 的卷積神經網路(CNN),在網路的頂層加入了一些人臉邊框預測層。
face-api面部標誌探測器(圖片來源官方文件)
確認替換後,針對於React Native執行緒排程做一下調優,為了方便理解,我簡單繪製了一個示意圖,講解下流程:
• JS Thread:React 等 JavaScript 程式碼都在這個執行緒執行。
• Bridge:連線橋,具有非同步,序列化,批處理的特點
• Shadow Thread:進行佈局計算和構造 UI 介面的執行緒。
• Native modules提供 Native 功能(比如相簿、藍芽)
• UI Thread:Android/iOS(或其它平臺)應用中的主執行緒。
ReactNative執行緒示意圖
比如我們繪製一個UI,JS thread會先對其序列化,形成一條UIManager.createView 訊息,然後通過Bridge發到Shadow Thread。Shadow Tread接收到這條資訊後,先反序列化,形成Shadow tree,再轉換原生布局資訊,傳給UI thread。
而UI thread 拿到訊息後,同樣先反序列化,然後根據所給佈局資訊,進行繪製。
而這一系列都強依賴於 bridge,像高度計算、UI更新每次的操作都通過 bridge傳遞,任務一多,就會生成任務佇列,非同步操作批量處理,一些前端的更新很難及時反應到 UI 上,特別是類似於更新頻率較高的動畫操作,任務較多,很難保證每一幀及時渲染。
那麼,優化的方向:
1.減少 JS Thread 和 UI Thread 之間的非同步通訊,或者減少較少JSON的大小
2.儘量減少 JS Thread 側的計算
9.解決問題
整體解決方案是face-api替代tracker;React Native做一下調優。下面主要分三步講下React Native調優。
1.開啟動畫原生驅動
useNativeDrive: true
JS Thread 和 UI Thread 之間是通過 JSON 字串傳遞訊息的。對於一些非佈局的屬性、直接事件,(useNativeDriver 這個屬性只能使用到只有非佈局相關的動畫屬性上,例如 transform 和 opacity。佈局相關的屬性,比如說 height 和 position 相關的屬性,開啟後會報錯。)比如人臉識別成功,人員資訊卡片動畫,我們可以使用 useNativeDrive: true 開啟原生動畫驅動。
Animated.timing(this.state.animatedValue, { toValue: 1, duration: 500, useNativeDriver: true, // <-- Add this }).start();
通過啟用原生驅動,我們在啟動動畫前就把其所有配置資訊都傳送到原生端,利用原生程式碼在 UI 執行緒執行動畫,而不用每一幀都在兩端間來回溝通。如此一來,動畫一開始就完全脫離了 JS 執行緒,因此此時即便 JS 執行緒被卡住,也不會影響到動畫了。
2.使用互動管理器 InteractionManager
使用InteractionManager將部分需要優化的任務在互動操作和動畫完成之後再執行,比如:會場分佈的跳轉動畫。目的是平衡複雜任務和互動動畫之間的執行時機。
const handle = InteractionManager.createInteractionHandle();// 執行動畫... (runAfterInteractions
中的任務現在開始排隊等候)// 在動畫完成之後開始清除控制程式碼:InteractionManager.clearInteractionHandle(handle);// 在所有控制程式碼都清除之後,現在開始依序執行佇列中的任務
根據官方解釋的解釋:runAfterInteractions接受一個回撥函式,或是一個PromiseTask物件,該物件返回一個Promise。如果提供的引數是一個PromiseTask, 那麼即便是非同步的它也會阻塞任務佇列,直到它執行完畢後,才會執行下一個任務。這樣就可以按需優化動畫流暢度。
3.重新渲染
首先,RN與React中,當父元件中觸發setState, 未修改任何state中的值也會引起所有子元件的重新渲染, 或者當父元件傳給子元件的props發生改變, 不管該props是否被子元件用到, 也都會去重新渲染子元件。
那麼,針對重新渲染問題,使用PureComponent和shouldComponentUpdate對於普通函式進行優化;對於hook元件使用memo優化;
至驗證後整體得到改善,互動較為流暢,達到基本效能指標。現在主要是針對於概率性問題是否復現。尋求測試同事的幫助。
10.驗證問題(效能監控平臺的應用)
首先為什麼要使用效能監控平臺:1.處理重複資訊,避免一些問題在多個APP上重複處理,或者在一個APP上反覆處理;2持續捕捉重要可疑資訊,提升效率,降低人力成本。
其次什麼時候、什麼場景下使用效能監控平臺:除了測試、運維需要使用效能監控平臺,開發者也要學會利用效能監控平臺去輔助定位解決問題,這裡推薦兩個方案:
1. Google Android Vitals + Firebase
Android vitals是Google為提高Android裝置穩定性和效能而推出的一項計劃, Google Play 的Android vitals控制檯可以突出顯示崩潰率、ANR 發生率、喚醒次數過多以及喚醒鎖定被卡住等指標。包含了開發者常用功能,關鍵是不侵入程式碼,應用比較方便。
而Firebase除此之外還可以獲取詳細的自定義崩潰報告資料,以瞭解應用中出現的崩潰情況。該工具會按相似堆疊軌跡將崩潰分門別類,並根據崩潰對使用者所產生影響的嚴重程度進行分級。除了接收自動生成的報告外,還可以通過記錄自定義事件來獲知導致應用崩潰的操作。
Vitals + Firebase功能對比圖(圖片來源官網)
所以一般情況下使用Android Vitals可處理大部分簡單問題,並可搭配Firebase靈活處理自定義事件。
不太方便的是Google國內限制,需要公司申請專線跨境聯網,並且網路波動時,經常需要身份驗證(這點比較煩人)。
費用上:Android Vitals使用免費,但是需要25$註冊開發者賬號;Firebase有免費版和付費版。適合外企、跨國公司或者有相關資質的公司研發使用。
2.友盟+ U-APM
2.1產品概述:
由於Google國內限制,很多企業沒有網路報備不能連線外網,那麼友盟+ 的U-APM也可以完美滿足以上需求。針對於我的專案,我這裡是選擇接入友盟+SDK協助問題檢測。
友盟的推送和統計在業界做的是比較好的,而比較熟悉友盟的朋友應該瞭解U-APP的穩定性功能,那麼U-APM就是友盟+在U-APP穩定性功能的基礎上升級推出的一款面向開發者監控應用的穩定性資料產品。
U-APM核心技術與優勢(圖片來源友盟官網)
為什麼選擇友盟+ U-APM 應用效能監控平臺:
該產品不僅通過發現線上問題-快速定位問題-高效解決問題打造體系化線上質量監控平臺。而且擁有支援實時監控線上App崩潰趨勢,7*24小時監控告警與修復驗證,復現使用者崩潰現場,關鍵環節的重點監控,修復測試等特點。
重點還在於有阿里技術的加持,可以提供長期穩定的產品迭代和專案服務及專家諮詢能力。貼心啊,企業工程化需要的就是長期穩定!小廠的產品可能用著用著就找不到人了。
U-APM與競品功能對比(圖片來源友盟官網)
2.2開發準備
如果之前有使用過U-APP的,可以直接檢視官網的升級說明按體驗U-APM;那麼沒有使用過友盟產品的需要到 【友盟+】官網 註冊並且新增新應用,獲得AppKey。
注:請一定認真閱讀U-APM合規指南,滿足工信部相關合規要求。避免因隱私政策風險導致APP下架。
2.3整合SDK
maven自動整合:
maven自動整合是比較簡單快速的
首先在工程build.gradle配置指令碼中buildscript和allprojects段中新增【友盟+】sdk 新maven倉庫地址。如下圖。
然後在工程App 對應build.gradle配置指令碼dependencies段中新增SDK庫依賴,是不是很簡單呢。
- dependencies {
- implementation fileTree(include:['*.jar'], dir:'libs')
- // 下面各SDK根據宿主App是否使用相關業務按需引入。
- implementation 'com.umeng.umsdk:common:9.4.4'// 必選
- implementation 'com.umeng.umsdk:asms:1.4.1'// 必選
- implementation 'com.umeng.umsdk:apm:1.4.2' // 必選
- }
**手動Android Studio整合:
那麼我這裡是採用的手動整合**
(1)首先在選擇U-APM SDK元件並下載,解壓.zip檔案得到相應元件包
得到如下檔案:
umeng-common-9.4.4.jar // 統計SDK 必選
umeng-asms-armeabi-v1.4.1.aar // 必選
以及apm目錄下的
umeng-apm-armeabi-v1.4.2.aar//U-APM SDK 必選
可如有UTDID需求,整合thirdparties下
utdid4all-1.5.2.1-proguard.jar UTDID服務的補充包
如需要ABTest模組,可整合common下
umeng-abtest-v1.0.0.aar ABTest模組
(2)在Android Studio的專案工程libs目錄中拷入以上jar包。
右鍵Android Studio的專案工程 —> 選擇Open Module Settings —> 在 Project Structure彈出框中 —> 選擇 Dependencies選項卡 —> 點選左下“+” —> 選擇元件包型別 —> 引入相應的元件包。
(3)在app的build.gradle檔案中引入相應的元件包。參考示例如下:
- repositories{
- flatDir{
- dirs 'libs'
- }
- }
- dependencies {
- implementation fileTree(include:['*.jar'], dir:'libs')
- implementation (name:'umeng-asms-armeabi-v1.4.1', ext:'aar')
- implementation (name:'umeng-apm-armeabi-v1.4.2', ext:'aar')
- }
注意:如果需要適配armeabi 以外的平臺,或者遇到了多CPU架構so庫載入失敗問題[SA10070],除了需要引入相應的包,還要分別下載並考入對應的.so檔案。
2.4許可權授予
按照官網教程授予如下許可權:
- <manifest ……>
- <uses-sdkandroid:minSdkVersion="8"></uses-sdk>
- <uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permissionandroid:name="android.permission.ACCESS_WIFI_STATE"/>
- <uses-permissionandroid:name="android.permission.READ_PHONE_STATE"/>
- <uses-permissionandroid:name="android.permission.INTERNET"/>
- <application ……>
2.5混淆設定
如果APP中使用了程式碼混淆,需要增加如下配置
- -keep class com.umeng.* { ; }
- -keep class com.uc.* { ; }
- -keep class com.efs.* { ; }
- -keepclassmembers class *{
- public<init>(org.json.JSONObject);
- }
- -keepclassmembers enum *{
- publicstatic**[] values();
- publicstatic** valueOf(java.lang.String);
- }
2.6初始化sdk
在rn的安卓原生的application.onCreate函式中呼叫基礎元件包提供的初始化函式:
- /**
- 注意: 即使您已經在AndroidManifest.xml中配置過appkey和channel值,也需要在App程式碼中調
- 用初始化介面(如需要使用AndroidManifest.xml中配置好的appkey和channel值,
- UMConfigure.init呼叫中appkey和channel引數請置為null)。
- */
- UMConfigure.init(Context context,String appkey,String channel,int deviceType,String pushSecret);
或者呼叫此預初始化函式
- public static void preInit(Context context,String appkey,String channel)
然後開啟日誌開關 - /**
- *設定元件化的Log開關
- *引數: boolean 預設為false,如需檢視LOG設定為true
- */
- UMConfigure.setLogEnabled(true);
至此即可使用卡頓分析功能、Java、Native崩潰分析、ANR分析功能等等基礎功能了。因為其原理通過主執行緒的響應時間,將有卡頓體驗的裝置資訊、卡頓日誌進行上報。那麼等待裝置上報後我們可以在web控制檯看到上傳的Error(列印SDK整合或執行時錯誤資訊),Warn(列印SDK警告資訊),Info(列印SDK提示資訊),Debug(列印SDK除錯資訊)。以及報表。
U-APM崩潰資訊日誌示例圖
但是從報文直接看錯誤堆疊非常麻煩, U-APM利用聚合演算法提供了卡頓模組的功能,篩選影響使用者量大的200個堆疊從棧頂到棧底雙向聚合,展示出現頻率前10的模組,子樹深度最多支援50層,幫助下挖詳細的卡頓模組資訊。
U-APM卡頓模組示例圖
除此之外,U-APM中還提供了啟動分析、記憶體分析、網路分析,使用者細查模組等高階功能。除了記憶體分析外是其他功能需要進行配置才能使用的。大家可以去體驗一下。
那麼最終通過U-APM也是順利的驗證問題、解決問題。完成了整個研發閉環。感興趣的話,可以免費體驗U-APM。
四.專案總結
1.不要盯著問題看。對於app的效能優化也好,系統優化也好。問題的表象可能是由於本質的副作用帶來的。例如,本專案中區域性現象是卡頓、不流暢,只盯著現象,我們很可能陷入優化困境,去優化渲染、減少canvas繪圖,甚至精簡業務。而最終突破我們的效能瓶頸是通過修改實現方式達成的,更適合業務場景、更能發揮機器效能。而這一切,需要資料去支撐。
2.用資料說話。不要憑感覺,去檢測效能問題、評估效能優化的效果,要有可量化的渲染效能評判標準,以及可量化、視覺化的優化工具。利用經驗去感覺、猜測對於團隊是沒有沉澱的,而資料和工具是可以傳承的。例如:對於優化效能如果沒有標準,對於結果沒有資料體現。那麼整體的工作是沒有意義的,成功與否全靠leader拍腦門決定。
3.使用低配置的裝置:同樣的程式,在低端配置的裝置中,相同的問題會暴露得更為明顯。例如:在前期安卓開發真機上並沒有卡頓現象,放在工業真機上才暴露出卡頓等問題。而對於高低端裝置都能帶來很好的使用者體驗,一直是一個很重要的問題。
4.權衡利弊:在能夠保證產品穩定、按時完成需求的前提下去做優化,投入產出比過高時,應採取其他方案,切勿過度優化。永遠不要忘記,優化效能的目的是提高使用者體驗,而不是炫技。
5.拋棄沉沒成本:對於研發中已經付出且不可收回的成本,不要影響未來的決策,例如:對於已經使用track開發的人臉識別模組,資料證明選型影響到了效能。投入產出比在可接受範圍內,越早替換預期收益越高。