首页 > 免root版 > 如何不root用gg修改器_gg修改器怎么不用root修改
如何不root用gg修改器_gg修改器怎么不用root修改
  • 如何不root用gg修改器_gg修改器怎么不用root修改

  • 大小:4.03MB日期:2024-03-29 21:51:13
  • 语言:简体中文系统:Android
绿色无毒,安全可靠!部分设备误报拦截请通过!

应用详情

大家好,今天小编为大家分享关于如何不root用gg修改器_gg修改器怎么不用root修改的内容,赶快来一起来看看吧。

一、前言

⾸先,我们必须对App的稳定性有正确的认识,它是App质量构建体系中最基本和最关键的⼀环。如果我们的App不稳定,并且经常不能正常地提供服务,那么⽤户⼤概率会卸载掉它。所以稳定性很重要,并且Crash是P0优先级,需要优先解决。 ⽽且,稳定性可优化的⾯很⼴,它不仅仅只包含Crash这⼀部分,也包括卡顿、耗电等优化范畴。

稳定性纬度

应⽤的稳定性可以分为三个纬度,如下所⽰:

  1. Crash纬度:最重要的指标就是应⽤的Crash率。
  2. 性能纬度:包括启动速度、内存、绘制等等优化⽅向,相对于Crash来说是次要的,在做应⽤性能体系化建设之前,我们必须要确保应⽤的功能稳定可⽤。
  3. 业务⾼可⽤纬度:它是⾮常关键的⼀步,我们需要采⽤多种⼿段来保证我们App的主流程以及核⼼路径的稳定性,只有⽤户经常使⽤我们的App,它才有可能发现别的⽅⾯的问题。

稳定性优化注意事项

我们在做应⽤的稳定性优化的时候,需要注意三个要点,如下所⽰:

1、重在预防、监控必不可少

对于稳定性来说,如果App已经到了线上才发现异常,那其实已经造成了损失,所以,对于稳定性的优化,其重点在于预防。从开发同学的编码环节,到测试同学的测试环节,以及到上线前的发布环节、上线后的运维环节,这些环节都需要来预防异常情况的发⽣。如果异常真的发⽣了,也需要将想⽅设法将损失降到最低,争取⽤最⼩的代价来暴露尽可能多的问题。此外,监控也是必不可少的⼀步,预防做的再好,到了线上,总会有各种各样的异常发⽣。所以,⽆论如何,我们都需要有全⾯的监控⼿段来更加灵敏地发现问题。

2、思考更深⼀层、重视隐含信息:如解决Crash问题时思考是否会引发同⼀类问题

当我们看到了⼀个Crash的时候,不能简单地只处理这⼀个Crash,⽽是需要思考更深⼀层,要考虑会不会在其它地⽅会有⼀样的Crash类型发⽣。如果有这样的情况,我们必须对其统⼀处理和预防。 此外,我们还要关注Crash相关的隐含信息,⽐如,在⾯试过程当中,⾯试官问你,你们应⽤的Crash率是多少,这个问题表明上问的是Crash率,但是实际上它是问你⼀些隐含信息的,过⾼的Crash率就代表开发⼈员的⽔平不⾏,leader的架构能⼒不⾏,项⽬的各个阶段中优化的空间⾮常⼤,这样⼀来,⾯试官对你的印象和评价也不会好。

3、长效保持需要科学流程

应⽤稳定性的建设过程是⼀个细活,所以很容易出现这个版本优化好了,但是在接下来的版本中如果我们不管它,它就会发⽣持续恶化的情况,因此,我们必须从项⽬研发的每⼀个流程⼊⼿,建⽴科学完善的相关规范,才能保证长效的优化效果。

4、Crash相关指标

要对应⽤的稳定性进⾏优化,我们就必须先了解与Crash相关的⼀些指标。

1、UV、PV

PV(Page View):访问量UV(Unique Visitor):独⽴访客,0 – 24⼩时内的同⼀终端只计算⼀次

2、UV、PV、启动、增量、存量 Crash率

UV Crash率(Crash UV / DAU):针对⽤户使⽤量的统计,统计⼀段时间内所有⽤户发⽣崩溃的占⽐,⽤于评估Crash率的影响范围,结合PV。需要注意的是,需要确保⼀直使⽤同⼀种衡量⽅式。 PV Crash率:评估相关Crash影响的严重程度。 启动Crash率:启动阶段,⽤户还没有完全打开App⽽发⽣的Crash,它是影响最严重的Crash,对⽤户伤害最⼤,⽆法通过热修复拯救,需结合客户端容灾,以进⾏App的⾃主修复。(这块后⾯会讲)增量、存量Crash率:增量Crash是指的新增的Crash,⽽存量Crash则表⽰⼀些历史遗留bug。增量Crash是新版本重点,存量Crash是需要持续啃的硬⾻头,我们需要优先解决增量、持续跟进存量问题。

5、Crash率评价

那么,我们App的Crash率降低多少才能算是⼀个正常⽔平或优秀的⽔平呢?Java与Native的总崩溃率必须在千分之⼆以下。Crash率万分位为优秀:需要注意90%的Crash都是⽐较容易解决的,但是要解决最后的10%需要付出巨⼤的努⼒。

6、Crash关键问题

这⾥我们还需要关注Crash相关的关键问题,如果应⽤发⽣了Crash,我们应该尽可能还原Crash现场。因此,我们需要全⾯地采集应⽤发⽣Crash时的相关信息,如下所⽰: 堆栈、设备、OS版本、进程、线程名、Logcat前后台、使⽤时长、App版本、⼩版本、渠道CPU架构、内存信息、线程数、资源包信息。

