正确姿势分析App占用CPU过高

         正确姿势分析App占用CPU过高

   客户就是上帝,放在那个行业都是亘古不变的道理(某些行业除外,你懂的)。最近客户反馈一个问题,就是他们开发的应用安装在我们的Android 5.xx版本上面严重卡顿(不要问我为啥用的还是Android 5.xx版本,某些行业的产品以稳为主,对新版本的追求吗就不是那么重要了),几乎不能正常操作,既然客户大兄弟提出了问题,那么我们就得解决不是,下面让我来记录这次怎么揪出为啥客户应用卡顿问题(其实就是CPU占用过高)。

注意:该问题是基于Android 5.xx版本分析。



复现问题

   客户提供了App,我们在我们的终端上面安装成功,然后运行。果真客户诚不欺我,卡的一逼。然后我在Android 5.xx版本模拟器和公司其它Android 5.xx终端上面测试都是ok的,更加气人的是在出问题的终端的升级版本Android 7.xx上面也木有,看来就只在此终端的5.xx版本才有。



分析问题

   既然问题已经复现,那么我们就得来分析问题了,下面来跟着我的步伐一步步分析:

定位问题App包名

这个吗方法比较多(反编译Apk,或者通过命令查看),我们使用的是通过dumpsys命令。打开该App,然后输入如下命令,具体如下:

root@CB03:/ # dumpsys activity top
TASK iSellerExpress.Android id=7
  ACTIVITY iSellerExpress.Android/md5d281e6724ce34eeb0b3d19a0ab5be4fe.HomeActivity 5020653 pid=4967
    Local Activity 21199253 State:
      mResumed=true mStopped=false mFinished=false
      mLoadersStarted=true
      mChangingConfigurations=false
      mCurrentConfig={1.0 ?mcc?mnc zh_CN ldltr sw360dp w360dp h567dp 320dpi nrml port finger -keyb/v/h -nav/h s.6}
    Active Fragments in 149b829f:
      #0: ReportFragment{dfd3fec #0 android.arch.lifecycle.LifecycleDispatcher.report_fragment_tag}
        mFragmentId=#0 mContainerId=#0 mTag=android.arch.lifecycle.LifecycleDispatcher.report_fragment_tag
        mState=5 mIndex=0 mWho=android:fragment:0 mBackStackNesting=0
        mAdded=true mRemoving=false mResumed=true mFromLayout=false mInLayout=false
        mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
        mRetainInstance=false mRetaining=false mUserVisibleHint=true
        mFragmentManager=FragmentManager{149b829f in HomeActivity{21199253}}
        mActivity=md5d281e6724ce34eeb0b3d19a0ab5be4fe.HomeActivity@21199253
        Child FragmentManager{3ad64fb5 in ReportFragment{dfd3fec}}:
          FragmentManager misc state:
            mActivity=md5d281e6724ce34eeb0b3d19a0ab5be4fe.HomeActivity@21199253
            mContainer=android.app.Fragment$1@3487464a
            mParent=ReportFragment{dfd3fec #0 android.arch.lifecycle.LifecycleDispatcher.report_fragment_tag}
            mCurState=5 mStateSaved=false mDestroyed=false

查看App的CPU占用率

通过前面我们知道了App的包名是iSellerExpress.Android,这个时候就得用到Android内置命令工具top了可以看出App的CPU占用率,可以通过top命令分析了:

root@CB03:/ # top -m 5

User 1%, System 26%, IOW 0%, IRQ 0%
User 23 + Nice 0 + Sys 329 + Idle 882 + IOW 10 + IRQ 0 + SIRQ 1 = 1245

  PID PR CPU% S  #THR     VSS     RSS PCY UID      Name
 4967  1  24% S    24 1192608K 155428K  fg u0_a68   iSellerExpress.Android
11396  2   1% R     1   2784K   1260K  fg root     top
  264  2   0% S    15  67120K  12244K  fg system   /system/bin/surfaceflinger
  284  3   0% S     6  15768K   1776K  fg root     /system/bin/paxservice
  841  1   0% S   100 1098788K  92396K  fg system   system_server

尼玛,这个应用究竟干了啥,CPU占用率竟然感到了了24%,难怪这么卡顿,但是说也奇怪竟然没有报ANR的异常。


查看App的内存使用情况

root@CB03:/ # dumpsys meminfo iSellerExpress.Android
Applications Memory Usage (kB):
Uptime: 2918517 Realtime: 2918517

** MEMINFO in pid 4967 [iSellerExpress.Android] **
                   Pss  Private  Private  Swapped     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap    17848    17816        0      208    19392    19278      113
  Dalvik Heap    49578    49448        0       52    65932    49629    16303
 Dalvik Other      224      224        0        0
        Stack      164      164        0        0
      Gfx dev    60056    60056        0        0
    Other dev        5        0        4        0
     .so mmap     4270      216     3016      268
    .apk mmap    17037        0    16544        0
    .ttf mmap      163        0       92        0
    .dex mmap     5372        0     5368        0
    .oat mmap     1402        0      308        0
    .art mmap     1062      652      148        0
   Other mmap       33        4       12        0
   EGL mtrack    13440    13440        0        0
      Unknown    12112    12112        0       12
        TOTAL   182766   154132    25492      540    85324    68907    16416

 Objects
               Views:       42         ViewRootImpl:        1
         AppContexts:        4           Activities:        2
              Assets:        3        AssetManagers:        3
       Local Binders:       15        Proxy Binders:       16
       Parcel memory:        5         Parcel count:       20
    Death Recipients:        0      OpenSSL Sockets:        0

 SQL
         MEMORY_USED:        0
  PAGECACHE_OVERFLOW:        0          MALLOC_SIZE:        0

root@CB03:/ #

可以发现内存占用的情况不是严重,但是Views有点多。


查看cpu具体使用情况

感谢Android的妈咪谷歌为Android内置了许多强大的命令工具,其中dumpsys就是一个,下面依然使用dumpsys分析:

130|root@CB03:/ # dumpsys cpuinfo iSellerExpress.Android
Load: 6.34 / 6.32 / 6.08
CPU usage from 10631ms to 4614ms ago:
  98% 4967/iSellerExpress.Android: 5.3% user + 93% kernel / faults: 51669 minor
  2.4% 264/surfaceflinger: 0.1% user + 2.3% kernel
  1.9% 2610/com.android.systemui: 1.3% user + 0.6% kernel / faults: 19 minor
  1.8% 284/paxservice: 0% user + 1.8% kernel
  1.3% 841/system_server: 0.9% user + 0.3% kernel / faults: 1 minor
  0.6% 10973/kworker/u8:2: 0% user + 0.6% kernel
  0.4% 33/kworker/u9:0: 0% user + 0.4% kernel
  0.4% 258/logd: 0.4% user + 0% kernel
  0% 334/adbd: 0% user + 0% kernel
  0.4% 4927/kworker/0:0: 0% user + 0.4% kernel
  0.4% 12248/kworker/1:3: 0% user + 0.4% kernel
  0.3% 4043/irq/215-408000.: 0% user + 0.3% kernel
  0% 4926/kworker/1:2: 0% user + 0% kernel
  0% 3/ksoftirqd/0: 0% user + 0% kernel
  0.1% 7/rcu_preempt: 0% user + 0.1% kernel
  0.1% 162/cfinteractive: 0% user + 0.1% kernel
  0% 165/mmcqd/0: 0% user + 0% kernel
  0% 3011/com.pax.daemon: 0% user + 0% kernel / faults: 4 minor
  0.1% 4928/mdss_fb0: 0% user + 0.1% kernel
  0.1% 5002/kworker/u8:4: 0% user + 0.1% kernel
  0.1% 6958/kworker/u8:3: 0% user + 0.1% kernel
28% TOTAL: 2% user + 25% kernel + 0.3% iowait + 0% softirq
root@CB03:/ #

可以看出,此时iSellerExpress.Android的CPU占用率主要在Kernel层,那么接下来该怎么继续分析呢,当然是查看App的调用堆栈信息了。


查看App调用堆栈信息

对于Android源码有一定研究的读者应该知道,对于Native进程可通过 debuggerd输出traces信息。可通过一条命令来获取指定Native进程的traces信息,例如输出pid=17529进程信息:

扫描二维码关注公众号,回复: 8610605 查看本文章
adb shell debuggerd -b 17529 //可指定进程pid

而我们这里的App,所以对于Java进程可通过kill -3向目标进程发送信号SIGNAL_QUIT, 输出相应的traces信息保存到目录/data/anr/traces.txt,此时通过前面的章节我们知道该App的PID是4967 ,执行命令:

root@CB03:/data/anr # kill -3 4967
root@CB03:/data/anr # ls
traces.txt
root@CB03:/data/anr #

导出traces.txt文件,查看分析:

----- pid 4967 at 2019-12-12 15:18:24 -----
Cmd line: iSellerExpress.Android
ABI: arm
Build type: optimized
Zygote loaded classes=3601 post zygote classes=303
Intern table: 39209 strong; 918 weak
JNI: CheckJNI is on; globals=416 (plus 1 weak)
Libraries: /data/app/iSellerExpress.Android-1/lib/arm/libmonodroid.so /system/lib/libandroid.so /system/lib/libaudioeffect_jni.so /system/lib/libcompiler_rt.so /system/lib/libjavacrypto.so /system/lib/libjnigraphics.so /system/lib/libmedia_jni.so /system/lib/librs_jni.so /system/lib/libsoundpool.so /system/lib/libwebviewchromium_loader.so libjavacore.so (11)
Heap: 24% free, 48MB/64MB; 59675 objects
Dumping cumulative Gc timings
Total number of allocations 221050
Total bytes allocated 66MB
Free memory 15MB
Free memory until GC 15MB
Free memory until OOME 111MB
Total memory 64MB
Max memory 160MB
Total mutator paused time: 0
Total time waiting for GC to complete: 11.590ms

DALVIK THREADS (22):
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 obj=0x72d60000 self=0xb7e140f0
  | sysTid=4967 nice=0 cgrp=default sched=0/0 handle=0xb6fd8bec
  | state=S schedstat=( 22705658502 3875307396 13896 ) utm=2008 stm=262 core=0 HZ=100
  | stack=0xbe472000-0xbe474000 stackSize=8MB
  | held mutexes=
  native: #00 pc 0000f9a8  /system/lib/libc.so (syscall+28)
  native: #01 pc 00013185  /system/lib/libc.so (__pthread_cond_timedwait_relative(pthread_cond_t*, pthread_mutex_t*, timespec const*)+56)
  native: #02 pc 0003b2e3  /system/lib/libhwui.so (???)
  native: #03 pc 0003b319  /system/lib/libhwui.so (???)
  native: #04 pc 00927513  /data/dalvik-cache/arm/system@framework@boot.oat (Java_android_view_ThreadedRenderer_nSyncAndDrawFrame__JJJF+134)
  at android.view.ThreadedRenderer.nSyncAndDrawFrame(Native method)
  at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:340)
  at android.view.ViewRootImpl.draw(ViewRootImpl.java:2548)
  at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2364)
  at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1994)
  at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1073)
  at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5903)
  at android.view.Choreographer$CallbackRecord.run(Choreographer.java:773)
  at android.view.Choreographer.doCallbacks(Choreographer.java:586)
  at android.view.Choreographer.doFrame(Choreographer.java:556)
  at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:759)
  at android.os.Handler.handleCallback(Handler.java:739)
  at android.os.Handler.dispatchMessage(Handler.java:95)
  at android.os.Looper.loop(Looper.java:135)
  at android.app.ActivityThread.main(ActivityThread.java:5259)
  at java.lang.reflect.Method.invoke!(Native method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:697)

