通过bugly日志分析,了解到的Android盲区知识

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 通过bugly日志分析,了解到的Android盲区知识
# GLThread 1371(29457) SIGSEGV(SEGV_MAPERR)
 #00 pc 0000000000aa3f58 /data/app/~~6_tV_wy5KiC1VMeFQPNi2g==/com.yddm.haiwai.game-bHeiJjIVfYMTmPlQwANbrw==/split_config.arm64_v8a.apk
 #01 pc 0000000000aafb84 /data/app/~~6_tV_wy5KiC1VMeFQPNi2g==/com.yddm.haiwai.game-bHeiJjIVfYMTmPlQwANbrw==/split_config.arm64_v8a.apk
java:
 org.cocos2dx.lib.Cocos2dxRenderer.onDrawFrame(Cocos2dxRenderer.java:105) // 一个jni函数,调用到了c++,进行OpenGL相关的绘制
 android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1593)
 android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1287)

有效的信息就这么多。

apk安装的细节

安卓系统已安装的软件会在/data/app目录内以base.apk形式存在

如果是通过google play安装的,aab的原因,apk会以split的形式存在

我在模拟器上验证了,从google play上下载的apk结构,apk大小还都不一样

perl

复制代码

star2qltechn:/data/app/com.romewargames.strategyempire-AGP1qpUXkGG3E_m0dBHR1A== # ls -ll
total 408188
-rw-r--r-- 1 system system    8197841 2023-11-02 17:11:57.111427681 +0800 base.apk
-rw-r--r-- 1 system system      12532 2023-11-02 17:13:35.134761014 +0800 base.dm
drwxr-xr-x 3 system system       4096 2023-11-02 17:13:35.261427681 +0800 lib
drwxrwx--x 3 system install      4096 2023-11-02 17:13:35.944761014 +0800 oat
-rw-r--r-- 1 system system  355816799 2023-11-02 17:13:04.944761014 +0800 split_base_assets.apk
-rw-r--r-- 1 system system   53932934 2023-11-02 17:13:30.771427681 +0800 split_config.arm64_v8a.apk
通过adb install,或者直接拖拽安装的,是以base.apk存在

perl

复制代码

star2qltechn:/data/app/free.v2ray.proxy.VPN-6RDmu-BT0ZNqmAZImYDKlA== # ls -ll
total 14608
-rw-r--r-- 1 system system  14950019 2023-10-25 10:28:44.089183196 +0800 base.apk
drwxr-xr-x 3 system system      4096 2023-10-25 10:28:44.175849863 +0800 lib
drwxrwx--x 3 system install     4096 2023-10-25 10:28:45.615849863 +0800 oat

大小不太一致

从google play上安装的游戏,发现split_config.arm64_v8a.apk才34M,游戏整体大小是800多M,发现体积最大的是split_asset_pack.apk

yaml

复制代码

x1q:/data/app/com.yddm.haiwai.game-mbV5YOgNm4TcoRDp5ovYqg== # ls -ll
total 906060
-rw-r--r-- 1 system system   49230425 2023-11-03 11:13:00.186139000 +0800 base.apk
-rw-r--r-- 1 system system      20712 2023-11-03 11:13:07.536139000 +0800 base.dm
drwxr-xr-x 3 system system       4096 2023-11-03 11:23:44.736139000 +0800 lib
drwxrwx--x 3 system install      4096 2023-11-03 11:23:48.176139000 +0800 oat
-rw-r--r-- 1 system system  842630903 2023-11-03 11:23:32.496139000 +0800 split_asset_pack.apk
-rw-r--r-- 1 system system   35701753 2023-11-03 11:18:48.466139000 +0800 split_config.arm64_v8a.apk
-rw-r--r-- 1 system system      33177 2023-11-03 11:13:20.846139000 +0800 split_config.en.apk
-rw-r--r-- 1 system system      24985 2023-11-03 11:13:15.056139000 +0800 split_config.es.apk
-rw-r--r-- 1 system system      85791 2023-11-03 11:12:13.916139000 +0800 split_config.xhdpi.apk
-rw-r--r-- 1 system system      41369 2023-11-03 11:18:55.236139000 +0800 split_config.zh.apk
split_config.arm64_v8a.apk / Split APK

Android5.0引入了Split APK机制,Split APK是Google为解决65535上限以及APK越来越大的问题而提出的一种方案。

它可以将一个庞大的APK按照屏幕密度、ABI等形式拆分成多个独立的APK,这些APK共享相同的data、cache目录,共享相同的进程,共享相同的包名。

它们还可以使用各自的资源,并且继承了Base APK里的资源。相关的官方文档

Split APK机制可以将一个APK,拆分成多个独立APK。

在引入了Split APK机制后,APK有两种分类:

  • Single APK:安装文件为一个完整的APK,即base APK。Android称其为Monolithic。
  • Mutiple APK:安装文件在一个文件目录中,其内部有多个被拆分的APK,这些APK由一个 base APK和一个或多个split APK组成。Android称其为Cluster。

multiDexEnabled

