作者:拔根
優酷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 篇移動技術實踐&乾貨給你思考!