这个明显看到,此时阻塞在了GPU渲染ThreadedRenderer.nSyncAndDrawFrame里面了。此时的我不得不怀疑是客户的UI是不是写得太水了,然后导致渲染出现问题了,可是为啥其它的Android 5.xx木有问题呢,那么接下来得使用Android利器TraceView进行分析了,如果有源码的话使用Android studio CPU Profiler分析更为好。


使用Android开发者选项

为了验证前面的是GPU渲染的问题,进入设置—>开发者选项将显示面(surface)更新打开,然后重新进入问题App界面,发现此时的界面一直在刷新,由于牵涉到客户的应用这里我就不录屏放上来了。通过上面的种种,这就更加确定了我的验证。且在该App进入后台以后,通过top命令也发现,cpu明显下降了:

130|root@CB03:/ #
130|root@CB03:/ # top | grep iSellerExpress.Android
 4967  1   0% S    22 1132564K 126908K  bg u0_a68   iSellerExpress.Android
 4967  1   0% S    22 1132564K 126908K  bg u0_a68   iSellerExpress.Android


使用DDMS的TraceView分析

(1)启动TraceView

开始准备使用DDMS的TraceView分析该App,关于TraceView的介绍可以参见正确姿势使用TraceView工具,可是连上eclipse发现怎么也找不到进程号,如下图

在这里插入图片描述

