Android系统(110)---稳定性问题总结

稳定性问题总结

稳定性问题比较杂,且很多是概率性问题,没有统一处理方式,需要针对具体的问题,具体分析,

必现的问题较易解决,针对当前代码添加各种调试log,一步步debug去定位,过程虽然可能慢点,但一般都会解决。

但针对偶发性的概率问题,则较为麻烦,依赖于大量的测试复现,然后统计 分析当前抓取到的 events、system 等log中,找到复现的步骤,然后去定位。

且针对与这种概率问题,最好能够拿到当时的现场,所以有时候需要将 tombstone 或者anr、crash 转为anr 去处理。

稳定性问题分类

  • ANR
  • Watchdog
  • Crash、界面、流程异常
  • Tombstone
  • Panic

ANR

系统中发生的ANR 大概分为三类:

1. 输入事件响应超时(主要类型):主线程 (“事件处理线程” / “UI线程”) 在5秒内没有响应输入事件

2. 广播接收处理超时:BroadcastReceiver 没有在10秒内完成返回

备注:

  • 前台广播为10s, 后台广播为 60s, 默认发送的为后台广播

  • 发送广播时,携带Intent.FLAG_RECEIVER_FOREGROUND将广播设置为前台广播

3. Service服务处理超时(小概率事件):Service在20秒(前台5s, 后台service 为200 秒,默认为后台服务)内没有执行完成应用发起的service请求

备注:

  • 前台服务:超时时间为5s, 通过startForegroundService 将指定服务指定为前台服务,会在notification中存在提示信息(像音乐类应用)
  • 被前台进程binder的服务: service 服务被前台进程binder, 那么此服务的超时时间为 20 秒
  • 被后台进程binder的服务: service 服务被后台进程binder,那么此服务的超时时间为 200 秒

以上三类ANR产生的原因可能是各种各样的,但常见的原因可以分为:

1. 程序自身主线程有问题引起的anr,此类问题往往可以直接通过 ANR的 backtrace 定位,此类异常常见的为:

a、主进程进行IO文件操作

b、主进程进行大量频繁的数据库操作

c、主进程进入死循环

d、主进程进行联网操作(应该已经限制不允许了)

但也有一些诡异的无法通过堆栈定位的异常,需要针对具体情况,具体分析。例如:

证卷类的 大富翁apk,更新时在某些低端机上会产生anr,主线程会卡在

         at android.os.MessageQueue.nativePollOnce(Native Method)
  • 1

这个方法是表示主线程处于空闲状态,主线程 handler 等待消息过来处理,那应该不会发生问题啊?

之后多次怀疑,多次定位,才定位出来原因是:

大富翁在更新时, 会在主线程里面有一个显示更新的进度条,每下载一个字节会使用handler 通知主进程 更新一下进度条,会毫秒级的 发送handler 消息。

在低端机上,性能较差,主进程的 handler 消息处理不过来,正常事件没有机会的到处理因而产生anr

高端机旗舰机,性能好,不会产生此问题。

2. 调用到对端,对端异常造成的ANR,此类常见的为 通过am、pm等相关manager 调用到了 system_server 进程内的 service 实现,system_server 因为某些异常没有及时返回,导致主进程black 产生的ANR, 究其根本是system_server 异常引起的问题,常见原因为:

a、system_server 对应的service 正在等待某个锁,没有及时处理请求。

b、system_server 正在频繁的进行io操作(例如:system_server 正在抓取bugreport)

所以定位处理此类的问题的关键为:

问题产生时 dump 到的堆栈必须齐全

3. iowait 过高,此类异常是后台有进程在进行大量io磁盘操作,导致cpu空转,严重影响系统性能,使发生ANR的主线程不能够获得cpu执行,因而产生的anr,例如:

迅雷app 后台以1~2M/s的速度下载数据,会造成整体较为卡钝,会有很大几率产生anr

这种异常从backtrace并不能看出异常,但问题发生的时间,往往伴随着大量anr, 此类问题,找出大量进行io操作的 进程即可,往往不需要具体定位。

ps:iowait 达到20% 就已经算非常高了