⽤户⾏为⽇志接着,采集完上述信息并上报到后台后,我们会在APM后台进⾏聚合展⽰,具体的展⽰信息如下所⽰: Crash现场信息 Crash Top机型、OS版本、分布版本、区域Crash起始版本、上报趋势、是否新增、持续、量级最后,我们可以根据以上信息决定Crash是否需要⽴马解决以及在哪个版本进⾏解决,关于APM聚合展⽰这块可以参考 Bugly平台的APM后台聚合展⽰。然后,我们再来看看与Crash相关的整体架构。

7、APM Crash部分整体架构

APM Crash部分的整体架构从上⾄下分为采集层、处理层、展⽰层、报警层。下⾯,我们来详细讲解⼀下每⼀层所做的处理。

1)采集层

⾸先,我们需要在采集层这⼀层去获取⾜够多的Crash相关信息,以确保能够精确定位到问题。需要采集的信息主要为如下⼏种: 错误堆栈 设备信息 ⾏为⽇志 其它信息

2)处理层

然后,在处理层,我们会对App采集到的数据进⾏处理。 数据清洗:将⼀些不符合条件的数据过滤掉,⽐如说,因为⼀些特殊情况,⼀些App采集到的数据不完整,或者由于上传数据失败⽽导致的数 据不完整,这些数据在APM平台上肯定是⽆法全⾯地展⽰的,所以,⾸先我们需要把这些信息进⾏过滤。 数据聚合:在这⼀层,我们会把Crash相关的数据进⾏聚合。 纬度分类:如Top机型下的Crash、⽤户Crash率的前10%等等维度。 趋势对⽐

3)展⽰层

经过处理层之后,就会来到展⽰层,展⽰的信息为如下⼏类: 数据还原 纬度信息 起始版本 其它信息

4)报警层

最后,就会来到报警层,当发⽣严重异常的时候,会通知相关的同学进⾏紧急处理。报警的规则我们可以⾃定义,例如整体的Crash率,其环⽐(与上⼀期进⾏对⽐)或同⽐(如本⽉10号与上⽉10号)抖动超过5%,或者是单个Crash突然间激增。报警的⽅式可以通过 邮件、IM、电话、短信 等等⽅式。

8、责任归属

最后,我们来看下Crash相关的⾮技术问题,需要注意的是,我们要解决的是如何长期保持较低的Crash率这个问题。我们需要保证能够迅速找到bug的相关责任⼈并让开发同学能够及时地处理线上的bug。具体的解决⽅法为如下⼏种:

⼆、Crash优化

1、单个Crash处理⽅案

对于单个Crash的处理⽅案我们可以按如下三个步骤来进⾏解决处理。 1)根据堆栈及现场信息找答案解决90%问题解决完后需考虑产⽣Crash深层次的原因 2)找共性:机型、OS、实验开关、资源包,考虑影响范围 3)线下复现、远程调试

2、Crash率治理⽅案

要对应⽤的Crash率进⾏治理,⼀般需要对以下三种类型的Crash进⾏对应的处理,如下所⽰: 1)解决线上常规Crash 2)系统级Crash尝试Hook绕过 3)疑难Crash重点突破或更换⽅案

3、Java Crash

出现未捕获异常,导致出现异常退出

Thread.setDefaultUncaughtExceptionHandler();
复制代码

我们通过设置⾃定义的UncaughtExceptionHandler,就可以在崩溃发⽣的时候获取到现场信息。注意,这个钩⼦是针对单个进程⽽⾔的,在多 进程的APP中,监控哪个进程,就需要在哪个进程中设置⼀遍ExceptionHandler。 获取主线程的堆栈信息:

Looper.getMainLooper().getThread().getStackTrace();
复制代码

获取当前线程的堆栈信息:

Thread.currentThread().getStackTrace();
复制代码

获取全部线程的堆栈信息:

Thread.getAllStackTraces();
复制代码

第三⽅Crash监控⼯具如 Fabric、腾讯Bugly,都是以字符串拼接的⽅式将数组StackTraceElement[]转换成字符串形式,进⾏保存、上报或者展⽰。那么,我们如何反混淆上传的堆栈信息? 对此,我们⼀般有两种可选的处理⽅案,如下所⽰: 1、每次打包⽣成混淆APK的时候,需要把Mapping⽂件保存并上传到监控后台。 2、Android原⽣的反混淆的⼯具包是retrace.jar,在监控后台⽤来实时解析每个上报的崩溃时。retrace.jar 会将Mapping⽂件进⾏⽂本解析和对象实例化,这个过程⽐较耗时。因此可以将Mapping对象实例进⾏内存缓存,但为了防⽌内存泄露和内存过多占⽤,需要增加定期⾃动回收的逻辑。

如何获取logcat⽅法?

logcat⽇志流程是这样的,应⽤层 –> liblog.so –> logd,底层使⽤ ring buffer 来存储数据。获取的⽅式有以下三种:

1、通过logcat命令获取。

优点:⾮常简单,兼容性好。 缺点:整个链路⽐较长,可控性差,失败率⾼,特别是堆破坏或者堆内存不⾜时,基本会失败。

2、hook liblog.so实现

通过hook liblog.so 中的 __android_log_buf_write ⽅法,将内容重定向到⾃⼰的buffer中。 优点:简单,兼容性相对还好。 缺点:要⼀直打开。

3、⾃定义获取代码。通过移植底层获取logcat的实现,通过socket直接跟logd交互。

优点:⽐较灵活,预先分配好资源,成功率也⽐较⾼。 缺点:实现⾮常复杂

