使用adb shell dumpsys检测Android的Activity任务栈

谈起Android程序开发,就需要了解其四个主要的部件ActivityServiceContentProviderBroadcastReceiver。而其中Activity是唯一直接控制程序界面呈现,直面用户操作的部件(当然BrowadCastReceiver也能通过桌面控件(App Widgets)来呈现有限的操作界面)。Android对于Activity有严格的生命周期控制,以限制开发者在适当的回调函数里的放上合适的代码。对于多个Activity的转换,Android也有非常好的管理和流畅的切换,对此Android还引入了任务栈(Task Stack)的概念,这个概念对于Android设备上得返回按键有极其重要的联系。

(大部分文档都将其表述为Tasks and Back Stack,但从官方文档的描述来看,Android的相对于Activity讲到的Task都视为一个存放Activities的Stack,所以将其称为Task Stack也不为过。)

在AndroidManifest中申明所要用到的Activity时可以设置不同的launchMode来得到不同的Activity“启动”效果。在使用startActivity开启新的Activity时,传入的Intent也可以设置不同的Flag来达到不同的效果。另一方面,在Activity启动时它可能又开启了另一个Activity,或者调用了finish()函数终结了Activity。

这使得Activity栈变得无法掌握,有时候按下返回按钮或者点击关闭当前Activity的操作,都不知道Android系统会把程序带到那个Activity,不确定这是否是最后一个Activity以致退出了整个程序。亦或者一些按钮和操作循环产生Activity而造成内存膨胀。对于这些问题,如果能够在调试期间知道当前任务栈的情况,就能很方便的观察和发现问题存在的原因,进而选择正确的launchMode,设置恰当IntentFlag来使程序达到预期的效果。

通过ActivityManager获取状态

Android提供了ActivityManger来帮助开发者了解运行期间的状态,通过调用getRunningTasks(int)方法,就可以在得到RunningTaskInfo的列表,其代表着当前Android设备正在运行着的Task。从RunningTaskInfo中又可以进一步得到更多的信息。

ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> runningTaskInfoList =  am.getRunningTasks(10);
for (RunningTaskInfo runningTaskInfo : runningTaskInfoList) {
    log("id: " + runningTaskInfo.id);
    log("description: " + runningTaskInfo.description);
    log("number of activities: " + runningTaskInfo.numActivities);
    log("topActivity: " + runningTaskInfo.topActivity);
    log("baseActivity: " + runningTaskInfo.baseActivity.toString());
}

例如文中提供的 示例程序 中定义了4个具有不同 launchMode 的Activity,每点击一次菜单栏上得选项就会弹出一个新的Activity(或者将指定Singleton的Activity置前)。


Activity上显示的数字则指示startActivity()被第几次调用时开启了这个Activity。有一些Singleton的会显示多个数字,也表明它是被复用的。


因为在onCreate()方法上放置了上述代码,所以观察log就能发现当前有多少个Task在被执行,每个Task又有多少个Activities。

D/IDER    ( 3700): =====================
D/IDER    ( 3700): ---------------------
D/IDER    ( 3700): id: 25
D/IDER    ( 3700): description: null
D/IDER    ( 3700): number of activities: 4
D/IDER    ( 3700): topActivity: ComponentInfo{com.iderzheng/com.iderzheng.StandardActivity}
D/IDER    ( 3700): baseActivity: ComponentInfo{com.iderzheng/com.iderzheng.SingleTaskActivity}
D/IDER    ( 3700): ---------------------
D/IDER    ( 3700): id: 24
D/IDER    ( 3700): description: null
D/IDER    ( 3700): number of activities: 1
D/IDER    ( 3700): topActivity: ComponentInfo{com.iderzheng/com.iderzheng.SingleInstanceActivity}
D/IDER    ( 3700): baseActivity: ComponentInfo{com.iderzheng/com.iderzheng.SingleInstanceActivity}
D/IDER    ( 3700): ---------------------
D/IDER    ( 3700): id: 23
D/IDER    ( 3700): description: null
D/IDER    ( 3700): number of activities: 2
D/IDER    ( 3700): topActivity: ComponentInfo{com.iderzheng/com.iderzheng.StandardActivity}
D/IDER    ( 3700): baseActivity: ComponentInfo{com.iderzheng/com.iderzheng.MainActivity}
D/IDER    ( 3700): ---------------------
D/IDER    ( 3700): id: 1
D/IDER    ( 3700): description: null
D/IDER    ( 3700): number of activities: 1
D/IDER    ( 3700): topActivity: ComponentInfo{com.android.launcher/com.android.launcher2.Launcher}
D/IDER    ( 3700): baseActivity: ComponentInfo{com.android.launcher/com.android.launcher2.Launcher}

