作者:拔根
優酷App接入支付寶小程序框架,擴展了優酷App的能力。但由於內置小程序sdk過程中,優酷App和支付寶App平台運行時環境存在差異,帶來了以下幾大問題:
- 小程序sdk包體積較大,遠遠增加了優酷APP的包大小;
- 小程序容器啓動後,線程數暴增,疊加優酷主APP場景線程,引發crash率增高;
- 初始化小程序引擎會影響優酷APP啓動速度和佔用內存。
為解決以上問題,優酷勢必要在包大小、線程數、內存等方面有不一樣的處理。接下來,本文就將為大家介紹優酷在面臨這些差異與問題時的解決方案。
遠程化SO
在內置過程中,我們發現支付寶小程序框架大概佔了23MB。比較理想的方案是全部遠程化,這樣不佔用優酷App的包大小,但目前優酷短時間內暫不具備全部遠程化的條件。通過分析,我們發現,23MB的空間裏面so佔了7MB,而相對於代碼和資源文件,so相對獨立。因此我們的方案如下:
- so間存在依賴關係,事先需要收集好,加載的時候按序加載;
- 打包過程中排除這些so,上傳到服務器,並記錄SO的相關信息。其中包括上傳後的遠程下載地址、md5值;
- 用户打開APP進入小程序的時候,從服務器下載這些so,存儲到指定目錄;
- 按照事先收集好的依賴關係,依次加載so。
其中,比較複雜的就是 so 依賴關係分析,我們的處理過程如下:
1、分析依賴關係
objdump -x *.so|grep -i needed|awk '{print $2}'
objdump是Linux下的反彙編目標文件或者可執行文件的命令,它以一種可閲讀的格式讓你更多地瞭解二進制文件可能帶有的附加信息,類似的還有readelf命令。依賴的 so 有的是操作系統的,有的是其他aar包裏面的,其中操作系統的就不用關心了,其他aar包裏面的so需要進行記錄。
2、收集依賴關係:
| aar包名 | SO名稱 | 依賴SO名稱 | 有效依賴 |
|---|---|---|---|
| com.AAA:aaa-build | libA1.so | libopenssl.so libandroid.so liblog.so libm.so libstdc++.so libEGL.so libGLESv2.so libOpenSLES.so libz.so libdl.so libc.so | libopenssl.so |
| libA2.so | libB1.so liblog.so libandroid.so libstdc++.so libm.so libc.so libdl.so | libB1.so | |
| com.BBB:bbb-build | libB1.so | libopenssl.so libc++_shared.so liblog.so libz.so libc.so libm.so libdl.so |
大家可以參考上述表格來對每個so進行依賴信息的收集,最終這些so的關係可以組成一個網狀型。如下所示:
3、分層依賴關係:
分層依賴關係的設計原則如下:
- 第一層:不允許依賴任何其他aar包裏面的so,只允許依賴操作系統的so或者無依賴的so;
- 第二層:只允許依賴第一層收集的so和操作系統的so;
- 第三層:只允許依賴第一層、第二層收集的so和操作系統的so。
依此類推。
| 第一層(xx個) | 第二層(xx個) | 第三層(xx個) | 第四層(xx個) | 第五層(xx個) |
|---|---|---|---|---|
| A1 | B1 | C1 | ||
| A2 | B2 | C2 | ||
| A3 | B3 | |||
| A4 |
4、代碼實現
private static String[] LIB_NAMES = {
// 第一層, 不依賴其他so
"A1",
"A2",
"A3",
"A4",
// 第二階段, 依賴前一階段加載完畢
"B1",
"B2",
"B3",
// 第三階段, 依賴前兩個階段加載完畢
"C1",
"C2",
// 第四階段, 依賴前三個階段加載完畢
...
// 第五階段, 依賴前四個階段加載完畢
...
};
public static void ensureAllSoLoaded() {
try {
for (String lib_name : LIB_NAMES) {
Log.d(TAG, "lib_name:" + lib_name);
System.loadLibrary(lib_name);
}
} catch (Throwable throwable) {
Log.e(TAG, "loadLibrary lib_name exception:");
throwable.printStackTrace();
}
}
5、小結
在集成小程序過程中,優酷通過遠程化so,減小了7MB的包體積。相應的,用户首次進入小程序時,需要一定的等待時間,影響了一點用户體驗。
注入線程池
在某些手機中,尤其是華為手機,針對APP線程數有上限,超過就會crash。基於此,優酷APP通過統一線程池對線程進行管控的,當線程數達到一定數量時,會通過排隊方式執行任務,而不是繼續新增線程。
由於業務屬性不同,優酷播放器頁面啓動線程數量很多,使用過程中一旦進入此頁面,線程數最高可以達到300+。該頁面如果存在小程序入口,一旦啓動優酷小程序,線程數會在原來基礎上暴增100左右。這樣就很容易達到APP線程數上限,導致 java.lang.OutOfMemoryError 錯誤。
事實上,通過線上監控平台發現,確實有不少此原因導致的Crash。那麼,怎麼解決這個問題呢?
一個比較好的方案是希望將支付寶小程序框架的線程納入到優酷的統一線程池來管理。這樣,當支付寶小程序框架希望執行任務時,只需要把任務提交給優酷線程池執行,而不用真正的創建線程。優酷線程池根據系統的情況決定是新建線程還是排隊執行。
通過和支付寶的同事溝通,他們也認同這種方案。由支付寶小程序框架提供接口,將優酷統一線程池注入給小程序框架。當底層判斷已經有優酷統一線程池,就直接使用,不再新建。
實現如下:
public class MyThreadPoolManager implements IThreadPoolManager {
private static final int NCPU = Runtime.getRuntime().availableProcessors();
private ThreadPoolExecutor mThreadPoolExecutor;
@Override
public ThreadPoolExecutor createExecutor(ScheduleType scheduleType) {
if (mThreadPoolExecutor == null) {
mThreadPoolExecutor = YKExecutorService.from("miniappSdk", 2 * NCPU + 1, 2 * NCPU + 1 ,1000, TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
}
return mThreadPoolExecutor;
}
}
// 支付寶小程序框架提供接口,注入優酷線程池。
TinySdk.setThreadPoolManager(new MyThreadPoolManager())
上線前後,從以下數據中可以看到,效果非常顯著。幾乎各個模塊都減少了90%的crash。數據對比效果如下:
懶加載
小程序上線初期,業務量不大,如果一啓動優酷APP就立刻初始化小程序框架,影響了啓動速度,浪費內存。因此我們的策略是採取懶加載模式。流程如下所示:
1、遠程依賴檢查
小程序運行是有遠程依賴的,下載這些遠程依賴需要流量和等待時間。因此我們會在首次打開小程序業務的時候,彈窗告訴用户這些信息,並詢問是否同意:如果用户不接受,則不進入小程序APP,關閉彈窗;如果用户接受,則下載遠程依賴,並在下載完成後,自動完成後續流程。
2、小程序運行條件檢查
最終加載小程序頁面的是渲染內核,當運行條件檢查發現不具備運行條件,也就是冷啓動小程序時,就必須先判斷渲染內核是否已經加載完成 (需注意:渲染內核不止小程序一個業務依賴,有可能被其他業務先加載)。
渲染內核初始化完成後,我們繼續進行小程序框架的初始化。等到這一步正常結束,小程序運行條件才完全具備。我們才可以正常加載小程序頁面。
初始化後,通過變量標識,當小程序退出時,並不會回收全部內存。所以第二次及以後就是熱加載小程序了。由於省略了渲染內核的初始化和小程序框架初始化,速度也會快很多。
總結和展望
優酷APP主要為用户提供內容服務,在集成支付寶小程序框架後,擴大了能夠提供內容服務的範圍,進一步擴大了優酷作為平台的能力。後續優酷仍會繼續在以下技術方向努力:
- 將整體小程序框架打包放到雲端,完全消除集成小程序框架對優酷app包大小的影響。
- 增加更多通用JSAPI,讓業務方使用更加方便。
- 閒時加載取代懶加載,提供更好的業務體驗,形成正反饋。
關注我們,每週 3 篇移動技術實踐&乾貨給你思考!