admin管理员组

文章数量:1588266

项目场景:

二期会议室需要替换成OD20的会议平板,为了方便安装,给了framework的同事一个会议室版本的无线投屏APK,作为系统应用打包进去了。

将无线投屏升级到现在调试的版本,启动后,底部通知栏就duang duang duang的弹,随之系统crash。

环境:OD20会议平板
角色:32位的会议室版本Apk,64位调试版Apk


原因分析:

前置知识:

  1. 不借助adb,不能直接在launcher里卸载系统应用,进入设置卸载覆盖安装的,可将app恢复成系统应用
  2. 系统应用如果短时间内反复crash,会导致系统崩溃
  3. 应用崩溃会重启的原因以及规避方案参考:Android 解决应用崩溃后重启的问题
  4. 项目中直接/间接引用到的第三方库,都会在 Android Studio 的 External Libraries 下找到
  5. 动态链接库要提供整套,不然会crash
  6. Service的标识:包(package)名}/{包名}.{服务(service)名称}

Step 1

开始分析,从报错堆栈:

03-26 17:01:33.352 28825 28825 D AndroidRuntime: Shutting down VM
03-26 17:01:33.353 28825 28825 E AndroidRuntime: FATAL EXCEPTION: main
03-26 17:01:33.353 28825 28825 E AndroidRuntime: Process: com.hikvision.cast, PID: 28825
03-26 17:01:33.353 28825 28825 E AndroidRuntime: java.lang.RuntimeException: Unable to get provider androidx.lifecycle.ProcessLifecycleOwnerInitializer:
 java.lang.ClassNotFoundException: Didn't find class "androidx.lifecycle.ProcessLifecycleOwnerInitializer" on path: DexPathList[[zip file "/system/app/i
nstcast/instcast.apk"],nativeLibraryDirectories=[/system/app/instcast/lib/arm, /system/app/instcast/instcast.apk!/lib/armeabi-v7a, /system/lib, /vendor/
lib]]
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at android.app.ActivityThread.installProvider(ActivityThread.java:6288)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at android.app.ActivityThread.installContentProviders(ActivityThread.java:5851)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5772)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at android.app.ActivityThread.-wrap1(Unknown Source:0)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1661)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:105)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at android.os.Looper.loop(Looper.java:164)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at android.app.ActivityThread.main(ActivityThread.java:6541)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at java.lang.reflect.Method.invoke(Native Method)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:775)
03-26 17:01:33.353 28825 28825 E AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.lifecycle.ProcessLifecycleOwne
rInitializer" on path: DexPathList[[zip file "/system/app/instcast/instcast.apk"],nativeLibraryDirectories=[/system/app/instcast/lib/arm, /system/app/in
stcast/instcast.apk!/lib/armeabi-v7a, /system/lib, /vendor/lib]]
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:93)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        at android.app.ActivityThread.installProvider(ActivityThread.java:6273)
03-26 17:01:33.353 28825 28825 E AndroidRuntime:        ... 10 more
03-26 17:01:33.357  2414  5634 W ActivityManager: Process com.hikvision.cast has crashed too many times: killing!

结合缺陷表现来看,好的没谁了,就是它!

开始分析为什么找不到 androidx.lifecycle.ProcessLifecycleOwnerInitializer
混淆?分包?

垂死病中惊坐, androidx.lifecycle:lifecycle-process:2.2.0 我都没用到!

难道是LiveEventBus间接引用到了?但不应该啊,AS的 External Libraries 下也没找到(AS你病了吗?快醒醒)

罢了罢了,先解决问题,把这个库引用上,防混淆,打入主dex,笑嘻嘻的恢复出厂设置,install,open…
… … duang duang duang,系统crash

佛祖心中坐,卧槽心中留

Step 2

不能轻易放弃,几经折腾,猜测有可能是前面的crash导致的,最终表现在这个Class没找到?就跟调试时经常出现的JNI的某方法没实现问题一样。往上一瞟:

2021-03-29 18:12:30.487 3737-3737/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.hikvision.cast, PID: 3737
    java.lang.UnsatisfiedLinkError: dlopen failed: "/data/app/com.hikvision.cast-5Xy7PPXmWlo655vBQWul3Q==/lib/arm64/libmmkv.so" is 64-bit instead of 32-bit
        at java.lang.Runtime.loadLibrary0(Runtime.java:1016)
        at java.lang.System.loadLibrary(System.java:1657)
        at com.tencent.mmkv.MMKV.n(SourceFile:2)
        at com.tencent.mmkv.MMKV.l(SourceFile:3)
        at com.hikvision.cast.basic.utils.f.h(SourceFile:1)
        at com.hikvision.cast.App.onCreate(SourceFile:4)
        at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1118)
        at android.app.LoadedApk.makeApplication(LoadedApk.java:981)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2725)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
        at android.os.Handler.dispatchMessage(Handler.java:105)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6541)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:775)

被腾讯坑了?MMKV拿了个32位的动态链接库,放到armv8a目录下?
很快啊,打开Ubuntu虚拟机一看,是64位的没错。

而且冷静下来一看,log提示是说拿64位的代替32位了。

Step 3

难道是链接库的CPU架构支持导致?一顿重启、重装、恢复出厂设置、覆盖安装后,得出以下结论:

Step 4

基本有个定论了,32位的系统apk只能升级到32位的。

但是还有个小彩蛋,为什么有两个投屏码?而且杀死进程后,又出现系统崩溃的问题。
尝试解决:重启试试?果然重启大法好。那多半是framework的缓存和策略问题了。

再扩展思考下:
和 install 和 install -r 有关系吗?
—> 不加-r 都安装不上

adb: failed to install G:\WorkSpace\Apps\InstCast-Project-2.2-Dev\app-s\build\outputs\apk\meeting\release\cast_1.4.7_meeting_2021_03_30_v7_release.apk: Failure [INSTALL_FAILED_ALREADY_EXISTS: Attempt to re-install com.hikvision.cast without first uninstalling.]

Step 5

  1. 猜测是Service没有被替换的原因,使用老的分支(要被升的那个apk),将Service替换成新的com.hikvision.cast.service.InteractService,确实没覆盖上,但杀死新app后不会触发系统崩溃。
  2. 在新的分支,替换成display.interactive.cast.service.InteractService,投屏码显示****,并没有覆盖原来的Service

分析新老分支:
相同:package 都是 com.hikvision.cast
不同:旧分支的代码目录是display.interactive.cast,新分支代码目录是com.hikvision.cast(看dex里的结构,确实是用的代码目录)

但是!服务的标识,和代码的包名也是相关的。至此,应用层角度,能排查的和能试验的都完毕了,总结一下,去咨询一下Framework的专家们。

整体排查历程如下所示:


整理总结

出错现象:

  1. OD20设备,系统应用覆盖安装后崩溃,不断被系统恢复,然后系统crash

现象总结:

  1. 使用32位动态链接库的系统应用不能直接升级到64位,只能从32位升级到32位(32+64位的都放在包里也不行)
  2. 预置系统应用的Service不能被覆盖(有两个投屏码),杀新app的进程,会导致系统crash

可能能作为突破的点:

  1. 首次安装系统/恢复出厂设置会出现,restart后,恢复到预装(启动预装应用),再覆盖安装,启动不出错,杀进程也不出错
    (系统应用读取动态链接库的顺序?手动升级系统内置应用还是系统应用吗?为何会固步自封在被覆盖app支持的CPU架构上,有缓存还是没正确的解析新apk?)
  2. 如何才能正确的停止系统服务的Service
  3. 有没有类似手机厂商的,预装应用但不是系统应用的方案

想要得到的结果:

  1. 系统应用能从32位正确的更新到64位
  2. 系统应用的Service正确的覆盖

解决方案:

经过Framework同事的答疑解惑后,覆盖安装的问题应该是系统应用动态链接库的读取策略有一些问题,Service的问题应该是系统应用的服务被替换后没有清理干净导致。
—> 但可能不对,参考【拓展:2.Android 调用so库全过程】结合系统应用思考可得,64位机为了兼容32位系统,会跑一个兼容的32位的zygote来孵化应用进程,而系统应用主动拉起App导致ClassLoade根据CPU架构去apk里释放so的时候就出事了
<可以通过ClassLoader打印nativeLib路径>

采取如下方案:

  1. Crash:充分非系统应用测试后,非系统应用能较好的应对这些奇怪问题。分析无线投屏app的场景定位,后续采用集成到系统包,但不作为系统应用的方式
  2. Service:因为后期打入系统应用也不会再用这么老的版本,Service标识不变
  3. 继续跟进Framework从根本上解决该问题,完成闭环

现存的风险点:

  1. 二期会议室的十几台试点OD20存在升级风险
  2. 早期领导办公室的把投屏打包为系统应用的版本存在升级风险

拓展:
1.Android动态链接库查找顺序
2.Android 调用so库全过程


思考总结

  1. 分析日志视野要广一些
  2. 升级迭代版本不宜过大

根因定位

Service重启恢复
1.启动时,serviceDoneExecutingLocked调用onStartCommand的返回值设置ProcessRecord的stopIfKilled和deliveredStarts字段,构建重启数据
2.Service挂掉后,触发AMS的Binder讣告机制,handleAppDiedLocked,最终调用ActivityServices.killServicesLocked负责清理和重启需要的Service
3.限制:超过两次都失败,就不再重启Service,系统APP不受限制;像START_NOT_STICKY这种,登记会再次被取消
4.最近任务列表中移除,也会触发Service重启,仅仅多了onTaskRemoved回调,若不需要重启,设置stopWithTask
5.START_REDELIVER_INTENT重发所有Intent,且重启延时较大,START_STICK一般固定1s

RescueParty救援程序
1.触发 kill用户进程 和 恢复出厂设置
2.分为5个救援优先级,system_server 在5分钟内重启5次以上调整一次级别,永久性系统应用在30秒内崩溃5次以上调整一次级别
3.禁用场景:eng版本、userdebug版本、 persist.sys.disable_rescue为true

Android的so文件加载机制
1.通过primaryCpuAbi确定应用程序的abi,影响系统寻找so文件目录的流程(通过apk包里包含的so库的架构来决定app的primaryCpuAbi的值)
2.nativeLibraryDir -> 32(vendor/lib,再system/lib)/64(vendor/lib64,再system/lib64)
3.不同的cpu架构的so文件不能够混合使用
4.应用运行64位进程,无法使用32位abi的so文件,反之亦然
5.zygote根据primaryCpuAbi确定启动那个zygote,分为zygote32/zygote32_64/zygote64/zygote64_32

综上所述,导致问题的根本原因是(以32位系统应用升级为64位为例):
1.32位系统应用升级,非正常stopService,因设置了persistent,触发Binder讣告,AMS重启Service
2.32位进程被拉起,但通过apk获取的primaryCpuAbi是64位(即使有32/64位也是优先64位),读取64位的so文件目录,但进程是32位的又crash
3.短时间设置了persistent反复crash,触发RescueParty,并不断提高救援优先级,导致系统Recorvery

至于换Service的路径,出现两个投屏码是很明确的,因为标识的一部分就是路径,一个是被AMS拉起的旧App的Service,一个是新App启动的Service。

最终解决

知道了导致问题的根本原因,最终解决方案也就灵光乍现:
1.在无线投屏App还未形成一个稳定版本的时候,打成系统应用去掉persistent;但为了保活,附带一个保活App,20s周期检测无线投屏App是否运行中,挂掉的话拉起App,为了保证系统稳定性,重复拉起10次失败则退出保活。
2.无线投屏App加入保活机制,且加入4分钟连续crash5次,则终止保活的熔断机制,避免导致系统的Recorvery。

本文标签: 升级到系统androidBug