缺点

必须在程序中注入调试代码,因为要控制在发布时代码必须被清理了。RunningTaskInfo虽然能够告诉我们有多少个Activity保存在其上,但是没有提供完整的列表,只能看到头尾两个Activity。给出的两个Activity的属性:topActivity和baseActivity也只是ComponentName类型,并非真实的Activity对象,因此除了类的名字没有其他更多信息。

手动记录和管理Activities栈

Activity的创建和销毁都会有相应的回调函数:onCreate()onDestroy()。因此可以自建一个静态全局Stack对象,在onCreate()时候讲当前Activity对象加入到Stack中,而在onDestroy()时把它从Stack中移除。这样我们就随时可以知道当前Activity的详细情况了。

要让所有Activity的onCreate()onDestroy()方法上有对应的进出栈的方法,要么有统一的基类,要么强制每个Activity都加入这些代码,但两种方式都不完美。另外也很难模拟singleTask这类会创建出新的Task的情况,这时光使用一个Stack就不足够了,要考虑所有的情况又不太可能。再者如同使用ActivityManager一样这些代码也应该只出现在调试阶段

使用adb shell指令

Android还为开发者提供了adb(Android Debug Bridge),这是非常强大的调试工具。最常用的自然是logcat来显示日志记录。另外一个很强大的指令就是这里要提到的dumpsysdumpsys还可以添加不同的参数来指示需要输出哪一类Service的信息。对于本文提到的内容,需要查看的就是activity,指令就是:

adb shell dumpsys activity

输入上述指令,就能得到关于设备非常长的一段讯息,单是也能清晰看出它们比较详细的分类

ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents)
  * PendingIntentRecord{42b05f20 com.android.vending startService}
  ... ... ... ...

ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)
  Historical broadcasts [foreground]:
  #0: BroadcastRecord{430d2fb8 u-1 android.intent.action.TIME_TICK}
    act=android.intent.action.TIME_TICK flg=0x50000014 (has extras)
    extras: Bundle[{android.intent.extra.ALARM_COUNT=1}]
  ... ... ... ...

ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers)
  Published single-user content providers (by class):
  * ContentProviderRecord{429d18a8 u0 com.android.phone/.IccProvider}
    proc=ProcessRecord{429765d8 858:com.android.phone/1001}
    singleton=true
    authority=icc
  ... ... ... ...