在android5.0之前,每一个android应用中只会含有一个dex文件,但是这个dex的方法数量被限制在65535之内,这就是著名的64K(64*1024)事件。为了解决这个问题,Google官方推出了这个类似于补丁一样的support-library,MultiDex。

arduino
复制代码
android {
  compileSdkVersion 21
  buildToolsVersion "21.1.0"
  defaultConfig {
      ...
      minSdkVersion 14
      targetSdkVersion 21
      ...
      // Enabling multidex support.
      multiDexEnabled true
  }
  ...
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}
xml
复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.multidex.myapplication">
<application
...
android:name="android.support.multidex.MultiDexApplication">
...
</application>
</manifest>

dalvik

Dalvik 是早期 Android 版本中用于运行安卓应用的虚拟机,由 Dan Bornstein 编写的,名字来源于他的祖先曾经居住过名叫 Dalvík 的小渔村,村子位于冰岛。

Dalvik 是解释执行加上 JIT,每次app运行的时候,它动态的将一部分 Dalvik 字节码

解释为机器码。随着 App 的运行,更多的字节码被编译和缓存。因为 JIT 只编译了一部分代码,它具有更小的内存占用和更少的设备物理空间占用。但是,边解释边执行,效率低下,这也是后来 Dalvik 遭到抛弃的原因。

从 Android 4.4 开始,Google 开始引入了全新的虚拟机 ART(Android Runtime)。ART 是基于 AOT 编译的,由于安装应用耗时过长,后期高版本的 Android 系统加入了加强版的 JIT 编译。Dalvik 在 Android 5.0 中正式被删除,ART 完成上位。

JIT (Just-In-Time - 实时编译) 和 AOT (Ahead-Of-Time - 预先编译)

ART 是向下兼容的,ART 和 Dalvik 是运行 Dex 字节码的兼容运行时,因此针对 Dalvik 开发的应用也能在 ART 环境中运作。不过,Dalvik 采用的一些技术并不适用于 ART。因此,Dalvik 虚拟机的部分特性以及 Dalvik 字节码指令其实和 ART 都是相通的。

/data/app目录

Android不同的目录存放不同类型的应用,如下所示:

  • /system/framwork:保存的是资源型的应用程序,它们用来打包资源文件。
  • /system/app:保存系统自带的应用程序。
  • /data/app:保存用户安装的应用程序。
  • /data/app-private:保存受DRM保护的私有应用程序。
  • /vendor/app:保存设备厂商提供的应用程序。

获取abi

复制代码

adb shell getprop ro.product.cpu.abi

符号表

so里面有非常多的section,如果有.debug_xxx 的section一般都是带符号表的so,也就是debug的so,但是debug so里面也可以没有这些section,因为可以通过命令行工具把这部分section剥离掉。

一般我们说的符号表,不是什么特定的文件,指的就是.debug_xxx之类的section,你可以把so的这部分section单独提取出来。

在 Android.mk 文件中启用符号表选项通常需要在 LOCAL_CFLAGSLOCAL_LDFLAGS 中设置相应的选项。

以下是一个示例,它向您展示如何启用符号表选项:

makefile

复制代码

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := mylibrary
LOCAL_SRC_FILES := mylibrary.c
LOCAL_CFLAGS    += -g
LOCAL_LDFLAGS   += -Wl,-Map=output.map -Wl,--cref
include $(BUILD_SHARED_LIBRARY)

在上面的示例中,我们通过将 -g 添加到 LOCAL_CFLAGS 变量来启用调试符号选项。这会告诉编译器在生成目标文件时为其添加调试信息。

我们还使用 LOCAL_LDFLAGS 添加了两个链接标志:-Wl,-Map=output.map-Wl,--cref。这些选项告诉链接器在生成库文件时生成映射和交叉引用表。可以使用 ndk-build 命令构建您的项目,并找到生成的库文件及其相关符号表。

请注意,启用符号表选项可能会增加库文件的大小。因此,它们应该仅在调试过程中启用,并且在发布版本中应该禁用它们,以减小应用程序的体积。

readelf -S soFilePath

image.png

配合grep命令也能很快得到debug相关的信息

image.png

readelf 工具用于读取 ELF(可执行和可链接的格式)文件的信息。在调试版本的 NDK 库中,通常会包含以下重要的调试节区:

  1. .debug_info:这个节区包含了调试信息的核心部分,例如类型、变量、函数等的信息。它是调试器显示源代码和调试符号所需的主要节区之一。
  2. .debug_ranges:这个节区包含了调试地址范围的信息。它指定了编译单元中代码的地址范围,用于在调试器中定位变量和函数的位置。
  3. .debug_str:这个节区包含了调试信息中使用的字符串。它存储了类型和变量名称等字符串的实际文本。
  4. .debug_line:这个节区包含了源代码行号和程序计数器(PC)之间的映射关系。它允许调试器在源代码级别上进行断点设置和单步执行。
  5. .debug_loc:这个节区包含了局部变量和表达式的位置列表。它指定了变量在每个编译单元中的地址或地址范围。

这些节区的存在有助于调试器准确地映射源代码与机器代码之间的关系,并支持源代码级别的调试功能。

请注意,这只是对这些节区的简要解释,而实际的调试信息可能更加复杂和详细。对于更深入的了解,建议查阅相关文档或调试工具的说明。