如何获取Java 堆栈?

当发⽣native崩溃时,我们通过unwind只能拿到Native堆栈。但是我们希望可以拿到当时各个线程的Java堆栈。对于这个问题,⽬前有两种处理 ⽅式,分别如下所⽰:

1、Threa1、Thread.getAllStackTraces()。

优点 简单,兼容性好。 缺点 成功率不⾼,依靠系统接⼝在极端情况也会失败。 7.0之后这个接⼝是没有主线程堆栈。 使⽤Java层的接⼝需要暂停线程。

2、hook libart.so。

通过hook ThreadList和Thread 的函数,获得跟ANR⼀样的堆栈。为了稳定性,需要在fork的⼦进程中执⾏。 优点:信息很全,基本跟ANR的⽇志⼀样,有native线程状态,锁信息等等。 缺点:⿊科技的兼容性问题,失败时我们可以使⽤Thread.getAllStackTraces()兜底。

4、Java Crash处理流程

讲解了Java Crash相关的知识后,我们就可以去了解下Java Crash的处理流程,这⾥借⽤Gityuan流程图进⾏讲解,如下图所⽰: 1、⾸先发⽣crash所在进程,在创建之初便准备好了defaultUncaughtHandler,⽤来 处理Uncaught Exception,并输出当前crash的基本信息; 2、调⽤当前进程中的AMP.handleApplicationCrash;经过binder ipc机制,传递到 system_sersystem_server进程; 3、接下来,进⼊system_server进程,调⽤binder服务端执⾏3、接下来,进⼊system_server进程,调⽤binder服务端执⾏ AMS.handleApplicationCrash; 4、从mProcessNames查找到⽬标进程的Proes查找到⽬标进程的ProcessRecord对象;并将进程crash信息 输出到⽬录/data/system/dropbox; 5、执⾏makeAppCrashingLocked: 创建当前⽤户下的crash应⽤的error receiver,并忽略当前应⽤的⼴播; 停⽌当前进程中所有activity中的WMS的冻结屏幕消息,并执⾏相关⼀些屏幕相关操作; 6、再执⾏handleAppCrashLocked⽅法: 当1分钟内同⼀进程未发⽣连续crash两次时,则执⾏结束栈顶正在运⾏activity的流程; 当1分钟内同⼀进程连续crash两次时,且⾮persistent进程,则直接结束该应⽤所有activity,并杀死该进程以及同⼀个进程组下的所有进程。然后再恢复栈顶第⼀个⾮finishing状态的activity;当1分钟内同⼀进程连续crash两次时,且persistent进程,则只执⾏恢复栈顶第⼀个⾮finishing状态的activity。 7、通过mUiHandler发送消息SHOW_ERROR_MSG,弹出crash对话框; 8、到此,system_server进程执⾏完成。回到crash进程开始执⾏杀掉当前进程的操作; 9、当crarash进程被杀,通过binder死亡通知,告知system_server进程来执⾏sh进程被杀,通过binder死亡通知,告知system_server进程来执⾏appDiedLocked(); 10、最后,执⾏清理应⽤相关的四⼤组件信息。补充加油站:binder 死亡通知原理 这⾥我们还需要了解下binder 死亡通知的原理,其流程图如下所⽰:

由于Crash进程中拥有⼀个Binder服务端ApplicationThread,⽽应⽤进程在创建过程调⽤attachApplicationLocked(),从⽽attach到system_server进程,在system_server进程内有⼀个ApplicationThreadProxy,这是相对应的Binder客户端。

当Binder服务端ApplicationThread所在进程(即Crash进程)挂掉后,则Binder客户端能收到相应的死亡通知,从⽽进⼊binderDied流程。

5、Native Crash

特点: 访问⾮法地址 地址对齐出错 发⽣程序主动abort 上述都会产⽣相应的signal信号,导致程序异常退出。

1、合格的异常捕获组件

⼀个合格的异常捕获组件需要包含以下功能: ⽀持在crash时进⾏更多扩展操作打印logcat和⽇志上报crash次数对不同crash做不同恢复措施可以针对业务不断改进的适应

2、现有⽅案

1、Google Breakpad

优点:权威、跨平台 缺点:代码体量较⼤

2、Logcat

优点:利⽤安卓系统实现 缺点:需要在crash时启动新进程过滤logcat⽇志,不可靠

3、coffeecatch

优点:实现简洁、改动容易 缺点:有兼容性问题

3、Native崩溃捕获流程

Native崩溃捕获的过程涉及到三端,这⾥我们分别来了解下其对应的处理。

1、编译端

编译C/C++需将带符号信息的⽂件保留下来。

2、客户端

捕获到崩溃时,将收集到尽可能多的有⽤信息写⼊⽇志⽂件,然后选择合适的时机上传到服务器。

3、服务端

读取客户端上报的⽇志⽂件,寻找合适的符号⽂件,⽣成可读的C/C++调⽤栈。

4、Native崩溃捕获的难点

核⼼:如何确保客户端在各种极端情况下依然可以⽣成崩溃⽇志。

1、⽂件句柄泄漏,导致创建⽇志⽂件失败?

提前申请⽂件句柄fd预留。

2、栈溢出导致⽇志⽣成失败?

使⽤额外的栈空间signalstack,避免栈溢出导致进程没有空间创建调⽤栈执⾏处理函数。(signalstack:系统会在危险情况下把栈指针指向 这个地⽅,使得可以在⼀个新的栈上运⾏信号处理函数) 特殊请求需直接替换当前栈,所以应在堆中预留部分空间。

