一种Android运行时异常复现方法*

2016-08-10 03:43姜雨蒙
计算机与数字工程 2016年7期

姜雨蒙 严 悍

(南京理工大学计算机科学与工程学院 南京 210094)



一种Android运行时异常复现方法*

姜雨蒙严悍

(南京理工大学计算机科学与工程学院南京210094)

摘要在Android系统中发生异常,开发维护人员依赖现有的异常记录难以有效分析导致异常的原因,增加了系统维护的难度,软件质量难以保障。由于Android平台的特点,Java的异常处理机制提供的信息存在不足。针对传统日志信息杂乱缺失的问题,给出一个基于Android系统分层的解决方案,为重异常现提供了一种可行的、具有通用性的解决方法。

关键词Android; 运行时异常处理; 异常重现

Class NumberTN959.53

1引言

目前移动开发是软件开发的主流方式。但由于移动手机的多样性,用户操作的复杂性,移动软件投入使用后发生异常,开发人员对异常发生的根本原因很难定位。异常会导致用户业务中断,用户输入数据失效,不仅浪费用户处理业务时间,用户满意度降低。开发人员想要快速定位异常,修复异常并发布新版本来修复异常是非常困难的。

传统Java发生异常时能提供一组基础异常信息(Basic Exception Message,BEM)。BEM通常包含{方法全名,代码行,异常类型,异常消息及时间}。其中,方法全名包含方法所在的包、类。通过BEM信息可以得知发生异常时代码行。但导致异常通常是由之前的操作导致的。开发人员需要重现异常来协助定位和解决异常。

Android做为目前主流的移动手机操作系统,在进行Android软件开发时同样面临这样的问题。通过研究一些资料发现,传统在Android平台解决异常的方法是开发人员自己在程序关键位置做日志记录[1]。程序会定时将所有日志信息传递给服务器。但在移动平台,导致异常的原因一方面是因为移动平台的硬件环境复杂。有些异常只在部分手机上出现,而在别的手机上表现正常。另一方面日志信息繁杂,对解决异常问题缺乏系统性描述。有时候甚至会起到干扰作用[2]。因此,开发人员很难利用日志定位在用户端软件发生的异常。

因此,本文研究目的是:在异常发生后,协助开发维护人员根据记录提供的信息重现异常,加快异常定位速度。本文研究出发点是将异常作为一种“自解释对象”,能自我说明导致异常出现的条件,使开发维护人员能在特定语境中重现异常,准确分析其原因,进而采取措施来消除异常。

主要针对Android平台会引起程序崩溃的运行时异常的处理,本文试图给出一种改进Android日志系统的解决方案,使开发人员在面对移动平台定位异常困难的问题时,可以快速重现异常,加快定位速度,提升软件维护效率,从而提高软件的稳定性和质量。

研究依据如下: 1) 任何异常都有原因,不存在不能解释原因的异常。 2) 目前尚没有某种技术或方法能有效证明一个复杂系统无异常。尽管可捕获所有异常,但若不从根本上消除,它仍会再现。 3) 只要有足够有效的信息,异常原因是可分析的,而且大部分可消除[3]。

2Android程序分层模型

一个Android程序由数个模块组成,其中用户能直接感知的主要是Application,Activity和View三个部分。

2.1Android程序结构

Application是随着程序启动时创建的一个对象。在Android系统中,一个进程只与一个Application对象绑定,也就是说一个程序的生命周期与自身Application对象是完全一致的。在一个Application对象中,可以实现多个Activity对象[4]。

Activity是单独用于处理用户操作的类,是用户可视化的执行接口。Activity对象会在Application中创建,形成程序基础的可视化界面。用户行为必须在一个Activity对象中执行。Activity的生命周期包含七种方法:创建onCreate()、启动onStart()、恢复onResume()、暂停onPause()、停止onStop()、销毁onDestory(),重启onRestart()。由于Android采用IOC(Inversion of Control,控制反转)设计模式,当一个Activity进入某种状态时,都会主动按照一定顺序调用这七个方法[4]。

图1是Activity的生命周期,其中矩形框表明Activity在状态转换之间的回调操作,开发人员可重写实现以执行应用相关代码。

开发人员扩展Activity基类设计自己的子类,作为View控件的容器。

View作为所有控件的总称,是构建用户界面组件(如Button,TextView等)的基类,可对用户交互事件做出响应。一个View占用屏幕上的一个矩形区域并负责界面绘制和事件处理[5]。

