尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Android 启动优化踩坑记:百度地图 SDK 延迟初始化必崩 std::bad_cast,凶手竟是 AVM SDK 里静态链接的 libc++

Android 启动优化踩坑记:百度地图 SDK 延迟初始化必崩 std::bad_cast,凶手竟是 AVM SDK 里静态链接的 libc++ 一、背景一次合理的启动优化车机 App 启动速度优化思路很常规把非首屏必需的 SDK 初始化挪到首帧渲染之后。百度地图 百度导航 SDK 首当其冲——它初始化耗时高、首屏又用不到于是写了一个延迟初始化器object DeferredSdkInitializer { private val mainHandler Handler(Looper.getMainLooper()) fun startAfterFirstFrame(application: Application) { // 首帧后延迟初始化百度地图/导航 SDK mainHandler.postDelayed({ SDKInitializer.setAgreePrivacy(application.applicationContext, true) initBaiduNavi(application) SDKInitializer.initialize(application.applicationContext) SDKInitializer.setCoordType(CoordType.BD09LL) }, 0) } }在MainActivity第一帧绘制完成后触发。逻辑看起来无懈可击initialize只是从Application.onCreate挪到了首帧后执行内容完全一样。然后崩了。必现。二、现象SIGABRT std::bad_castF DEBUG : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr -------- F DEBUG : Abort message: terminating with uncaught exception of type std::bad_cast: std::bad_cast崩溃栈关键帧#05 liba2.so (__cxa_throw124) #07 libc_shared.so (std::__ndk1::locale::use_facet176) #08 libBaiduMapSDK_base_v7_6_4.so (_baidu_vi::CVCMMap::UrlEncode648) #09 libBaiduMapSDK_base_v7_6_4.so (Java_com_baidu_mapsdkplatform_comjni_util_JNIMD5_encodeUrlParamsValue80) #17 com.baidu.mapsdkplatform.comapi.BMapManagerInternal.permcheck86 #19 com.baidu.mapsdkplatform.comapi.Initializer.initialize126 #21 com.baidu.mapapi.SDKInitializer.initialize12 #23 xxxx.DeferredSdkInitializer.startAfterFirstFrame$lambda$124调用链SDKInitializer.initialize → permcheck → AppMD5.encodeUrlParamsValue → JNI → libBaiduMapSDK_base_v7_6_4.so 的 UrlEncode → locale::use_facet → 抛 std::bad_cast。第一反应是用 try-catch 兜住不就行了——不行。这是native 层 SIGABRTJava 层的runCatching根本拦不住进程直接死。三、排查崩溃栈里的双面间谍仔细观察崩溃栈发现一个诡异的地方use_facet这个函数在libc_shared.so里执行#07 帧但它抛异常时调用的__cxa_throw却在liba2.so里#05 帧等等liba2.so是谁百度地图 SDK 的依赖吗用 PowerShell 暴力搜了一下所有 AARGet-ChildItem -Path . -Recurse -Filter *.aar | ForEach-Object { $zip [System.IO.Compression.ZipFile]::OpenRead($_.FullName) $entries $zip.Entries | Where-Object { $_.FullName -match liba2\.so|libc\\_shared\.so } if ($entries) { $($_.FullName): $($entries.FullName -join , ) } $zip.Dispose() }结果D:\work\xxxx\mod_avm\libs\avmsdk-release.aar:jni/arm64-v8a/liba2.so, jni/arm64-v8a/libc_shared.soliba2.so 来自 360 环视AVMSDK再验证它的符号$bytes [System.IO.File]::ReadAllBytes($so) $text [System.Text.Encoding]::ASCII.GetString($bytes) __cxa_throw: $($text.Contains(__cxa_throw)) # True use_facet: $($text.Contains(use_facet)) # True std::bad_cast: $($text.Contains(std::bad_cast)) # True libc_shared.so: $($text.Contains(libc_shared.so)) # False ← 不依赖共享库结论浮出水面liba2.so 静态内嵌了一份完整的 libc而且把__cxa_throw等符号导出到了全局符号表否则 libcshared.so 的代码不可能调用到它。四、根因两份 libc 的符号抢占进程里出现了两份 libc库libc 来源使用方libc_shared.so共享版动态链接百度地图 SDK、火星 XLog 等liba2.so静态内嵌一份 导出符号360 环视 AVM SDKAndroid 动态链接器对重名符号采用先加载先得first-loaded wins规则后加载的库导出的重名符号不会覆盖全局符号表中已有的符号反过来后加载的库内部对同名符号的引用会被解析到先加载的库。于是时序就成了关键优化前同步初始化Application.onCreate里百度 SDK 先执行 →libc_shared.so先加载__cxa_throw先进全局表 → 之后liba2.so加载符号抢不走 → 两边各用各的 libc不崩。优化后延迟初始化车机场景下开机/倒车会自动触发 360 环视liba2.so先加载等百度 SDK 初始化时libc_shared.so加载其未定义的__cxa_throw被解析到了liba2.so 的静态 libc。百度 SDK 的UrlEncode → locale::use_facet抛bad_cast时异常对象由 liba2.so 的 RTTI 表处理与 libcshared.so 的 typeinfo 完全不一致 →std::bad_cast→ SIGABRT。本质延迟初始化改变了 native 库的加载顺序暴露了 AVM SDK 静态链接 libc 且未隐藏符号的隐患。五、解决方案治标立刻可用让共享版 libc 永远第一个加载在Application.onCreate最开头super.onCreate()之后第一行任何其他 native 库加载之前预加载override fun onCreate() { super.onCreate() // 预加载 libc_shared.so让共享版 libc 符号最先进入全局符号表 // 避免 avmsdk 的 liba2.so静态内嵌 libc 且导出符号抢先加载后 // 百度地图 SDK 抛异常时串到 liba2.so 的异常处理导致 std::bad_cast 崩溃。 System.loadLibrary(c_shared) ... }原理libc_shared.so的符号先进全局表之后liba2.so无论何时加载其静态 libc 符号都抢不走__cxa_throw的解析权百度 SDK 与 AVM 各自用自己的 libc互不干扰。实测编译验证通过崩溃消失。代价预加载一个 1~3MB 的 so约 10~30ms对启动耗时影响很小。治本需要厂商配合修 liba2.so 的符号导出方案 A最优liba2.so 改为动态链接公共libc_shared.soDT_NEEDED全进程只有一份 libc彻底无冲突。这也是 NDK 官方推荐做法。方案 B次优静态链接但隐藏符号-Wl,--exclude-libs,ALL或 version script或-fvisibilityhidden-Bsymbolic让静态 libc 的符号只在本库内部可见、不进全局符号表。此时即使与公共 libc 共存也互不干扰。⚠️注意单独把 libc改个名字如 libcext.so是不够的——只要它还导出__cxa_throw等符号照样进全局符号表跟公共版重名冲突依旧只是变成了谁先加载谁赢。六、经验总结延迟初始化第三方 SDK 前先想清楚它的 native 库加载顺序。同一个 SDK 同步初始化没问题、延迟必崩大概率不是 SDK 的问题而是加载顺序/环境变了。runCatching、try-catch 拦不住 native 崩溃SIGABRT/SIGSEGV崩溃处理要放到崩溃栈层面分析。排查 native 崩溃先看崩溃栈里符号归属use_facet在 libcshared.so、__cxa_throw在别的 so基本就是两份 libc 共存 符号抢占的典型特征。类似的还有std::__ndk1::locale相关崩溃。对厂商 AAR 里的 so 保持警惕接入新 AAR 时可以用llvm-readelf --dyn-syms xxx.so检查是否导出了异常的 libc 符号--exclude-libs,ALL是第三方 prebuilt 库的标配要求写入采购/对接 checklist。多进程、多 SDK 共存的 Android 工程libc_shared.so建议在 Application 最早期统一预加载一劳永逸规避符号抢占类的玄学崩溃。
返回列表