作者:拔根
优酷 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 篇挪动技术实际 & 干货给你思考!