4. cpu占用率过高,查看backtrace 中cpu的信息,看那个进程占用cpu较高,具体分析占用cpu较高的进程是否正常。

5. 内存过低, 手机在内存过低的情况下,系统会不断的LowMemeoryKiller优先级低进程,进行整体GC释放内存,对性能影响较大,也是无法直接从backtrace 中看出有效信息的,需要解决当时情况,去分析是否属于正常。

6. 还有一种是 很麻烦的情况,进程卡钝产生了ANR, 但在抓取堆栈的时候,进程恢复过来了,因此 无法从堆栈中看出有效信息,且也是概率性的产生ANR,这类情况,如果发生概率较高,需要综合分析log,没有很好的处理方式。

Watchdog

watchdog 每过30s 检测一次, 如果 要监控的线程30s 后没有相应,系统会dump出此进程堆栈,如果超过60s 没有相应,会触发watchdog,并重启系统。

系统原生的watchdog 分为两种 monitorCheck 与handlerChecker 这两种类型, 在MTK平台上结合现有的流程,添加了一种用于检测watchdog线程的机制:hang_detect, 所以说watchdog的检测类型有三种:

1. handlerChecker 检测

用于检测固定的某些线程,通过向线程的handler post msg,如果检测的 handlerChecker 没有添加 monitorCheck,那么此 msg 只是用来检测一下对应线程能否正常处理事件,handlerChecker 固定检测的线程为:

a、 foreground thread 主要处理的事物为:

  • AccountManagerService
  • BatteryStatsService
  • DreamManagerService
  • MountService
  • NetworkManagementService
  • PackageManagerService
  • usb相关(debug, device, portmanager)
  • WindowManagerService(screenshotApplicationsInner)

b、 android.bg 主要处理的事物为:

  • mBgHandler.handleMessage()的两个消息:CHECK_INTERNET_PERMISSION_MSG、COLLECT_PSS_BG_MSG

c、 main thread 主要处理的事物为:

  • system_server 的主线程

d、 ui thread 主要处理的事物为:

  • AMS UiHandler里show各种msg
  • DisplayManagerService里的overlay相关msg
  • PointerEventDispatcher inputevent相关
  • VoiceInteractionManagerService Voice交互
  • WindowManagerPolicy init操作

e、 i/o thread 主要处理的事物为:

  • BluetoothManagerService 相关操作
  • MountService里的obb操作
  • Tethering 网络共享(usb /wifi/mobile?)
  • TvInputManagerService tv里channel session相关

f、 display thread 主要处理的事物为:

  • DisplayManagerService(display adapter,viewport ,event…)
  • InputManagerService (keyboard , input device …)
  • WindowManagerService 实例的创建

ps:正常情况下只有foreground thread 线程,才会存在 monitorCheck。

2. monitorCheck检测

monitorCheck主要用来检测系统常见的各个服务,在android系统里面,monitorCheck 是基于foreground thread 的 handlerChecker的,而monitorCheck检测的动作是由要检查的服务自己继承Monitor接口 去实现,目前各个service的实现是去检测各自对应的锁。

3. hang_detect检测

hang_detect 是 mtk 设计的用来检测 watchdog 自身是否正常的一套机制,它的实现方式为 在kernel 中专门注册了一个设备( /dev/RT_Monitor ),用于和上层通信 监控 watchdog是否正常。 
大概流程为:watchdog 向/dev/RT_Monitor 设置一个值,然后hang_detect 驱动根据此值,计算出一个时间,在此时间内,watchdog,必须再次通知一下hang_detect, 来表示并未超时,如果超时也去会dump 相关堆栈,重启手机。

正常情况下 watchdog 的解决办法,就是从watchdog 文件定位查找死锁原因,但也有一些复杂的情况,例如:

1、 死锁:watchdog 中最简单的情况,直接根据watchdog 一步步查找出来死锁即可

2、 native层 与java 层互掉造成的死锁, 这种情况比较少见,但也遇到过,例如:

乐视的三指截屏功能,java 层在system_server 里面,申请锁后 会调用到底层去实现 三指截屏的功能,而在底层,也会去申请一个native的方法锁,去进行三指截屏,截屏操作完,会回调java 层 发送广播,截屏完毕,而发送广播依赖java 锁, 然后native的锁就与 java 层的锁,死锁了。 
因为native的锁,并不会在 backtrace 体现,所以碰到这种情况的死锁,要堆栈结合代码,一步步定位,

3、 system_server的16 个 binder 全部堵塞,这种情况也比较少见,一般是因为system_server的某些异常引起的,我也是只遇到一次:

这个bug也是在乐视碰到的,某段时间,一些手机概率报出 同一类的watchdog,查看堆栈是:system_server 的16 个binder 线程全部处于等待状态,从堆栈看不出特异的情况, 
结合 发生问题时的log, 才确定问题:

进程Crash, 会出来一个 fc 弹窗,此弹窗是交互式的,会堵塞binder,等用户相应后,会释放binder, 而问题就发生在这里,一个进程如果同一时间发生两次 异常,那么android 会直接kill 此进程,并将 弹窗dismiss 了, 而乐视 覆写了 弹窗的dismiss 方法,没有在弹窗dismiss 时释放binder, 因而就造成一个binder堵塞, 多发生几次,system_server 的 16 个binder 就全部堵塞了。

ps:我写的有关watchdog 的博客,多多光顾,多多光顾

http://blog.csdn.net/xiaolli/article/details/62039795

http://blog.csdn.net/xiaolli/article/details/54906528

http://blog.csdn.net/xiaolli/article/details/72150384

Crash、界面、流程异常

crash 类型的问题,是非常常见的,问题原因也比较杂,但每人应该都有自己的定位方式,在此就介绍一些定位的工具与小方法:

1、jdb

jdb是 jdk 中 自带的一个调试命令,使用ddms 知道进程的端口号后,可以方便的attach到对应进程进行调试,也可以attach到指定进程后关联源码,结合源码进行调试,因为非图形界面,所以打断点一类的较为麻烦,但调试功能还算齐全。

ps: 因为jdb是在运行时进行调试的, 所以针对加固应用,也可以达到调试目的。

2、framework 调试

之前是eclipse关联到源码后,对framework 进行调试, 现在android studio 应该也支持此功能,但没有使用过,使用eclipse对framework进行调试,调试的过层类似调试apk

3、strace、ltrace

来源于linux的命令,用于获取指定进程的函数调用、底层库调用,这两个命令适用性较为广泛,tombstone、性能问题 也可以使用这两个命令区排查定位

  • strace: 此命令常用来跟踪进程执行时的系统调用和所接收的信号。 在Android、Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。

系统调用:可以通过对比分析异常情况与正常情况下的 系统调用流程与参数、返回值,来定位crash、tombstone的问题。

执行耗时:strace 这个命令可以统计系统调用消耗的时间,通过这个时间可以用来定位系统的性能问题。

  • ltrace: 此命令同strace,但它比的优势在于它可以用来跟踪进程调用库函数的情况,比strace 的系统调用,更为直观。

这两个命令网上,教程比较多,有兴趣可以从网上搜索查看

4、java异常的捕获

这是有两种情况:

  • 设置了异常捕获器:

这种类型常见的场景是,某些应用它自己设置了异常捕获器,设置的异常捕获器uncaught的优先级高于default。之后在应用发生crash时, 自己捕获到了当时的异常,自行内部处理消化了异常,不交由系统处理,然后退出了。从log里面看,进程就是正常退出的,找不到异常的原因。

针对这种情况的处理方式,就是在系统侧将设置异常捕获器的方法内部实现给注销了, 以便在应用发生crash时,异常信息走系统流程,获取更多异常发生时的信息,便于定位。需要处理的方法为:

Thread类中的:

setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 
setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)

  • 进行了try catch处理:

这种情况类似异常捕获器的情况,常见为:应用一段代码发生了异常,但它进行try catch 处理,使应用进入了异常处理流程。进程依然在前台运行,并没有实际退出,但应用此时的表现区别于正常。处理这种问题,也往往无法直接log中看出异常,也需要在framework中进行特殊处理:

一般异常对象(error object),都是在异常发生的时候创建的。 而所有的异常对象,最后都会继承Throwable类,因而可知 在异常发生时,一定会调用Throwable 的构造方法,所以我们可以在 Throwable 中构造方法中,将当时的异常堆栈打印到log。之后可以从log 进行排查异常(这种方式log里面的打印的 异常堆栈会比较多,需要根据准确时间点去查找定位)

滴滴打车,之前版本有一个bug,打完车,给的代金卷,概率 无法分享到微信(点击分享,闪一下,又会回到代金卷页面), 就是此类问题

5、crash2anr

crash 是瞬时的,系统获取到异常信息就会把进程kill(java 层产生的异常),但有可能获取到的异常信息不全,需要异常发生时的现场才能定位, 这就需要保留当时crash的瞬时现场,为了达到这个目的可以将crash 转换为anr进行处理, 可以在RuntimeInit.java 里默认的异常捕获器进行处理, 也可以在 ActivityManagerService.java 的handleApplicationCrash 方法里面进行处理。

6、double crash

如果一个进程在同一时间点,同时发生两次异常,那么系统不会去捕获进程的异常信息,会直接退出。针对这种情况,也需要在framework中做处理: 
因为java世界的进程fork自zygote,所以只要给zygote 设置了 异常捕获器,那么之后所有的进程都自动会存在异常捕获器,而zygote 的异常捕获器,在开机时是在RuntimeInit.java 设置:

        protected static final void commonInit() {
            ......
            Thread.setUncaughtExceptionPreHandler(new LoggingHandler());
            Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler());
            ......
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在DefaultUncaughtExceptionHandler KillApplicationHandler 对象里面发生异常时的处理方式为:

        private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
            public void uncaughtException(Thread t, Throwable e) {
                try {
                    if (mCrashing) return;
                    mCrashing = true;
                    ......
                    ActivityManager.getService().handleApplicationCrash(
                            mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
                } catch (Throwable t2) {
                   ......
                } finally {
                    Process.killProcess(Process.myPid());
                    System.exit(10);
                }
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

由代码可以看出,进程第一次发生异常,会通过handleApplicationCrash方法 调用ams方法 去获取异常信息,当同一时间第二次发生异常时,会直接kill进程,造成第一次的异常信息无法获取到。 可以修改此方法,在finally内, 将第二次的异常信息打印到log,并不实际去kill进程。

7、JEB

这是一个专业的用来反编译apk的付费软件,但网上有大牛分享的破解后的软件,有钱的可以支持一下正版,没钱可以从网上搜索破解版的下载,这个软件的特殊之处为:

  • 有window、mac、linux 的版本

  • 因为是商业软件,比较稳定且还在不断的更新

  • 方便,使用软件去打开apk,会自动进行反编译,并在内部关联反编译后函数,可以直接在软件很方便的浏览反编译后的代码

  • 调试,可以结合反编译后的代码,使用软件直接attach 到对应进程,进行调试

………….1

功能较为强大,有兴趣可以自行尝试摸索

8、apkanalyser

这是索尼出的一个很早期的一个跨平台软件,目前好像不在维护了,但旧版本依然可以使用, 它可以很方便的在 apk 的方法头或方法尾打桩, 去调试三方apk的调用流程.

ps:它缺点也较为明显,只能在方法头或方法尾打桩, 不能在方法中间进行处理,还有就是没有办法处理加固的应用。

9、smali

smali使用起来比较麻烦,但只要熟悉smali 语言,然后使用smali、baksmali 两个命令,可以实现任意打桩、修改apk流程、添加代码等目的,非常的强大。

ps:可以参考的博客

http://blog.csdn.net/xiaolli/article/details/51039600

https://www.cnblogs.com/daisin/articles/5512813.html

Tombstone

Tombstone是进程在native层挂了,然后由linker检测到异常,链接debuggerd去捕获异常信息,linker注册异常函数的流程为:

/bionic/linker/arch/arm64/begin.S
    /bionic/linker/linker_main.cpp(__linker_init)
    /bionic/linker/linker_main.cpp(__linker_init_post_relocation)
        /system/core/debuggerd/handler/debuggerd_handler.cpp(debuggerd_init)
        /system/core/debuggerd/handler/debuggerd_handler.cpp(使用debuggerd_register_handlers方法,将进程的相关异常信号的处理函数设置为 debuggerd_signal_handler)
  • 1
  • 2
  • 3
  • 4
  • 5

从流程中可知,系统在linker中使用debuggerd_register_handlers 函数,将进程的所有异常信号处理函数,全部注册为了debuggerd_signal_handler, 所以如果进程没有二次设置异常函数,当进程native 层发生异常,系统会自动进入debuggerd_signal_handler 函数中,进行异常处理,流程为:

/system/core/debuggerd/handler/debuggerd_handler.cpp(debuggerd_signal_handler)
/system/core/debuggerd/handler/debuggerd_handler.cpp(log_signal_summary)
  • 1
  • 2
  • 3

在debuggerd_signal_handler 函数中,会先通过log_signal_summary函数获取一下简单的消息,然后获取一下简单的消息头, 
之后在去获取堆栈信息,但8.0的代码与之前的代码有区别, 8.0之前的代码,会通过socket链接debuggerd,获取堆栈, 但在8.0上好像不用了。

获取堆栈信息后,根据此流程与堆栈延伸出来的定位方法为:

1、addr2line

tombstone的堆栈是以库地址调用展示,无法直接观看,需要使用对应版本带有符号标的 so库,结合 addr2line, 对堆栈进行解析观看,并且如果堆栈信息较少,也通过计算查看 stack的 调用信息, stack是backtrace里面函数调用的展开:

    backtrace:
        #00 pc 0000000000015730  /system/lib64/libc.so (flockfile+24)

    stack:
         0000007fe718d138  0000007f7d045db0  /system/lib64/libc.so (dlmalloc+240)
         0000007fe718d168  0000007f7d01eeac  /system/lib64/libc.so (open64+132)
         0000007fe718d178  0000007f7d05d3b0  /system/lib64/libc.so (vfprintf+28)
         0000007fe718d188  0000007f7d05d3e0  /system/lib64/libc.so (vfprintf+76)
         0000007fe718d190  0000007f7d0a4488  /system/lib64/libc.so
    memory map: (fault address prefixed with --->)
        0000007f7d007000-0000007f7d08efff r-x   557056  /system/lib64/libc.so   
        0000007f7d08f000-0000007f7d09dfff ---    61440  
        0000007f7d09e000-0000007f7d0a1fff r--    16384  /system/lib64/libc.so
        0000007f7d0a2000-0000007f7d0a4fff rw-    12288  /system/lib64/libc.so
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

解析backtrace:

addr2line -fe libc.so(对应版本带符号表的so库) 0000000000015730(偏移地址)

解析stack:

libc.so 库的在系统中的基地址为:0000007f7d007000

addr2line -fe libc.so 0000007fe718d138-0000007f7d007000(绝对地址-基地址=偏移地址)

2、objdump

此命令可以将so库反编译,然后根据backtrace里面的地址查看当时的汇编指令,跟踪汇编的调用流程,去定位问题。

3、coredump

android里面coredump默认是关闭的,如果打开coredump,那当系统收到异常信号ARORT/SEGV时,系统会终止当前进程,并保留下来手机当时出现异常时的现场数据,类似于照相机按下快门的一瞬间,得到的照片即为我们的coredump。通常情况下coredmp包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息等。可以理解为把程序工作的当前状态存储成一个文件。我们可以使用gdb命令 结合core文件,去定位当时发生的问题。

coredump在android上的打开方式: 
http://blog.csdn.net/xiaolli/article/details/51058300

ps:开启coredump后,默认只能抓取native进程的core文件,java层进程 的core文件,需要修改代码才能生成。

4、gdb

gdb 类似jdb,只不过他是用来调试native的,android 版本的gdb 有两种:


  • 通过google 提供的两个命令:gdb、gdbserver,来调试进程
  • 交叉编译android版本的gdb: 

http://blog.csdn.net/xiaolli/article/details/52650036

gdb 的使用方法是:将带有 symbols 的so库,push到机器中,然后attach到需要需要调试的进程,进行调试。 
gdb 的用法较多,可以从网上查询

5、tombstone2anr

这个类似crash,有时获取的异常信息,不足以定位问题,需要获取当时tombstone的现场,但tombstone 是瞬时的,所以也需要将tombstone 转换为anr才行。

但Android中进程如果因为 tombstone 被kill,那么他的异常信号是在native 层处理的,处理之后进程died,然后才会通知ams进行后续的扫尾操作,因为进程在通知ams之前已经死亡,所以之前做的 crash2anr 并不好使。

tombstone2anr 的修改,是在debuggerd_handler.cpp的log_signal_summary方法里面,添加一个循环休眠即可。

6、oat文件的 backtrace 定位

这是5.0 换成art 虚拟机之后,出现的一种特殊的tombstone堆栈,它是没有办法使用addr2line来反编译的,它的栈帧是每一个oat文件,如果要查看此堆栈需要将对应的整个oat文件反编译,然后计算 栈帧 在oat文件中的位置,然后从反编译后的文件中,查找此位置,进行查看:

        backtrace 为:
            #00 pc 00000000026c0024 /data/dalvik-cache/arm64/system@framework@boot.oat (offset 0x26c0000)
            #01 pc 00000000026e9d38  /data/dalvik-cache/arm64/system@framework@boot.oat (offset 0x26c0000)
        当时oat 的map 为:
        70cfb000-733bb000 r--p 00000000 fd:00 2909220                                       /data/dalvik-cache/arm64/system@framework@boot.oat
        733bb000-75d9f000 r-xp 026c0000 fd:00 2909220                            /data/dalvik-cache/arm64/system@framework@boot.oat
        75d9f000-75da0000 rw-p 050a4000 fd:00 2909220                            /data/dalvik-cache/arm64/system@framework@boot.oat
        7f9d97e000-7f9d97f000 r--p 00000000 fd:00 2909220                        /data/dalvik-cache/arm64/system@framework@boot.oat
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

boot.oat 可执行代码的加载地址为:733bb000

相对于当时出问题的oat 文件,代码段的偏移地址为:0x26c0000

相对于当时出问题的oat 文件, 发生问题的代码在oat 中的偏移地址为:00000000026e9d38

*编译或从对应手机中pull 出一个boot.oat 文件使用oatdump进行反编译:

此boot.oat文件,代码段的偏移地址为:

在boot.oat 的反编译文件中搜索:EXECUTABLE OFFSET 对应的地址 (0x026bf000)

正常与异常时 代码段的地址相见:

0x26c0000 - 0x026bf000 = 0x00001000*

那么可知 发生异常时oat的代码段要比当前这个整体大 0x00001000

那么:

0x026c0024 - 0x00001000

0x026e9d38 - 0x00001000

即为backtrace,中地址转换到当前 oat 中的地址

ps:如果 backtrace 给的是绝对地址,还需要减去733bb000,去计算

ps:参考我的博客(多多观光,多多观光) 
http://blog.csdn.net/xiaolli/article/details/70789075

Panic

panic 是 kernel 里面发生了异常,引起的重启,在aplog里面是看不出异常的,需要从kernel log里面,分析查看当时的堆栈,找kernel 的同事定位,我不擅长啊。

稳定性除了这点东西,还有一些其他的例如:*

  • Libc.debug.malloc 内存泄漏的检测
  • 反编译、调试工具 IDA 的使用(付费的,可贵)
  • android 自带工具debuggerd命令 的使用
  • 源码里面自带的解析tombstone文件的命令 stack
  • 调试内存的工具dlmalloc 的使用 

    http://blog.csdn.net/zirconsdu/article/details/8351591

  • pstack 实时查看堆栈的命令
  • taskset 命令
  • am profile 命令
  • 分析功耗的Battery Historian

……………………………………
  • battery-historian试用
  • Memory Monitor工具
  • Heap Viewer工具
  • Allocation Tracker(Device Monitor)
  • Allocation Tracker(Android Studio)
  • Heap Snapshot工具
  • TraceView工具(Device Monitor)
  • Android性能专项测试之MAT
  • Systrace工具
  • Android性能专项测试之GPU Monitor
  • Network monitor 
    https://www.kancloud.cn/digest/itfootballprefermanc/100915
  • 5
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值