排查mk为啥没导出符号信息

Android.mk就不用看了,重点排查app/jni/Application.mk,查看是否配置导出符号信息

makefile

复制代码
$(info $(NDK_DEBUG))
LOCAL_CFLAGS += -g
LOCAL_CPPFLAGS += -g

这个task就是去除so符号表的任务,所以导致你怎么设置,最后apk里面的so都是没有符号表的

image.png

修改Android.mk,在build的android{}里面添加:


arduino
复制代码
packagingOptions {
  doNotStrip "*/armeabi-v7a/*.so"
}

而带符号表的so的位置在:app\build\intermediates\ndkBuild\debug\obj\local\arm64-v8a一般我们的都是使用这个进行调试。

java
复制代码
public class Cocos2dxRenderer implements GLSurfaceView.Renderer {
    // GLSurfaceView.Renderer是Android中用于渲染OpenGL ES图形的接口。
    // 其中的onDrawFrame方法在每次绘制画面时被调用,它包含了一些必要的操作,以保证渲染的正确性和流畅性。
    @Override
    public void onDrawFrame(final GL10 gl) {
        /*
         * No need to use algorithm in default(60 FPS) situation,
         * since onDrawFrame() was called by system 60 times per second by default.
         */
        if (Cocos2dxRenderer.sAnimationInterval <= 1.0 / 60 * Cocos2dxRenderer.NANOSECONDSPERSECOND) {
            Cocos2dxRenderer.nativeRender();
        } else {
            final long now = System.nanoTime();
            final long interval = now - this.mLastTickInNanoSeconds;
            if (interval < Cocos2dxRenderer.sAnimationInterval) {
                try {
                    Thread.sleep((Cocos2dxRenderer.sAnimationInterval - interval) / Cocos2dxRenderer.NANOSECONDSPERMICROSECOND);
                } catch (final Exception e) {
                }
            }
            /*
             * Render time MUST be counted in, or the FPS will slower than appointed.
            */
            this.mLastTickInNanoSeconds = System.nanoTime();
            Cocos2dxRenderer.nativeRender(); // 105在这里
        }
    }
}
c++
复制代码
    JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) {
        cocos2d::Director::getInstance()->mainLoop(); // 调用到了engine的mainloop
    }

pause resume

image.png

怀疑是这个导致的,这个操作距离用户崩溃非常近,有可能是sdk导致的,需要写个demo验证下,频繁的触发Activity观察是否能复现

怎么触发?

error1

typescript
复制代码
D/cocos2d-x debug info: [LUA-print] [string "app/util/Common.luac"]:1541: invalid 'cobj' in function 'lua_cocos2dx_RenderTexture_begin'
    stack traceback:
      [string "app/util/Common.luac"]:1541: in function <[string "app/util/Common.luac"]:1532>

audio

yaml
复制代码
AudioMixerController            More than 8 active tracks: 18
AudioMixerController           More than 8 active tracks: 17
AudioMixerController          More than 8 active tracks: 17
assets/res/audio/sound/fight_die_blast.mp3
assets/res/audio/sound/env_day_land_atm_2.mp3

其他崩溃解决方案

firebase.google.cn/docs/crashl…

developer.android.google.cn/topic/perfo…

截屏

image.png

split_config.arm64_v8a.apk crash

blog.csdn.net/yinhe888675…

blog.csdn.net/qq114305115…



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
2月前
|
监控 Android开发 C语言
深度解读Android崩溃日志案例分析2:tombstone日志
深度解读Android崩溃日志案例分析2:tombstone日志
175 0
|
2月前
|
监控 安全 API
orhanobut/logger - 强大的Android日志打印库
orhanobut/logger - 强大的Android日志打印库
49 1
|
2月前
|
Android开发
Android 截屏 录屏 与获取log
Android 截屏 录屏 与获取log
23 1
|
2月前
|
Android开发
Android Mediatek 禁用 UART 日志输出
Android Mediatek 禁用 UART 日志输出
26 0
|
7月前
|
网络协议 Android开发 虚拟化
Android Studio无法运行程序调试程序出现Unable to connect to ADB.Check the Event Log for possible issues.Verify th
Android Studio无法运行程序调试程序出现Unable to connect to ADB.Check the Event Log for possible issues.Verify th
70 0
Android Studio无法运行程序调试程序出现Unable to connect to ADB.Check the Event Log for possible issues.Verify th
|
8月前
|
编译器 Android开发
深度解读Android崩溃日志案例分析1:so崩溃
深度解读Android崩溃日志案例分析1:so崩溃
147 1
|
12月前
|
存储 Android开发
Android开发日志打卡APP(二)
Android开发日志打卡APP(二)
|
12月前
|
Android开发 数据库管理 Kotlin
Android开发日志打卡APP(一)
Android开发日志打卡APP(一)
|
11月前
|
Android开发
Android Log类中方法的使用
Android Log类中方法的使用
67 0
|
运维 Android开发
使用logcat让Android应用支持查看实时日志并输出至界面显示功能
使用logcat让Android应用支持查看实时日志并输出至界面显示功能