图1 Activity的生命周期

一个Android应用程序主要分成三个部分:

1) Application对象,代表整个程序的生命周期,是承载Activity的容器;

2) Activity对象,呈现程序的界面,是承载View的容器;

3) View对象,响应用户操作的最小单位。

2.2Android程序的简化模型

本文研究目的是为了帮助开发人员重现在用户手机上程序发生的异常。而通常用户手机上发生的异常是由用户操作引起,而根源在于应用程序设计不当或缺陷造成的。因此从用户角度,将Android应用程序进行分层抽象,得到一个四层结构。不同层次都会发生异常。发生异常的层次越低,重现异常所需开销越少;层次越高,需要的成本越高。从高到底,这四个层次依次为

1) 系统层:在应用程序所在宿主机,主要包含硬件环境、系统ROM版本等。

2) 应用层:Application对象。

3) 活动层:Activity对象。

4) 操作层:View对象,支持用户交互操作。

2.3Android模型与异常关系

表1列出不同层次上异常发生的原因,及还原该异常所需要的代价。

本文关注在应用层、活动层和操作上异常还原,对于系统层的异常还原并不在本文的讨论范围中。

表1 不同层次的异常原因和还原异常成本

3系统监控与异常还原

为了还原异常,需要从两方面入手: 1) 对程序正常状态下,记录用户操作的必要信息。 2) 在发生异常时保留异常信息。

3.1模块设计

图2中,异常处理模块以上是一个Android程序正常运行部分。监控模块即为本文设计的模块,它可以监控Activity行为和View行为。行为记录会暂时存储在内存中。如果没有发生异常,这些记录不会被保存下来。当程序发生未处理的异常时,异常会被传入异常处理模块中处理。异常处理模块会将异常信息存储在本地磁盘。同时,监控的信息也会一并存入本地文件。

图2 监控系统结构图

3.2系统监控与记录

本文采用面向方面编程(Aspect-Oriented Programming,AOP)设计模式,主要使用语言AspectJ,对系统信息进行监控,实现监控代码对业务代码的剥离,减少对业务代码的干扰[7]。

3.2.1应用层监控

Activity需要记录的信息主要分为两部分: 1) 每个Activity的初始状态。 2) Activity的变化过程。

Activity对象的初始状态由自身的onCreate()方法和传入的数据决定[10]。Activity对象对另一个Activity对象传入数据通常方式是采用Intent类的对象实现。因此需要在对每个用Intent接收信息的Activity的onCreate()方法中记录Intent对象,从而可以还原Activity的初始状态。

利用AspectJ监听每个Activity的onCreate()方法,再用java反射机制从onCreate()方法中提取Intent对象。如果onCreate()方法中有使用Intent对象进行传值,那么将Intent对象存入一个HashMap中,key值为Activity的类名,value为由Activity传入的Intent对象。

根据图1,Activity进行状态转换时将会调用状态同名方法。所有状态方法的开头字母都是“on”,因此通过AspectJ监控Activity类中on开头的方法名,就能实现对活动层的监听。

在AspectJ中,可以通过定义Pointcut来实现这个效果[4]。如:

@Pointcut("execution(* *..DemoActivity.on*(..))")

监听了DemoActivity中所有on开头的方法名。通过监听状态方法的调用,可以得知活动层当前状态。将当前状态,与Activity类名,时间戳一起封装成记录信息,格式如下:应用层记录结构为:{时间,Activity类名,状态}。

3.2.2活动层和操作层监控

对于Android手机来说,用户的所有操作都是建立在与Android屏幕交互基础上,即触摸屏事件。对于触摸屏事件(鼠标事件)有按下有:按下、弹起、移动、滑动、滚动。按下、弹起、移动(down、move、up)是简单的触摸屏事件,而双击、长按、滑动、滚动需要根据运动的轨迹来做识别的。在Android中有专门的类去识别,android.view.GestureDetector。

Android的触摸和手势事件的是需要继承touch接口,重写里面方法。在这些方法里,Android系统会上抛被触发的view对象和事件类型。因此,由AspectJ监听这些方法,从而获知是用户点击的view和执行的触屏事件,从而进一步得知用户执行的操作指令[5]。

根据用户的操作指令已经响应指令的代码,从而可以得到一个操作指令的信息。所以,监控操作层执行过程只需要记录用户的操作指令。记录结构如下:{时间,view对象,触屏事件类型}。

3.2.3操作流

操作流,即用户在使用应用软件过程中按照时间先后顺序排列的操作指令。