ACTIVITY MANAGER SERVICES (dumpsys activity services)
  User 0 active services:
  * ServiceRecord{429f8668 u0 com.android.bluetooth/.hid.HidService}
    app=null
    created=-1h44m27s317ms started=false connections=0
  ... ... ... ...

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
  Stack #0:
    Task id #28
      TaskRecord{43525058 #28 A=com.android.systemui U=0 sz=1}
      Intent { act=com.android.systemui.recent.action.TOGGLE_RECENTS flg=0x10c00000 cmp=com.android.systemui/.recent.RecentsActivity (has extras) }
        Hist #0: ActivityRecord{428d1ae8 u0 com.android.systemui/.recent.RecentsActivity t28}
          Intent { act=com.android.systemui.recent.action.TOGGLE_RECENTS flg=0x10800000 cmp=com.android.systemui/.recent.RecentsActivity bnds=[328,886][656,1176] }
          ProcessRecord{42968230 695:com.android.systemui/u0a12}
  ... ... ... ...

ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
  Process LRU list (sorted by oom_adj,28 total,non-act at 3,non-svc at 3):
    PERS #27: sys   F/ /P  trm: 0 605:system/1000 (fixed)
  ... ... ... ...

每一个类别都有一个括号内容,给出了更加详细的指令来查看该类别下更多具体内容。因此再来尝试指令:

db shell dumpsys activity activities

就能看到下边的结果

CTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
  Stack #0:
    Task id #28
    * TaskRecord{43525058 #28 A=com.android.systemui U=0 sz=1}
      ... ... ... ...
      * Hist #0: ActivityRecord{428d1ae8 u0 com.android.systemui/.recent.RecentsActivity t28}
          ... ... ... ...
    Task id #1
    * TaskRecord{429a35f8 #1 A=com.android.launcher U=0 sz=1}
      ... ... ... ...
      * Hist #0: ActivityRecord{429a1760 u0 com.android.launcher/com.android.launcher2.Launcher t1}
          ... ... ... ...

    Running activities (most recent first):
      TaskRecord{43525058 #28 A=com.android.systemui U=0 sz=1}
        Run #1: ActivityRecord{428d1ae8 u0 com.android.systemui/.recent.RecentsActivity t28}
      TaskRecord{429a35f8 #1 A=com.android.launcher U=0 sz=1}
        Run #0: ActivityRecord{429a1760 u0 com.android.launcher/com.android.launcher2.Launcher t1}

    mLastPausedActivity: ActivityRecord{428d1ae8 u0 com.android.systemui/.recent.RecentsActivity t28}

  Stack #1:
    Task id #25
    * TaskRecord{42b0ee20 #25 I=com.iderzheng/.SingleTaskActivity U=0 sz=5}
      numActivities=5 rootWasReset=false userId=0 mTaskType=0 numFullscreen=5 mOnTopOfHome=true
      intent={cmp=com.iderzheng/.SingleTaskActivity}
      realActivity=com.iderzheng/.SingleTaskActivity
      Activities=[ActivityRecord{42a7e160 u0 com.iderzheng/.SingleTaskActivity t25},ActivityRecord{42bffdf0 u0 com.iderzheng/.StandardActivity t25},ActivityRecord{42e9e8f8 u0 com.iderzheng/.SingleTopActivity t25},ActivityRecord{434c2238 u0 com.iderzheng/.StandardActivity t25},ActivityRecord{4279d2d8 u0 com.iderzheng/.SingleTopActivity t25}]
      askedCompatMode=false
      lastThumbnail=null lastDescription=null
      lastActiveTime=6229735 (inactive for 357s)
      * Hist #4: ActivityRecord{4279d2d8 u0 com.iderzheng/.SingleTopActivity t25}
          packageName=com.iderzheng processName=com.iderzheng
          launchedFromUid=10124 launchedFromPackage=com.iderzheng userId=0
          app=ProcessRecord{4312cbb0 3700:com.iderzheng/u0a124}
          Intent { cmp=com.iderzheng/.SingleTopActivity bnds=[328,580][656,870] }
          frontOfTask=false task=TaskRecord{42b0ee20 #25 I=com.iderzheng/.SingleTaskActivity U=0 sz=5}
          taskAffinity=com.iderzheng
          realActivity=com.iderzheng/.SingleTopActivity
          baseDir=/data/app/com.iderzheng-1.apk
          dataDir=/data/data/com.iderzheng
          stateNotNeeded=false componentSpecified=true mActivityType=0
          compat={320dpi} labelRes=0x7f0a0013 icon=0x7f020057 theme=0x7f0b0000
          config={1.0 310mcc?mnc en_US ldltr sw384dp w384dp h567dp 320dpi nrml port finger -keyb/v/h -nav/h s.7}
          launchFailed=false launchCount=0 lastLaunchTime=-1h40m33s397ms
          haveState=false icicle=null
          state=RESUMED stopped=false delayedResume=false finishing=false
          keysPaused=false inHistory=true visible=true sleeping=false idle=true
          fullscreen=true noDisplay=false immersive=false launchMode=1
          frozenBeforeDestroy=false thumbnailNeeded=false forceNewConfig=false
          mActivityType=APPLICATION_ACTIVITY_TYPE
          thumbHolder: 42b0ee20 bm=null desc=null
          waitingVisible=false nowVisible=true lastVisibleTime=-5m56s862ms
    ... ... ... ...

    Running activities (most recent first):
      TaskRecord{42b0ee20 #25 I=com.iderzheng/.SingleTaskActivity U=0 sz=5}
        Run #7: ActivityRecord{4279d2d8 u0 com.iderzheng/.SingleTopActivity t25}
      TaskRecord{429e9558 #24 A=com.iderzheng U=0 sz=1}
        Run #6: ActivityRecord{429d5408 u0 com.iderzheng/.SingleInstanceActivity t24}
      TaskRecord{42b0ee20 #25 I=com.iderzheng/.SingleTaskActivity U=0 sz=5}
        Run #5: ActivityRecord{434c2238 u0 com.iderzheng/.StandardActivity t25}
        Run #4: ActivityRecord{42e9e8f8 u0 com.iderzheng/.SingleTopActivity t25}
        Run #3: ActivityRecord{42bffdf0 u0 com.iderzheng/.StandardActivity t25}
        Run #2: ActivityRecord{42a7e160 u0 com.iderzheng/.SingleTaskActivity t25}
      TaskRecord{4282e508 #23 A=com.iderzheng U=0 sz=2}
        Run #1: ActivityRecord{429655d8 u0 com.iderzheng/.StandardActivity t23}
        Run #0: ActivityRecord{429564e0 u0 com.iderzheng/.MainActivity t23}
 
  ... ... ... ...

  Recent tasks:
  ... ... ... ...

整个log显示了当前所有在运行的任务栈,它们的id分别是什么。对于每个Task,也有Activity数量等信息,同时也列出了其中的Activity列表,并且对于每个Activity也有比较详细的描述,比如启动它的Intent的内容。

如果觉得内容过多,只想看看栈的内容,也可以直接跳到”Running activities (most recent first)”那部分,比较简洁而又明了的列出了栈中得Activity列表,就能知道当按下返回键的时候会应该会回到哪个Activity以后是要退出程序。

对于”Running activitie”s的内容在dumpsys activity中就有,并不需要dumpsys activity activities,也可以用下边的指令来限制仅输出”Running activities”列表:

adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'

很明显的看出,使用adb shell的相对于之前的方式的明显好处就是不需要添加额外的代码,而且任务栈的信息也更加详尽。但是同样的它只能输出Activity的类名,对于具体属性没有记录。

adb shell对于调试Android程序有很多的帮助,可惜对于adb指令都没有比较全面详细而又系统的教程。只能靠在实践中慢慢摸索,从网上零星介绍中获得。

Download Source Code for Testing

References:
  1. Android Debug Bridge | Android Developers
  2. How to know which activities are running in Android | The skiing cube
  3. Wrightrocket: Using dumpsys commands in the Android adb shell
  4. activity – How to find back stack activities in an android application? – Stack Overflow
  5. What’s Android ADB shell ‘dumpsys’ tool and it’s benefits? – Stack Overflow


转自:http://blog.iderzheng.com/debug-activity-task-stack-with-adb-shell-dumpsys/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


用的openwrt路由器,家里宽带申请了动态公网ip,为了方便把2280端口映射到公网,发现经常被暴力破解,自己写了个临时封禁ip功能的脚本,实现5分钟内同一个ip登录密码错误10次就封禁这个ip5分钟,并且进行邮件通知使用步骤openwrt为19.07.03版本,其他版本没有测试过安装bashmsmtpopkg
#!/bin/bashcommand1&command2&wait从Shell脚本并行运行多个程序–杨河老李(kviccn.github.io)
1.先查出MAMP下面集成的PHP版本cd/Applications/MAMP/bin/phpls-ls 2.编辑修改.bash_profile文件(没有.bash_profile文件的情况下回自动创建)sudovim~/.bash_profile在文件的最后输入以下信息,然后保存退出exportPATH="/Applications/MAMP/bin/php/php7.2.20/b
1、先输入locale-a,查看一下现在已安装的语言2、若不存在如zh_CN之类的语言包,进行中文语言包装:apt-getinstalllanguage-pack-zh-hans3、安装好后我们可以进行临时修改:然后添加中文支持: locale-genzh_CN.UTF-8临时修改> export LC_ALL='zh_CN.utf8'> locale永久
BashPerlTclsyntaxdiff1.进制数表示Languagebinaryoctalhexadecimalbash2#[0~1]0[0~7]0x[0~f]or0X[0~f]perl0b[0~1]0[0~7]0x[0~f]tcl0b[0~1]0o[0~7]0x[0~f]bashdifferentbaserepresntationreference2.StringlengthLanguageStr
正常安装了k8s后,使用kubect工具后接的命令不能直接tab补全命令补全方法:yum-yinstallbash-completionsource/usr/share/bash-completion/bash_completionsource<(kubectlcompletionbash)echo"source<(kubectlcompletionbash)">>~/.bashrc 
参考这里启动jar包shell脚本修改过来的#!/bin/bash#默认应用名称defaultAppName='./gadmin'appName=''if[[$1&&$1!=0]]thenappName=$1elseappName=$defaultAppNamefiecho">>>>>>本次重启的应用:$appName<
#一个数字的行#!/bin/bashwhilereadlinedon=`echo$line|sed's/[^0-9]//g'|wc-L`if[$n-eq1]thenecho$linefidone<1.txt#日志切割归档#!/bin/bashcd/data/logslog=1.logmv_log(){[-f$1]&&mv$1$2
#文件增加内容#!/bin/bashn=0cat1.txt|whilereadlinedon=[$n+1]if[$n-eq5]thenecho$lineecho-e"#Thisisatestfile.\n#Testinsertlineintothisfile."elseecho$linefidone#备份/etc目录#
# su - oraclesu: /usr/bin/ksh: No such file or directory根据报错信息:显示无法找到文件 /usr/bin/ksh果然没有该文件,但是发现存在文件/bin/ksh,于是创建了一个软连接,可以规避问题,可以成功切换到用户下,但无法执行系统自带命令。$. .bash_profile-ksh: .: .b
history显示历史指令记录内容,下达历史纪录中的指令主要的使用方法如果你想禁用history,可以将HISTSIZE设置为0:#exportHISTSIZE=0使用HISTIGNORE忽略历史中的特定命令下面的例子,将忽略pwd、ls、ls-ltr等命令:#exportHISTIGNORE=”pwd:ls:ls-ltr:”使用HIS
一.命令历史  1.history环境变量:    HISTSIZE:输出的命令历史条数,如history的记录数    HISTFILESIZE:~/.bash_history保存的命令历史记录数    HISTFILLE:历史记录的文件路径    HISTCONTROL:     ignorespace:忽略以空格开头的命令
之前在网上看到很多师傅们总结的linux反弹shell的一些方法,为了更熟练的去运用这些技术,于是自己花精力查了很多资料去理解这些命令的含义,将研究的成果记录在这里,所谓的反弹shell,指的是我们在自己的机器上开启监听,然后在被攻击者的机器上发送连接请求去连接我们的机器,将被攻击者的she
BashOne-LinersExplained,PartI:Workingwithfileshttps://catonmat.net/bash-one-liners-explained-part-oneBashOne-LinersExplained,PartII:Workingwithstringshttps://catonmat.net/bash-one-liners-explained-part-twoBashOne-LinersExplained,PartII
Shell中变量的作用域:在当前Shell会话中使用,全局变量。在函数内部使用,局部变量。可以在其他Shell会话中使用,环境变量。局部变量:默认情况下函数内的变量也是全局变量#!/bin/bashfunctionfunc(){a=99}funcecho$a输出>>99为了让全局变量变成局部变量
1、多命令顺序执行;  命令1;命令2  多个命令顺序执行,命令之间没有任何逻辑联系&&  命令1&&命令2  逻辑与,当命令1正确执行,才会执行命令2||  命令1||命令2  逻辑或,当命令1执行不正确,才会执行命令2例如:ls;date;cd/home/lsx;pwd;who ddif=输入文件of=输
原博文使用Linux或者unix系统的同学可能都对#!这个符号并不陌生,但是你真的了解它吗?首先,这个符号(#!)的名称,叫做"Shebang"或者"Sha-bang"。Linux执行文件时发现这个格式,会把!后的内容提取出来拼接在脚本文件或路径之前,当作实际执行的命令。 Shebang这个符号通常在Unix系统的脚本
1、历史命令history[选项][历史命令保存文件]选项:-c:  清空历史命令-w:  把缓存中的历史命令写入历史命令保存文件 ~/.bash_historyvim/etc/profile中的Histsize可改存储历史命令数量历史命令的调用使用上、下箭头调用以前的历史命令使用“!n”重复执行第n条历史
目录1.Shell脚本规范2.Shell脚本执行3.Shell脚本变量3.1环境变量3.1.1自定义环境变量3.1.2显示与取消环境变量3.1.3环境变量初始化与对应文件的生效顺序3.2普通变量3.2.1定义本地变量3.2.2shell调用变量3.2.3grep调用变量3.2.4awk调用变量3.3
   http://www.voidcn.com/blog/wszzdanm/article/p-6145895.html命令功能:显示登录用户的信息命令格式:常用选项:举例:w显示已经登录的用户及正在进行的操作[root@localhost~]#w 11:22:01up4days,21:22, 3users, loadaverage:0.00,0.00,0.00USER