我想一定是该App设置了防止调试字段,让我们反编译一把(这里推荐网易的),果真如此啊,不出老夫所料啊:
在这里插入图片描述
难道就这么放弃了,当然不,修改源码然后重新编译固件放开对App的调试,具体的修改逻辑就不细述了,修改点如下。然后重新编译固件推入终端,重新开机,下面就是见证奇迹的时刻了。

tangkw@pd:~/ssd/Android_quacomm/msm8909-5-1-1$ git diff ./
diff --git a/frameworks/base/core/java/android/content/pm/PackageParser.java b/frameworks/base/core/java/android/content/pm/PackageParser.java
index 1fb39213f5..be818c9fab 100644
--- a/frameworks/base/core/java/android/content/pm/PackageParser.java
+++ b/frameworks/base/core/java/android/content/pm/PackageParser.java
@@ -2513,6 +2513,9 @@ public class PackageParser {
                 false)) {
             ai.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
         }
+               
+               if(pkgName.equals("iSellerExpress.Android"))
+                       ai.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
 
         if (sa.getBoolean(
                 com.android.internal.R.styleable.AndroidManifestApplication_vmSafeMode,
tangkw@pd:~/ssd/Android_quacomm/msm8909-5-1-1$ 


然后重新编译固件推入终端,重新开机,下面就是见证奇迹的时刻了。

在这里插入图片描述


(2)开始分析

有了前面的铺垫,现在正式开始分析,该trace文件大家可以到ddms_traceView.zip下载下来自行分析。
在这里插入图片描述
通过时间抽面板,我们可以看到cpu的占用一直消耗在ThreadedRenderer.nSyncAndDrawFrame上面,同时通过下面的函数面板可以看到其parents是ViewRootImpl.draw里面,所以更加确定了是UI渲染的问题。


使用addr2line工具分析so库调用

[xxx] xxx@pd:~/ssd/Android_quacomm/msm8909-5-1-1$ arm-linux-androideabi-addr2line -f -e out/target/product/msm8909/symbols/system/lib/libhwui.so  0003b319
_ZN7android10uirenderer12renderthread13DrawFrameTask9drawFrameExx
xxx/Android_quacomm/msm8909-5-1-1/frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp:77
[xxx] xxx@pd:~/ssd/Android_quacomm/msm8909-5-1-1$ arm-linux-androideabi-addr2line -f -e out/target/product/msm8909/symbols/system/lib/libhwui.so  0003b2e3
_ZN7android9Condition4waitERNS_5MutexE
xxx/Android_quacomm/msm8909-5-1-1/system/core/include/utils/Condition.h:106
[xxx] xxx@pd:~/ssd/Android_quacomm/msm8909-5-1-1$ 

addr2line的使用具体参见正确姿势使用arm-linux-androideabi-addr2line文章,此时可以看到定位到的是frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp的第77行,让我们看看这一样究竟做了啥,
在这里插入图片描述
此时可以看到阻塞在渲染线程queue这里,到这里虽然定位到了具体Android源码位置,但是由于没有App源码依然无法解决客户的问题。此时我心里一万只草泥马。此时的我真的不知道接下来怎么分析了。



解决问题

   通过前面的章节我们已经定位到了是,UI的渲染出了问题,但是说实话本人对这块也不是很熟悉,到此问题陷入了僵局,但是客户在催咋办呢。此时我想到了既然android模拟器上面没有问题,那么会不会有可能是芯片厂导致的呢,然后我将AOSP代码和我们的代码对比,这里推荐一个比较好的网站Android源码网站可以查看各个版本最新Android源码,终于找到了在硬件加速模块里面hwui和Android aosp有一处不同,具体如下,我们芯片平台添加的。
在这里插入图片描述
抱着试一试的态度,将此代码注释,然后重新编译固件,烧录。怀着忐忑的心情验证,果真是这句代码的问题,不过这块我现在也没有弄明白,为啥这句话导致的。所以写到这里我写不下去了,因为我也是连猜带蒙解决的。虽然解决这个CPU占用率过高有一定运气成分,但是该分析过程可以供各位读者学习。



写在最后

   最后间接通过和高通的沟通,发现上述改动确实是高通的工程师为了解决一个BUG而改动的,但是我感觉这个改法有点太粗暴,截图如下,放上链接https://source.codeaurora.org/quic/la/platform/frameworks/base/commit/libs/hwui/DamageAccumulator.cpp?h=LA.BR.1.1.3.c12&id=8cb546eace2b03bf85e4b42fb337cdbbb53c496c
在这里插入图片描述

发布了89 篇原创文章 · 获赞 92 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/tkwxty/article/details/103508374