谈起Android程序开发,就需要了解其四个主要的部件:Activity、Service、ContentProvider、 BroadcastReceiver。而其中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
,设置恰当Intent
的Flag
来使程序达到预期的效果。
通过ActivityManager获取状态
Android提供了ActivityManger来帮助开发者了解运行期间的状态,通过调用getRunningTasks(int)
方法,就可以在得到RunningTaskInfo
的列表,其代表着当前Android设备正在运行着的Task。从RunningTaskInfo中又可以进一步得到更多的信息。
[codesyntax lang=”java” lines=”normal”]
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()); }
[/codesyntax]
例如文中提供的示例程序中定义了4个具有不同launchMode
的Activity,每点击一次菜单栏上得选项就会弹出一个新的Activity(或者将指定Singleton的Activity置前)。
Activity上显示的数字则指示startActivity()
被第几次调用时开启了这个Activity。有一些Singleton的会显示多个数字,也表明它是被复用的。
因为在onCreate()
方法上放置了上述代码,所以观察log就能发现当前有多少个Task在被执行,每个Task又有多少个Activities。
缺点
必须在程序中注入调试代码,因为要控制在发布时代码必须被清理了。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来显示日志记录。另外一个很强大的指令就是这里要提到的dumpsys
。dumpsys
还可以添加不同的参数来指示需要输出哪一类Service的信息。对于本文提到的内容,需要查看的就是activity
,指令就是:
[codesyntax lang=”bash”]
adb shell dumpsys activity
[/codesyntax]
输入上述指令,就能得到关于设备非常长的一段讯息,单是也能清晰看出它们比较详细的分类
每一个类别都有一个括号内容,给出了更加详细的指令来查看该类别下更多具体内容。因此再来尝试指令:
[codesyntax lang=”bash”]
db shell dumpsys activity activities
[/codesyntax]
就能看到下边的结果
整个log显示了当前所有在运行的任务栈,它们的id
分别是什么。对于每个Task,也有Activity数量等信息,同时也列出了其中的Activity列表,并且对于每个Activity也有比较详细的描述,比如启动它的Intent的内容。
如果觉得内容过多,只想看看栈的内容,也可以直接跳到”Running activities (most recent first)”那部分,比较简洁而又明了的列出了栈中得Activity列表,就能知道当按下返回键的时候会应该会回到哪个Activity以后是要退出程序。
对于”Running activitie”s的内容在dumpsys activity
中就有,并不需要dumpsys activity activities
,也可以用下边的指令来限制仅输出”Running activities”列表:
[codesyntax lang=”java”]
adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'
[/codesyntax]
缺点
很明显的看出,使用adb shell
的相对于之前的方式的明显好处就是不需要添加额外的代码,而且任务栈的信息也更加详尽。但是同样的它只能输出Activity的类名,对于具体属性没有记录。
adb shell
对于调试Android程序有很多的帮助,可惜对于adb指令都没有比较全面详细而又系统的教程。只能靠在实践中慢慢摸索,从网上零星介绍中获得。
很实用的信息,最近在学习Activity Launch Mode和adb shell dumpsys,就搜索到了博主这里。发现是个新的博客,希望博主多分享实用的心得。
Activity的Launch Mode加上一堆Flag确实需要花很多时间理解。adb shell很多指令也没有详细的文档说明,我之后会写一篇实用adb指令集。
读者的欣赏是我写作的动力。
这个博客不错,博主加油啊!
github上有现成的工具,TaskLogger
stack #0 这句代表什么含义呢? 很容易让人误解就一个任务栈呢。。。