3、堆内存耗尽导致⽇志⽣产失败?

参考Breakpad重新封装Linux Syscall Support的做法以避免直接调⽤libc去分配堆内存。

4、堆破坏或⼆次崩溃导致⽇志⽣成失败?

Breakpad使⽤了fork⼦进程甚⾄孙进程的⽅式去收集崩溃现场,即便出现⼆次崩溃,也只是这部分信息丢失。 这⾥说下Breakpad缺点: ⽣成的minidump⽂件是⼆进制的,包含过多不重要的信息,导致⽂件数过⼤。但minidump可以使⽤gdb调试、看到传⼊参数。 需要了解的是,未来Chromium会使⽤Crashpad替代Breakpad。

5、想要遵循Android的⽂本格式并添加更多重要的信息?

改造Breakpad,增加Logcat信息,Java调⽤栈信息、其它有⽤信息。

5、Native崩溃捕获注册

⼀个Native Crash log信息如下: 堆栈信息中 pc 后⾯跟的内存地址,就是当前函数的栈地址,我们可以通过下⾯的命令⾏得出出错的代码⾏数

arm-linux-androideabi-addr2line -e 内存地址
复制代码

下⾯列出全部的信号量以及所代表的含义:

#define SIGHUP 1 // 终端连接结束时发出(不管正常或⾮正常)
#define SIGINT 2 // 程序终⽌(例如Ctrl-C)
#define SIGQUIT 3 // 程序退出(Ctrl-)
#define SIGILL 4 // 执⾏了⾮法指令,或者试图执⾏数据段,堆栈溢出
#define SIGTRAP 5 // 断点时产⽣,由debugger使⽤
#define SIGABRT 6 // 调⽤abort函数⽣成的信号,表⽰程序异常
#define SIGIOT 6 // 同上,更全,IO异常也会发出
#define SIGBUS 7 // ⾮法地址,包括内存地址对齐出错,⽐如访问⼀个4字节的整数, 但其地址不是4的倍数
#define SIGFPE 8 // 计算错误,⽐如除0、溢出
#define SIGKILL 9 // 强制结束程序,具有最⾼优先级,本信号不能被阻塞、处理和忽略
#define SIGUSR1 10 // 未使⽤,保留
#define SIGSEGV 11 // ⾮法内存操作,与 SIGBUS不同,他是对合法地址的⾮法访问,⽐如访问没有读权限的内存,向没有写权限的地址写数据
#define SIGUSR2 12 // 未使⽤,保留
#define SIGPIPE 13 // 管道破裂,通常在进程间通信产⽣
#define SIGALRM 14 // 定时信号,
#define SIGTERM 15 // 结束程序,类似温和的 SIGKILL,可被阻塞和处理。通常程序如果终⽌不了,才会尝试SIGKILL
#define SIGSTKFLT 16 // 协处理器堆栈错误
#define SIGCHLD 17 // ⼦进程结束时, ⽗进程会收到这个信号。
#define SIGCONT 18 // 让⼀个停⽌的进程继续执⾏
#define SIGSTOP 19 // 停⽌进程,本信号不能被阻塞,处理或忽略
#define SIGTSTP 20 // 停⽌进程,但该信号可以被处理和忽略
#define SIGTTIN 21 // 当后台作业要从⽤户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号
#define SIGTTOU 22 // 类似于SIGTTIN, 但在写终端时收到
#define SIGURG 23 // 有紧急数据或out-of-band数据到达socket时产⽣
#define SIGXCPU 24 // 超过CPU时间资源限制时发出
#define SIGXFSZ 25 // 当进程企图扩⼤⽂件以⾄于超过⽂件⼤⼩资源限制
#define SIGVTALRM 26 // 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占⽤的CPU时间.
#define SIGPROF 27 // 类似于SIGALRM/SIGVTALRM, 但包括该进程⽤的CPU时间以及系统调⽤的时间
#define SIGWINCH 28 // 窗⼝⼤⼩改变时发出
#define SIGIO 29 // ⽂件描述符准备就绪, 可以开始进⾏输⼊/输出操作
#define SIGPOLL SIGIO // 同上,别称
#define SIGPWR 30 // 电源异常
#define SIGSYS 31 // ⾮法的系统调⽤
复制代码

注意

JNI_OnLoad是最适合安装信号初始函数的地⽅。 建议在上报时调⽤Java层的⽅法统⼀上报。Native崩溃捕获注册。

6、崩溃分析流程

⾸先,应收集崩溃现场的⼀些相关信息,如下:

1、崩溃信息

进程名、线程名 崩溃堆栈和类型 有时候也需要知道主线程的调⽤栈

2、系统信息

系统运⾏⽇志 /system/etc/event-log-tags 机型、系统、⼚商、CPU、ABI、Linux版本等 注意,我们可以去寻找共性问题,如下: 设备状态 是否root 是否是模拟器

3、内存信息

系统剩余内存

/proc/meminfo
复制代码

当系统可⽤内存⼩于MemTotal的10%时,OOM、⼤量GC、系统频繁⾃杀拉起等问题⾮常容易出现。 应⽤使⽤内存 包括Java内存、RSS、PSS PSS和RSS通过/proc/self/smap计算,可以得到apk、dex、so等更详细的分类统计。 虚拟内存 获取⼤⼩:

/proc/self/status
复制代码

获取其具体的分布情况:

/proc/self/maps
复制代码

需要注意的是,对于32位进程,32位CPU,虚拟内存达到3GB就可能会引起内存失败的问题。如果是64位的CPU,虚拟内存⼀般在3~4GB。如果⽀持64位进程,虚拟内存就不会成为问题。

