最近跟朋友讨论手机软件的时候,分析了几种常见的类型,当然都是测试工作经验的一种体现,记录下来,陆续会不断更新。 1. 网络异常 通常在网络异常的情况下,客户端发出的请求,没有在一定时间内得到恢复,但是一般都会有一个超时的概念,如果程序在没有处理
最近跟朋友讨论手机软件的时候,分析了几种常见的类型,当然都是测试工作经验的一种体现,记录下来,陆续会不断更新。
1. 网络异常
通常在网络异常的情况下,客户端发出的请求,没有在一定时间内得到恢复,但是一般都会有一个超时的概念,如果程序在没有处理好的情况下,超时之后无法处理程序的逻辑,则经常会出现Crash。这种问题在网络差的情况下,经常出现,比如浏览论坛的时候,正常网络下访问无问题,在网络极其差的情况下,经常性的崩溃就是属于这个问题。
所以测试的过程中,我会通过拔路由器的网线的方式来进行测试,提交一个接口请求之后,立即拔去路由器的线。这样数据无法正常返回到客户端,等待超时之后,看前端的处理方式。如果处理不好的情况下,就会出现崩溃发生。
2. 内存问题
通常在开发程序的时候,内存的泄露或者没有正常回收,造成程序随着操作越来越多,占用的内存越来越大,最终导致崩溃的发生。
测试的过程中,这类问题会比较麻烦,总的来说,一款内存小的手机在测试的过程中是必须的,我会选择一款256M内存,Android 2.3的机器来进行测试。
同时会使用Emmagee的小软件进行检测,当然有一个合理的测试用力也是必须的。根据测试用例来正常跑软件,测试结束之后得到一张关于内存使用的图标,慢慢进行分析,对照测试用力进行分析查看是否能发现内存泄露的操作,如果有可疑的操作就要对其进行重复性测试,还是使用Emmagee的软件,不断的检测一个点。知道确认内存泄露的功能模块。
高级的测试还会使用DDMS进行查看,原理基本相同,具体方法可以查看网上写的逻辑。
总的来说,内存泄露对于测试人员,特别是手动测试人员比较困难,但是不是没有方法来进行。
3. 接口返回值错误
通常会遇到接口返回值和预期返回值不相同的问题,如果App前端处理不太周全的情况下,会出现程序崩溃。
在遇到这样的问题的时候,一般会采用协调前台和后台之间的信息来处理。根据公司的经验,一般后台传输数据都需要自己的检测程序来查看具体的接口传输数据,有了合理的工具合理的分析平台才能处理的更好,在此感谢Don, Jason的努力,在能查看接口传输数据之后,确实对测试的工作产生了正面的影响。
4. 手机特定类型错误
因为安卓手机毕竟有着众多的品牌和类型,软件在运行的过程中难免会出现功能和某些测试机器,或者不同UI上出现崩溃的问题。
目前没有太好的方案来解决,一般会采用Testin自动化平台运行App,从测试中发现的问题进行判定是否出现的问题时固定可以重现的。
汇总的说,其实Umeng平台还是提供了良好的方式来处理这些崩溃问题,在友盟捕捉到的错误日志中分析,可以不断的提升产品质量。不是做广告,只是告诉大家明智的敏捷开发团队一定会采用这样轻量级的平台来提升品质。
菜鸟写东西还需要不断更新。。。
5. 渲染图片出现的问题
因为在Android系统在渲染图片的时候需要加载到内存中,所以App上的一些图如果过大,可以造成崩溃事件的发生。
在系统版本为2.3 一下的手机上容易出现,其实这也是与手机的性能相关的,在2.3以下的时候,通常手机的内存都比较小 256兆 和 512的内存上经常会出现类似的情况。
如何处理奔溃
下面我们介绍一下如何在程序崩溃的情况下收集相关的设备参数信息和具体的异常信息,并发送这些信息到服务器供开发者分析和调试程序。
我们先建立一个crash项目,项目结构如图:

在MainActivity.Java代码中,代码是这样写的:
-
package com.scott.crash;
-
-
import android.app.Activity;
-
import android.os.Bundle;
-
-
public class MainActivity extends Activity {
-
-
private String s;
-
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
System.out.println(s.equals("any string"));
-
}
-
}
我们在这里故意制造了一个潜在的运行期异常,当我们运行程序时就会出现以下界面:

遇到软件没有捕获的异常之后,系统会弹出这个默认的强制关闭对话框。
我们当然不希望用户看到这种现象,简直是对用户心灵上的打击,而且对我们的bug的修复也是毫无帮助的。我们需要的是软件有一个全局的异常捕获器,当出现一个我们没有发现的异常时,捕获这个异常,并且将异常信息记录下来,上传到服务器公开发这分析出现异常的具体原因。
接下来我们就来实现这一机制,不过首先我们还是来了解以下两个类:android.app.Application和java.lang.Thread.UncaughtExceptionHandler。
Application:用来管理应用程序的全局状态。在应用程序启动时Application会首先创建,然后才会根据情况(Intent)来启动相应的Activity和Service。本示例中将在自定义加强版的Application中注册未捕获异常处理器。
Thread.UncaughtExceptionHandler:线程未捕获异常处理器,用来处理未捕获异常。如果程序出现了未捕获异常,默认会弹出系统中强制关闭对话框。我们需要实现此接口,并注册为程序中默认未捕获异常处理。这样当未捕获异常发生时,就可以做一些个性化的异常处理操作。
大家刚才在项目的结构图中看到的CrashHandler.java实现了Thread.UncaughtExceptionHandler,使我们用来处理未捕获异常的主要成员,代码如下:
-
package com.scott.crash;
-
-
import java.io.File;
-
import java.io.FileOutputStream;
-
import java.io.PrintWriter;
-
import java.io.StringWriter;
-
import java.io.Writer;
-
import java.lang.Thread.UncaughtExceptionHandler;
-
import java.lang.reflect.Field;
-
import java.text.DateFormat;
-
import java.text.SimpleDateFormat;
-
import java.util.Date;
-
import java.util.HashMap;
-
import java.util.Map;
-
-
import android.content.Context;
-
import android.content.pm.PackageInfo;
-
import android.content.pm.PackageManager;
-
import android.content.pm.PackageManager.NameNotFoundException;
-
import android.os.Build;
-
import android.os.Environment;
-
import android.os.Looper;
-
import android.util.Log;
-
import android.widget.Toast;
-
-
-
-
-
-
-
-
public class CrashHandler implements UncaughtExceptionHandler {
-
-
public static final String TAG = "CrashHandler";
-
-
-
private Thread.UncaughtExceptionHandler mDefaultHandler;
-
-
private static CrashHandler INSTANCE = new CrashHandler();
-
-
private Context mContext;
-
-
private Map<String, String> infos = new HashMap<String, String>();
-
-
-
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
-
-
-
private CrashHandler() {
-
}
-
-
-
public static CrashHandler getInstance() {
-
return INSTANCE;
-
}
-
-
-
-
-
-
-
public void init(Context context) {
-
mContext = context;
-
-
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
-
-
Thread.setDefaultUncaughtExceptionHandler(this);
-
}
-
-
-
-
-
@Override
-
public void uncaughtException(Thread thread, Throwable ex) {
-
if (!handleException(ex) && mDefaultHandler != null) {
-
-
mDefaultHandler.uncaughtException(thread, ex);
-
} else {
-
try {
-
Thread.sleep(3000);
-
} catch (InterruptedException e) {
-
Log.e(TAG, "error : ", e);
-
}
-
-
android.os.Process.killProcess(android.os.Process.myPid());
-
System.exit(1);
-
}
-
}
-
-
-
-
-
-
-
-
private boolean handleException(Throwable ex) {
-
if (ex == null) {
-
return false;
-
}
-
-
new Thread() {
-
@Override
-
public void run() {
-
Looper.prepare();
-
Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();
-
Looper.loop();
-
}
-
}.start();
-
-
collectDeviceInfo(mContext);
-
-
saveCrashInfo2File(ex);
-
return true;
-
}
-
-
-
-
-
-
public void collectDeviceInfo(Context ctx) {
-
try {
-
PackageManager pm = ctx.getPackageManager();
-
PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
-
if (pi != null) {
-
String versionName = pi.versionName == null ? "null" : pi.versionName;
-
String versionCode = pi.versionCode + "";
-
infos.put("versionName", versionName);
-
infos.put("versionCode", versionCode);
-
}
-
} catch (NameNotFoundException e) {
-
Log.e(TAG, "an error occured when collect package info", e);
-
}
-
Field[] fields = Build.class.getDeclaredFields();
-
for (Field field : fields) {
-
try {
-
field.setAccessible(true);
-
infos.put(field.getName(), field.get(null).toString());
-
Log.d(TAG, field.getName() + " : " + field.get(null));
-
} catch (Exception e) {
-
Log.e(TAG, "an error occured when collect crash info", e);
-
}
-
}
-
}
-
-
-
-
-
-
-
-
private String saveCrashInfo2File(Throwable ex) {
-
-
StringBuffer sb = new StringBuffer();
-
for (Map.Entry<String, String> entry : infos.entrySet()) {
-
String key = entry.getKey();
-
String value = entry.getValue();
-
sb.append(key + "=" + value + "\n");
-
}
-
-
Writer writer = new StringWriter();
-
PrintWriter printWriter = new PrintWriter(writer);
-
ex.printStackTrace(printWriter);
-
Throwable cause = ex.getCause();
-
while (cause != null) {
-
cause.printStackTrace(printWriter);
-
cause = cause.getCause();
-
}
-
printWriter.close();
-
String result = writer.toString();
-
sb.append(result);
-
try {
-
long timestamp = System.currentTimeMillis();
-
String time = formatter.format(new Date());
-
String fileName = "crash-" + time + "-" + timestamp + ".log";
-
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
-
String path = "/sdcard/crash/";
-
File dir = new File(path);
-
if (!dir.exists()) {
-
dir.mkdirs();
-
}
-
FileOutputStream fos = new FileOutputStream(path + fileName);
-
fos.write(sb.toString().getBytes());
-
fos.close();
-
}
-
return fileName;
-
} catch (Exception e) {
-
Log.e(TAG, "an error occured while writing file...", e);
-
}
-
return null;
-
}
-
}
在收集异常信息时,朋友们也可以使用Properties,因为Properties有一个很便捷的方法properties.store(OutputStream out, String comments),用来将Properties实例中的键值对外输到输出流中,但是在使用的过程中发现生成的文件中异常信息打印在同一行,看起来极为费劲,所以换成Map来存放这些信息,然后生成文件时稍加了些操作。
完成这个CrashHandler后,我们需要在一个Application环境中让其运行,为此,我们继承android.app.Application,添加自己的代码,CrashApplication.java代码如下:
-
package com.scott.crash;
-
-
import android.app.Application;
-
-
public class CrashApplication extends Application {
-
@Override
-
public void onCreate() {
-
super.onCreate();
-
CrashHandler crashHandler = CrashHandler.getInstance();
-
crashHandler.init(getApplicationContext());
-
}
-
}
最后,为了让我们的CrashApplication取代android.app.Application的地位,在我们的代码中生效,我们需要修改AndroidManifest.xml:
-
<application android:name=".CrashApplication" ...>
-
</application>
因为我们上面的CrashHandler中,遇到异常后要保存设备参数和具体异常信息到SDCARD,所以我们需要在AndroidManifest.xml中加入读写SDCARD权限:
-
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
搞定了上边的步骤之后,我们来运行一下这个项目:

看以看到,并不会有强制关闭的对话框出现了,取而代之的是我们比较有好的提示信息。
然后看一下SDCARD生成的文件:

用文本编辑器打开日志文件,看一段日志信息:
-
CPU_ABI=armeabi
-
CPU_ABI2=unknown
-
ID=FRF91
-
MANUFACTURER=unknown
-
BRAND=generic
-
TYPE=eng
-
......
-
Caused by: java.lang.NullPointerException
-
at com.scott.crash.MainActivity.onCreate(MainActivity.java:13)
-
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
-
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
-
... 11 more
这些信息对于开发者来说帮助极大,所以我们需要将此日志文件上传到服务器,有关文件上传的技术,请参照Android中使用HTTP服务相关介绍。
不过在使用HTTP服务之前,需要确定网络畅通,我们可以使用下面的方式判断网络是否可用:
-
-
-
-
-
-
-
public static boolean isNetworkAvailable(Context context) {
-
ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-
NetworkInfo[] info = mgr.getAllNetworkInfo();
-
if (info != null) {
-
for (int i = 0; i < info.length; i++) {
-
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
-
return true;
-
}
-
}
-
}
-
return false;
-
}
android app崩溃的常见类型和处理
转载https://www.codesocang.com/appboke/34804.html