通过对应用层的监控,可以形成一个粗密度的操作流,即Activity之间相互切换的过程。用户在每一个Activity中会有更加细密度的操作。用户细密度的操作流则被活动层和操作层的监控记录下来。如图3所示。

图3 操作流分类

通常,不同的Activity中承担的是不同的业务,所完成的功能是不一样的。因此,在应用层的操作流反映出的是程序整体业务的变化。活动层的操作流更关注用户具体的行为,反映出的是用户具体操作指令。

操作流最终会被记录在两个ArrayList中。应用层信息记录在应用层的ArrayList,活动层的操作流记录在活动层的ArrayList中。

3.3异常发生时处理

当一个未捕获的运行时异常发生时,会导致发生异常的活动层崩溃。利用前文活动层崩溃对Application层不影响的特点和java中UncaughtExceptionHandler类的性质,在应用层Application中生成一个ExceptionHandler对象,它是UncaughtExceptionHandler的子类。当异常发生时可以接管程序,此时可以将当异常发生时的信息,保存下来,避免信息丢失。

此时通过捕获异常对象,生成BEM信息。同时,ExceptionHandler还会查询该软件名和软件版本号。

ExceptionHandler再把内存中ArrayList和HashMap中数据一并取出,在手机本地生成一个异常日志文件,将这些信息写入文件内,文件名用软件名和软件版本号命名。最后退出程序。

当程序重新启动时,检测该文件是否存在。如果存在,则将该文件上传至服务器后,再把文件删除。

4异常实例分析

在传统的Android日志信息中,当发生异常时,日志提供的是以下信息:

12-10 15:32:40.734: FATAL EXCEPTION: main

12-10 15:32:40.734: Process: com.example.androidtestproject, PID: 3816

12-10 15:32:40.734: java.lang.NullPointerException

12-10 15:32:40.734: at com.example.androidtestproject.CounterView.onDraw(CounterView.java:36)

12-10 15:32:40.734: at android.view.View.draw(View.java:14613)

12-10 15:32:40.734: at android.view.View.getDisplayList(View.java:13510)

该信息只指出了异常类型和出错代码行。由于Android自身机制原因,剩下的错误信息都来自Android自身框架代码。在该例中,出错的类CounterView是一个自定义View,它在程序中有多个实例对象。由于其中某个实例对象传入了Null,导致了CounterView类内onDraw方法发生了空指针异常。但从以上信息来看是无法得知具体哪个对象导致的该异常。

通过采用本文中的解决方案,根据应用层操作流记录,可以查看到如下信息:

{ 01-08 00:44:01.214,MainActivity,onCreate方法已执行}

{ 01-08 00:44:01.214,MainActivity,onStart方法已执行}

{ 01-08 00:44:09.527,otherActivity,onCreate方法已执行}

{ 01-08 00:44:09.527, otherActivity, onStart方法已执行}

该记录并没有显示全部的状态记录,这是由于此程序中的Activity并没有重写全部的状态方法导致的。但因为已经执行了otherActivity中的onCreate方法,故不影响判断出程序是进入了otherActivity后发生的异常。由于onStart方法已经执行,因此可以判断otherActivity已经进入运行阶段。

所以取出otherActivity中活动层操作流记录:

{ 01-08 00:44:30.365,Button,onClick }

{ 01-08 00:44:37.125,CounterView,onClick}

这说明是CounterView类的对象触发点击事件的操作导致的异常。根据代码得知,MainActivity并没有给otherActivity传值,所以只需要重新启动otherActivity,点击CounterView类的对象,异常重现。因此可以得出结论:导致异常发生的原因是在操作层中。检查CounterView类的对象中onClick方法,排查异常。如果点击CounterView类的对象并没有发生异常,而点击Button对象后再点击CounterView类的对象,异常重现,则可以判断异常的原因是在活动层中,即Button对象的行为对CounterView类的对象产生了干扰。

本方法在多个Android软件中进行了实验。通过收集应用层、活动层、操作层的信息,获知了发生异常前、用户的操作行为,从而可以在脱离用户使用环境还原用户使用过程,使异常重现。证明了该方法的可行性。

5结论

本文在分析异常复杂性原因基础上,通过对系统模块进行分层抽象,将系统全局状态记录简化为对具体活动层的记录。通过记录还原每一层的必要信息,方便开发人员可以脱离异常发生的环境,在开发环境中重现异常。