4、资源信息

如果应⽤堆内存和设备内存⽐较充⾜,但还出现内存分配失败,则可能跟资源泄漏有关。 ⽂件句柄fd 获取fd的限制数量:

/proc/self/limits
复制代码

⼀般单个进程允许打开的最⼤句柄个数为1024,如果超过800需将所有fd和⽂件名输出⽇志进⾏排查。 线程数 获取线程数⼤⼩:

/proc/self/status
复制代码

⼀个线程⼀般占2MB的虚拟内存,线程数超过400个⽐较危险,需要将所有tid和线程名输出到⽇志进⾏排查。 JNI 容易出现引⽤失效、引⽤爆表等崩溃。 通过DumpReferenceTables统计JNI的引⽤表,进⼀步分析是否出现JNI泄漏等问题。 补充加油站:dumpReferenceTables的出处 在dalvik.system.VMDebug类中,是⼀个native⽅法,亦是static⽅法;在JNI中可以这么调⽤

jclass vm_class = env->FindClass("dalvik/system/VMDebug");
jmethodID dump_mid = env->GetStaticMethodID( vm_class, "dumpReferenceTables", "()V" );
env->CallStaticVoidMethod( vm_class, dump_mid );
复制代码

5、应⽤信息

崩溃场景 关键操作路径 其它跟⾃⾝应⽤相关的⾃定义信息:运⾏时间、是否加载补丁、是否全新安装或升级。

6、崩溃分析流程

接下来进⾏崩溃分析:

1、确定重点

确认严重程度 优先解决Top崩溃或对业务有重⼤影响的崩溃:如启动、⽀付过程的崩溃Java崩溃:如果是OOM,需进⼀步查看⽇志中的内存信息和资源信息Native崩溃:查看signal、code、fault addr以及崩溃时的Java堆栈 常见的崩溃类型有:

2、查找共性

机型、系统、ROM、⼚商、ABI这些信息都可以作为共性参考,对于下⼀步复现问题有明确指引。

3、尝试复现

复现之后再增加⽇志或使⽤Debugger、GDB进⾏调试。如不能复现,可以采⽤⼀些⾼级⼿段,如xlog⽇志、远程诊断、动态分析等等。 补充加油站:系统崩溃解决⽅式 1、通过共性信息查找可能的原因 2、尝试使⽤其它使⽤⽅式规避 3、Hook解决

6、实战:使⽤Breakpad捕获native崩溃

⾸先,这⾥给出《Android开发⾼⼿课》张绍⽂⽼师写的crash捕获⽰例⼯程,⼯程⾥⾯已经集成了Breakpad 来获取发⽣ native crash 时候的 系统信息和线程堆栈信息。下⾯来详细介绍下使⽤Breakpad来分析native崩溃的流程: 1、⽰例⼯程是采⽤cmake的构建⽅式,所以需要先到Android Studio中SDK Manager中的SDK Tools下下载NDK和cmake。 2、安装实例⼯程后,点击CRASH按钮产⽣⼀个native崩溃。⽣成的 crash信息,如果授予Sdcard权限会优先存放在/rd权限会优先存放在/sdcard/crashDump下,便于我们做进⼀步的分析。反之会 放到⽬录 /data/.dodola.breakpad/files/crashDump中。shDump中。 3、使⽤adb pull命令将抓取到的crash⽇志⽂件放到电脑本地⽬录中:

adb pull /sdcard/crashDump/***.dmp > ~/Documents/crash_log.dmp
复制代码

4、下载并编译Breakpad源码,在src/processor⽬录下找到minidump_star⽬录下找到minidump_stackwalk, 使⽤这个⼯具将dmp⽂件转换为txt⽂件:

// 在项⽬⽬录下clone Breakpad仓库
git clone https:///google/breakpad.git
// 切换到Breakpad根⽬录进⾏配置、编译
cd breakpad
./configure && make
// 使⽤src/processor⽬录下的minidump_stackwalk⼯具将dmp⽂件转换为txt⽂件
./src/processor/minidump_stackwalk ~/Documents/crashDump/crash_log.dmp >crash_log.txt
复制代码

5、打开crash_log.txt,可以得到如下内容:

Operating system: Android
0.0.0 Linux 4.4.78-perf-g539ee70 #1 SMP PREEMPT Mon Jan 14 17:08:14 CST 2019 aarch64
CPU: arm64
8 CPUs
GPU: UNKNOWN
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
0 libcrash-lib.so + 0x650
复制代码

其中我们需要的关键信息为CPU是arm64的,并且crash的地址为0x650。接下来我们需要将这个地址转换为代码中对应的⾏。 6、使⽤ndk 中提供的addr2line来根据地址进⾏⼀个符号反解的过程。

如果是arm64的so使⽤ $NDKHOME/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-
addr2line。
如果是arm的so使⽤ $NDKHOME/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-
addr2line。

由crash_log.txt的信息可知,我们机器的cpu架构是arm64的,因此需要使⽤aarch64-linux-android-addr2line这个命令⾏⼯具。该命令的⼀般使⽤格式如下:

// 注意:在mac下 ./ 代表执⾏⽂件 ./aarch64-linux-android-addr2line -e 对应的.so 需要解析的地址上述中对应的.so⽂件在项⽬编译之后,会出现在Chapter01-
master/sample/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/libcrash-lib.so这个位置,由于我的⼿机CPU架构
是arm64的,所以这⾥选择的是arm64-v8a中的libcrash-lib.so。接下来我们使⽤aarch64-linux-android-addr2line这个命令:
./aarch64-linux-android-addr2line -f -C -e ~/Documents/open-project/Chapter01-master/sample/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/libcra
参数含义:
-e --exe=<executable>:指定需要转换地址的可执⾏⽂件名。
-f --functions:在显⽰⽂件名、⾏号输出信息的同时显⽰函数名信息。
-C --demangle[=style]:将低级别的符号名解码为⽤户级别的名字。
复制代码

结果输出为:

Crash()
/Users/quchao/Documents/open-project/Chapter01-master/sample/src/main/cpp/crash.cpp:10
复制代码

由此,我们得出crash的代码⾏为crash.cpp⽂件中的第10⾏,接下来根据项⽬具体情况进⾏相应的修改即可。 Tips:这是从事NDK开发(⾳视频、图像处理、OpenCv、热修复框架开发)同学调试native层错误时经常要使⽤的技巧,强烈建议熟练掌握。

7、疑难Crash解决⽅案

最后,笔者这⾥再讲解下⼀些疑难Crash的解决⽅案。

问题1:如何解决Android 7.0 Toast BadTokenException?

参考Android 8.0 try catch的做法,代理Toast⾥的mTN(handler)就可以实现捕获异常。

问题2:如何解决 SharedPreference apply 引起的 ANR 问题apply为什么会引起ANR?

SP 调⽤ apply ⽅法,会创建⼀个等待锁放到 QueuedWork 中,并将真正的数据持久化封装成⼀个任务放到异步队列中执⾏,任务执⾏结束会释放锁。Activity onStop 以及 Service 处理 onStop,onStartCommand 时,执⾏ QueuedWork.waitToFinish() 等待所有的等待锁释放。

如何解决?

所有此类 ANR 都是经由 QueuedWork.waitToFinish() 触发的,只要在调⽤此函数之前,将其中保存的队列⼿动清空即可。 具体是Hook ActivityThrad的Handler变量,拿到此变量后给其设置⼀个Callback,Handler 的dispatchMessage 中会先处理 callback。最后在 Callback 中调⽤队列的清理⼯作,注意队列清理需要反射调⽤ QueuedWork。

注意

apply 机制本⾝的失败率就⽐较⾼(1.8%左右),清理等待锁队列对持久化造成的影响不⼤。

问题3:如何解决TimeoutExceptin异常?

它是由系统的FinalizerWatchdogDaemon抛出来的。 这⾥⾸先介绍下看门狗 WatchDog,它 的作⽤是监控重要服务的运⾏状态,当重要服务停⽌时,发⽣ Timeout 异常崩溃,WatchDog 负责将应⽤重启。⽽当关闭 WatchDog(执⾏stop()⽅法)后,当重要服务停⽌时,也不会发⽣ Timeout 异常,是⼀种通过⾮正常⼿段防⽌异常发⽣的⽅法。

规避⽅案

stop⽅法,在Android 6.0之前会有线程同步问题。 因为6.0之前调⽤threadToStop的interrupt⽅法是没有加锁的,所以可能会有线程同步的问题。

注意:Stop的时候有⼀定概率导致即使没有超时也会报timeoutexception。

缺点

只是为了避免上报异常采取的⼀种hack⽅案,并没有真正解决引起finialize超时的问题。

问题4:如何解决输⼊法的内存泄漏?

通过反射将输⼊法的两个View置空。

7、进程保活

我们可以利⽤SyncAdapter提⾼进程优先级,它是Android系统提供⼀个账号同步机制,它属于核⼼进程级别,⽽使⽤了SyncAdapter的进程优先级本⾝也会提⾼,使⽤⽅式请Google,关联SyncAdapter后,进程的优先级变为1,仅低于前台正在运⾏的进程,因此可以降低应⽤被系统杀掉的概率。

8、总结

对于App的Crash优化,总的来说,我们需要考虑以下四个要点:

注意

由于traces.txt上传⽐较耗时,所以⼀般线下采⽤,线上建议综合ProcessErrorStateInfo和出现ANR时的堆栈信息来实现ANR的实时上传。

2、ANR优化

ANR发⽣原因:没有在规定的时间内完成要完成的事情。 ANR分类 发⽣场景

从进程⾓度看发⽣原因有: 当前进程:主线程本⾝耗时或者主线程的消息队列存在耗时操作、主线程被本进程的其它⼦线程所blocked 远端进程:binder call、socket通信Andorid系统监测ANR的核⼼原理是消息调度和超时处理。 ANR排查流程

1、Log获取

抓取bugreport

adb shell bugreport > bugreport.txt
复制代码

2、直接导出/data/anr/traces.txt⽂件

adb pull /data/anr/traces.txt trace.txt
复制代码

2、搜索“ANR in”处log关键点解读

发⽣时间(可能会延时10-20s) pid:当pid=0,说明在ANR之前,进程就被LMK杀死或出现了Crash,所以⽆法接受到系统的⼴播或者按键消息,因此会出现ANRcpu负载Load: 7.58 / 6.21 / 4.83代表此时⼀分钟有平均有7.58个进程在等待 1、5、15分钟内系统的平均负荷 当系统负荷持续⼤于1.0,必须将值降下来 当系统负荷达到5.0,表⾯系统有很严重的问题

cpu使⽤率

CPU usage from 18101ms to 0ms ago 28% 2085/system_server: 18% user + 10% kernel / faults: 8689 minor 24 major11% 752/android.hardware.sensors@1.0-service: 4% user + 6.9% kernel / faults: 2 minor 9.8% 780/surfaceflinger: 6.2% user

----- pid 10494 at 2019-11-18 15:28:29 -----
复制代码

4、往下翻找到“main”线程则可看到对应的阻塞log

"main" prio=5 tid=1 Sleeping
| group="main" sCount=1 dsCount=0 flags=1 obj=0x746bf7f0 self=0xe7c8f000
| sysTid=10494 nice=-4 cgrp=default sched=0/0 handle=0xeb6784a4
| state=S schedstat=( 5119636327 325064933 4204 ) utm=460 stm=51 core=4 HZ=100
| stack=0xff575000-0xff577000 stackSize=8MB
| held mutexes=
复制代码

上述关键字段的含义如下所⽰: tid:线程号 sysTid:主进程线程号和进程号相同 Waiting/Sleeping:各种线程状态 nice:nice值越⼩,则优先级越⾼,-17~16 schedstat:Running、Runable时间(ns)与Switch次数 utm:该线程在⽤户态的执⾏时间(jiffies) stm:该线程在内核态的执⾏时间(jiffies) sCount:该线程被挂起的次数 dsCount:该线程被调试器挂起的次数 self:线程本⾝的地址 补充加油站:各种线程状态 需要注意的是,这⾥的各种线程状态指的是Native层的线程状态,关于Java线程状态与Native线程状态的对应关系如下所⽰:

enum ThreadState {
// Thread.State JDWP state
kTerminated = 66, // TERMINATED TS_ZOMBIE Thread.run has returned, but Thread* still around
kRunnable, // RUNNABLE TS_RUNNING runnable
kTimedWaiting, // TIMED_WAITING TS_WAIT in Object.wait() with a timeout
kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep()
kBlocked, // BLOCKED TS_MONITOR blocked on a monitor
kWaiting, // WAITING TS_WAIT in Object.wait()
kWaitingForLockInflation, // WAITING TS_WAIT blocked inflating a thin-lock
kWaitingForTaskProcessor, // WAITING TS_WAIT blocked waiting for taskProcessor
kWaitingForGcToComplete, // WAITING TS_WAIT blocked waiting for GC
kWaitingForCheckPointsToRun, // WAITING TS_WAIT GC waiting for checkpoints to run
kWaitingPerformingGc, // WAITING TS_WAIT performing GC
kWaitingForDebuggerSend, // WAITING TS_WAIT blocked waiting for events to be sent
kWaitingForDebuggerToAttach, // WAITING TS_WAIT blocked waiting for debugger to attach
kWaitingInMainDebuggerLoop, // WAITING TS_WAIT blocking/reading/processing debugger events
kWaitingForDebuggerSuspension, // WAITING TS_WAIT waiting for debugger suspend all
kWaitingForJniOnLoad, // WAITING TS_WAIT waiting for execution of dlopen and JNI on load code
kWaitingForSignalCatcherOutput, // WAITING TS_WAIT waiting for signal catcher IO plete
kWaitingInMainSignalCatcherLoop, // WAITING TS_WAIT blocking/reading/processing signals
kWaitingForDeoptimization, // WAITING TS_WAIT waiting for deoptimization suspend all
kWaitingForMethodTracingStart, // WAITING TS_WAIT waiting for method tracing to start
kWaitingForVisitObjects, // WAITING TS_WAIT waiting for visiting objects
kWaitingForGetObjectsAllocated, // WAITING TS_WAIT waiting for getting the number of allocated objects
kWaitingWeakGcRootRead, // WAITING TS_WAIT waiting on the GC to read a weak root
kWaitingForGcThreadFlip, // WAITING TS_WAIT waiting on the GC thread flip (CC collector) to finish
kStarting, // NEW TS_WAIT native thread started, not yet ready to run managed code
kNative, // RUNNABLE TS_RUNNING running in a JNI native method
kSuspended, // RUNNABLE TS_RUNNING suspended by GC or debugger
};
复制代码

1、接⼊成本

完善⽂档、接⼝简洁

2、统⼀配置后台

可按照APP、版本配置

3、定制性

⽀持定制功能,让接⼊⽅来决定具体⾏为

4、灰度机制

5、数据分析

采⽤统⼀数据平台,为安全模式改进提供依据

6、快速测试

创建更多的针对性测试案例,如模拟连续Crash

7、异常熔断

当多次请求失败则可让⽹络库主动拒绝请求。 容灾⽅案集合路径功能开关 -> 统跳中⼼ -> 动态修复 -> 安全模式

五、稳定性长效治理

要实现App稳定性的长效治理,我们需要从 开发阶段 => 测试阶段 => 合码阶段 => 发布阶段 => 运维阶段 这五个阶段来做针对性地处理。

1、开发阶段

统⼀编码规范、增强编码功底、技术评审、CodeReview机制 架构优化 能⼒收敛 统⼀容错:如在⽹络库utils中统⼀对返回信息进⾏预校验,如不合法就直接不⾛接下来的流程。

2、测试阶段

功能测试、⾃动化测试、回归测试、覆盖安装 特殊场景、机型等边界测试:如服务端返回异常数据、服务端宕机 云测平台:提供更全⾯的机型进⾏测试

3、合码阶段

编译检测、静态扫描 预编译流程、主流程⾃动回归

4、发布阶段

多轮灰度 分场景、纬度全⾯覆盖

5、运维阶段

灵敏监控 回滚、降级策略 热修复、本地容灾⽅案

六、稳定性优化问题

1、你们做了哪些稳定性⽅⾯的优化?

随着项⽬的逐渐成熟,⽤户基数逐渐增多,DAU持续升⾼,我们遇到了很多稳定性⽅⾯的问题,对于我们技术同学遇到了很多的挑战,⽤户经常 使⽤我们的App卡顿或者是功能不可⽤,因此我们就针对稳定性开启了专项的优化,我们主要优化了三项:

  1. Crash专项优化
  2. 性能稳定性优化
  3. 业务稳定性优化

通过这三⽅⾯的优化我们搭建了移动端的⾼可⽤平台。同时,也做了很多的措施来让App真正地实现了⾼可⽤。

2、性能稳定性是怎么做的?

全⾯的性能优化:启动速度、内存优化、绘制优化 线下发现问题、优化为主 线上监控为主 Crash专项优化 我们针对启动速度,内存、布局加载、卡顿、瘦⾝、流量、电量等多个⽅⾯做了多维的优化。 我们的优化主要分为了两个层次,即线上和线下,针对于线下呢,我们侧重于发现问题,直接解决,将问题尽可能在上线之前解决为⽬的。⽽真正到了线上呢,我们最主要的⽬的就是为了监控,对于各个性能纬度的监控呢,可以让我们尽可能早地获取到异常情况的报警。 同时呢,对于线上最严重的性能问题性问题:Crash,我们做了专项的优化,不仅优化了Crash的具体指标,⽽且也尽可能地获取了Crash发⽣时的详细信息,结合后端的聚合、报警等功能,便于我们快速地定位问题。

3、业务稳定性如何保障?

数据采集 + 报警需要

对项⽬的主流程与核⼼路径进⾏埋点监控,同时还需知道每⼀步发⽣了多少异常,这样,我们就知道了所有业务流程的转换率以及相应界⾯的转换率结合⼤盘,如果转换率低于某个值,进⾏报警异常监控 + 单点追查兜底策略,如天猫安全模式移动端业务⾼可⽤它侧重于⽤户功能完整可⽤,

主要是为了解决⼀些线上⼀些异常情况导致⽤户他虽然没有崩溃,也没有性能问题,但是呢,只是单纯的功能不可⽤的情况,我们需要对项⽬的主流程、核⼼路径进⾏埋点监控,来计算每⼀步它真实的转换率是多少,同时呢,还需要知道在每⼀步到底发⽣了多少异常。这样我们就知道了所有业务流程的转换率以及相应界⾯的转换率,有了⼤盘的数据呢,我们就知道了,如果转换率或者是某些监控的成功率低于某个值,那很有可能就是出现了线上异常,结合了相应的报警功能,我们就不需要等⽤户来反馈了,这个就是业务稳定性保障的基础。

同时呢,对于⼀些特殊情况,⽐如说,开发过程当中或代码中出现了⼀些catch代码块,捕获住了异常,让程序不崩溃,这其实是不合理的,程序虽然没有崩溃,当时程序的功能已经变得不可⽤,所以呢,这些被catch的异常我们也需要上报上来,这样我们才能知道⽤户到底出现了什么问题⽽导致的异常。此外,线上还有⼀些单点问题,⽐如说⽤户点击登录⼀直进不去,这种就属于单点问题,其实我们是⽆法找出其和其它问题的共性之处的,所以呢,我们就必须要找到它对应的详细信息。

最后,如果发⽣了异常情况,我们还采取了⼀系列措施进⾏快速⽌损。(=>4)

4、如果发⽣了异常情况,怎么快速⽌损?

功能开关 统跳中⼼ 动态修复:热修复、资源包更新 ⾃主修复:安全模式 ⾸先,需要让App具备⼀些⾼级的能⼒,我们对于任何要上线的新功能,要加上⼀个功能的开关,通过配置中⼼下发的开关呢,来决定是否要显⽰新功能的⼊⼝。如果有异常情况,可以紧急关闭新功能的⼊⼝,那就可以让这个App处于可控的状态了。 然后,我们需要给App设⽴路由跳转,所有的界⾯跳转都需要通过路由来分发,如果我们匹配到需要跳转到有bug的这样⼀个新功能时,那我们就不跳转了,或者是跳转到统⼀的异常正处理中的界⾯。如果这两种⽅式都不可以,那就可以考虑通过热修复的⽅式来动态修复,⽬前热修复的⽅案其实已经⽐较成熟了,我们完全可以低成本地在我们的项⽬中添加热修复的能⼒,当然,如果有些功能是由RN或WeeX来实现就更好了,那就可以通过更新资源包的⽅式来实现动态更新。

⽽这些如果都不可以的话呢,那就可以考虑⾃⼰去给应⽤加上⼀个⾃主修复的能⼒,如果App启动多次的话,那就可以考虑清空所有的缓存数据,将App重置到安装的状态,到了最严重的等级呢,可以阻塞主线程,此时⼀定要等App热修复成功之后才允许⽤户进⼊。

七、总结

Android稳定性优化是⼀个需要 长期投⼊,持续运营和维护 的⼀个过程,上⽂中我们不仅深⼊探讨了Java Crash、Native Crash和ANR的解决流程及⽅案,还分析了其内部实现原理和监控流程。

到这⾥,可以看到,要想做好稳定性优化,我们 必须对虚拟机运⾏、Linux信号处理和内存分配 有⼀定程度的了解,只有深⼊了解这些底层知识,我们才能⽐别⼈设计出更好的稳定性优化⽅案。

性能优化大全资料获取:《T10级工程师必会的Android性能优化解析》

私信发送:“核心笔记”或“手册”,即可获取全套资料!

以上就是关于如何不root用gg修改器_gg修改器怎么不用root修改的全部内容,感谢大家的浏览观看,如果你喜欢本站的文章可以CTRL+D收藏哦。

相关文章

热门下载

大家还在搜