该解决方案可以适用于传统Android软件的异常还原。里面主要是利用Android系统运行软件的特点,收集信息。因此只要Android应用框架行为不发生改变,本方案可以给大部分Android应用程序使用。

然而,在使用过程中也发现一些不足。由于在Android平台软件存在多样性,当遇到需要频繁操作的应用时,会触发大量触摸事件,因此会记录大量无用信息,反而对重现异常产生干扰。其次,本方案中未对程序调用的传感器进行进行监控,对用户使用传感器的行为信息缺乏。这可能会导致部分操作信息丢失。服务器端获取的数据还过于原始,需要自己翻译成操作流。在未来的工作中需要在服务器端实现异常统计和异常信息翻译的工作。

本文提供的模型和方案不同于传统开发中在异常发生现场定位和解决异常。而是利用Android系统的特性,为开发维护人员提供一种分层模型的分析方法和一种动态监控的方法,提高了异常重现与分析的效率,从而加快开发速度,提高系统的可靠性和可维护性,提升用户满意度。

参 考 文 献

[1] 彭智俊,张源,杨珉.用静态信息流分析检测Android应用中的日志信息[J].小型微型计算机系统,2013(6):1276-1281.PENG Zhijun, ZHANG Yuan, YANG Min. Static flow analysis detection of Android applications log information[J]. Small and Micro Computer System,2013(6):1276-1281.

[2] Ji C, Chen Z, Xu B, et al. A new mutation analysis method for testing Java exception handling[C]//Computer Software and Applications Conference, 2009. COMPSAC’09. 33rd Annual IEEE International. IEEE,2009(2):556-561.

[3] 严悍,梁君.多角色协同Web系统中的异常语境分析与重现[D].南京:南京理工大学,2014.YAN Han, LIANG Jun. Exception Context Reproducing and Analyse for Multi-Role Collaborative Web System[D]. Nanjing: Nanjing University of Science and Technology,2014.

[4] Google, Android APIs官方文档[EB/OL]. http://www.android-doc.com/reference/android/app/Activity.html,2015-12-03.

Google, Android APIs Official documentation[EB/OL]. http://www.android-doc.com/reference/android/app/Activity.html,2015-12-03.

[5] 余炜.Android触摸事件分发机制[EB/OL]. http://hunankeda110.iteye.com/blog/1944311,2013-09-20.

YU Wei. Android touch event distribution mechanisms[EB/OL]. http://hunankeda110.iteye.com/blog/1944311,2013-09-20.

[6] 阿拉神农.深入理解Android之AOP[EB/OL]. http://blog.csdn.net/innost/article/details/49387395,2015-12-14.

Aladingshennong. In-depth understanding of Android AOP[EB/OL]. http://blog.csdn.net/innost/article/details/49387395,2015-12-14.

[7] Fernando Cejas. Aspect Oriented Programming in Android[EB/OL]. http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/, 2014-08-03.

[8] Rogers R, Lombardo J, Mednieks Z, et al. Android application development: Programming with the Google SDK[M]. O’Reilly Media, Inc.,2009.

[9] Isohara T, Takemori K, Kubota A. Kernel-based behavior analysis for android malware detection[C]//Computational Intelligence and Security (CIS), 2011 Seventh International Conference on. IEEE,2011:1011-1015.

[10] Ji C, Chen Z, Xu B, et al. A new mutation analysis method for testing Java exception handling[C]//Computer Software and Applications Conference, 2009. COMPSAC’09. 33rd Annual IEEE International. IEEE,2009(2):556-561.

收稿日期:2016年1月10日,修回日期:2016年3月1日

作者简介:姜雨蒙,男,硕士研究生,研究方向:Android应用,敏感语境分析。严悍,男,博士研究生,研究方向:敏捷开发方法学。

中图分类号TN959.53

DOI:10.3969/j.issn.1672-9722.2016.07.028

Android Runtime Exception Reproduction Method

JIANG YumengYAN Han

(School of Computer Science and Engineering, Nanjing University of Science and Technology, Nanjing210094)

AbstractAbnormal in the Android system, the development and maintenance personnel to rely on the existing abnormal records is difficult to effectively analyze the causes of abnormal, increase the difficulty of system maintenance, software quality is difficult to guarantee. Due to the characteristics of the Android platform, the information provided by the exception handling mechanism of Java is insufficient. Aiming at the problem of traditional log information clutter, a solution based on Android system is presented, which provides a feasible and universal solution for the problem.

Key WordsAndroid, runtime execption handling, exception reoccurs