Android崩溃处理

我们写程序的时候都希望能写出一个没有任何Bug的程序,期望在任何情况下都不会发生程序崩溃。但没有一个程序员能保证自己写的程序绝对不会出现异常崩溃。特别是当你用户数达到一定数量级后,你也更容易发现应用不同情况下的崩溃。

对于还没发布的应用程序,我们可以通过测试、分析Log的方法来收集崩溃信息。但对已经发布的程序,我们不可能让用户去查看崩溃信息然后再反馈给开发者。所以,设计一个对于小白用户都可以轻松实现反馈的应用就显得很重要了。我这里结合我自己写的一个Demo,来分析从崩溃开始到崩溃信息反馈到我们服务器,我们程序都需要做什么。

当我们的程序因未捕获的异常而突然终止时,系统会调用处理程序的接口 UncaughtExceptionHandler 。如果我们想处理未被程序正常捕获的异常,只需实现这个接口里的uncaughtException方法,uncaughtException方法回传了Thread 和 Throwable两个参数。通过这两个参数,我们来对异常进行我们需要的处理。

综上,我对异常处理方式的思路是这样的:

1.我们需要首先收集产生崩溃的手机信息,因为Android的样机种类繁多,很可能某些特定机型下会产生莫名的bug。2.将手机的信息和崩溃信息写入文件系统中。这样方便后续处理。

3.崩溃的应用需要可以自动重启。重启的页面设置成反馈页面,询问 用户是否需要上传崩溃报告。

4.用户同意后,即将2中写入的崩溃信息文件发送到自己的服务器。


通过上面的步骤,我们就可以写出大概的伪代码:

[Java] 纯文本查看 复制代码

?

1
2
3
4
5
handleException() {
   collectDeviceInfo(context); //手机手机信息
   writeCrashInfoToFile(ex); //写入崩溃文件
   restart(); //应用重启
  }

最后,在重启页面通过AsyncTask将崩溃信息上传服务器。

有了以上思路,我们一步一步的写出每个伪函数的具体代码。

1.收集手机的信息:

[Java] 纯文本查看 复制代码

?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
   *
   * @param ctx
   * 手机设备相关信息
   */
  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);
     infos.put( "crashTime" , formatter.format( new Date()));
    }
   } 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);
    }
   }
  }
2.崩溃和手机信息写入文件:

[Java] 纯文本查看 复制代码

?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
   *
   * @param ex
   * 将崩溃写入文件系统
   */
  private void writeCrashInfoToFile(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);
   //这里把刚才异常堆栈信息写入SD卡的Log日志里面
   if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
   {
    String sdcardPath = Environment.getExternalStorageDirectory().getPath();
    String filePath = sdcardPath + "/cym/crash/" ;
    localFileUrl = writeLog(sb.toString(), filePath);
   }
  }
  /**
   *
   * @param log
   * @param name
   * [url=home.php?mod=space&uid=309376]@return[/url] 返回写入的文件路径
   * 写入Log信息的方法,写入到SD卡里面
   */
  private String writeLog(String log, String name)
  {
   CharSequence timestamp = new Date().toString().replace( " " , "" );
   timestamp = "crash" ;
   String filename = name + timestamp + ".log" ;
   File file = new File(filename);
   if (!file.getParentFile().exists()){
    file.getParentFile().mkdirs();
   }
   try
   {
    Log.d( "TAG" , "写入到SD卡里面" );
    //    FileOutputStream stream = new FileOutputStream(new File(filename));
    //    OutputStreamWriter output = new OutputStreamWriter(stream);
    file.createNewFile();
    FileWriter fw= new FileWriter(file, true );
    BufferedWriter bw = new BufferedWriter(fw);
    //写入相关Log到文件
    bw.write(log);
    bw.newLine();
    bw.close();
    fw.close();
    return filename;
   }
   catch (IOException e)
   {
    Log.e(TAG, "an error occured while writing file..." , e);
    e.printStackTrace();
    return null ;
   }
  }
3.重启应用:

[Java] 纯文本查看 复制代码

?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
注:我尝试过好多种应用重启的方法,最终选择采用PendingIntent的方式。
private void restart(){
    try {
      Thread.sleep( 2000 );
      } catch (InterruptedException e){
      Log.e(TAG, "error : " , e);
      }
      Intent intent = new Intent(context.getApplicationContext(), SendCrashActivity. class );
      PendingIntent restartIntent = PendingIntent.getActivity(
        context.getApplicationContext(), 0 , intent,
          Intent.FLAG_ACTIVITY_NEW_TASK);
      //退出程序
      AlarmManager mgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
      mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000 ,
          restartIntent); // 1秒钟后重启应用
  }
4.上传崩溃

应用重启后来到的是SendCrashActivity界面,在这里我设置了一个简单的按钮,点击后即可上传崩溃信息。代码比较多,这里列一个比较有用的上传方法吧:

[Java] 纯文本查看 复制代码

?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public static String uploadFile(File file,String requestUrl){
   String result = null ;
   String BOUNDARY = UUID.randomUUID().toString(); //边界标识 随机生成
   String PREFIX = "--" ;
   String LINE_END = "\r\n" ;
   String CONTENT_TYPE = "multipart/form-data" ; //内容类型
   try {
    URL url = new URL(requestUrl);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setReadTimeout(TIME_OUT);
    conn.setConnectTimeout(TIME_OUT);
    conn.setDoInput( true ); //允许输入流
    conn.setDoOutput( true ); //允许输出流
    conn.setUseCaches( false ); //不允许使用缓存
    conn.setRequestMethod( "POST" ); //请求方式
    conn.setRequestProperty( "Charset" , CHARSET); //设置编码
    conn.setRequestProperty( "connection" , "keep-alive" );
    conn.setRequestProperty( "Content-Type" , CONTENT_TYPE + ";boundary=" + BOUNDARY);
 
 
    if (file!= null )
    {
     /**
      * 当文件不为空,把文件包装并且上传
      */
     DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
     StringBuffer sb = new StringBuffer();
     sb.append(PREFIX);
     sb.append(BOUNDARY);
     sb.append(LINE_END);
     /**
      * 这里重点注意:
      * name里面的值为服务器端需要key 只有这个key 才可以得到对应的文件
      * filename是文件的名字,包含后缀名的 比如:abc.png
      */
 
     sb.append( "Content-Disposition: form-data; name=\"uploadcrash\"; filename=\"" +file.getName()+ "\"" +LINE_END);
     sb.append( "Content-Type: application/octet-stream; charset=" +CHARSET+LINE_END);
     sb.append(LINE_END);
     dos.write(sb.toString().getBytes());
     InputStream is = new FileInputStream(file);
     byte [] bytes = new byte [ 1024 ];
     int len = 0 ;
     while ((len=is.read(bytes))!=- 1 )
     {
      dos.write(bytes, 0 , len);
     }
     is.close();
     dos.write(LINE_END.getBytes());
     byte [] end_data = (PREFIX+BOUNDARY+PREFIX+LINE_END).getBytes();
     dos.write(end_data);
     dos.flush();
     /**
      * 获取响应码 200=成功
      * 当响应成功,获取响应的流
      */
      int res = conn.getResponseCode();
     Log.e(TAG, "response code:" +res);
     // if(res==200)
     // {
     Log.e(TAG, "request success" );
     InputStream input = conn.getInputStream();
     StringBuffer sb1= new StringBuffer();
     int ss ;
     while ((ss=input.read())!=- 1 )
     {
      sb1.append(( char )ss);
     }
     result = sb1.toString();
     Log.e(TAG, "result : " + result);
     // }
    // else{
     // Log.e(TAG, "request error");
     // }
    }
   } catch (MalformedURLException e) {
    e.printStackTrace();
   } catch (IOException e) {
    e.printStackTrace();
   }
 
   return result;
  }

整个流程基本走完,我们来看一下最终效果。(MainActivity点击按钮后执行了一个2/0的操作,所以崩溃)

03074907_L1w4.jpg

我将崩溃上传到了我的sae服务器的storage里。下图中红色圈起来的文件即是我们上传的崩溃文件。

03074909_YQrb.jpg

我把这个文件下载下来,内容如下:

[AppleScript] 纯文本查看 复制代码

?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
TIME = 1383016889000
FINGERPRINT = generic / sdk / generic : 4.4 / KRT 16 L / 892118 : eng / test - keys
HARDWARE = goldfish
UNKNOWN = unknown
RADIO = unknown
BOARD = unknown
versionCode = 1
PRODUCT = sdk
versionName = 1.0
DISPLAY = sdk - eng 4.4 KRT 16 L 892118 test - keys
USER = android - build
HOST = vpak 27. mtv.corp.google.com
DEVICE = generic
TAGS = test - keys
MODEL = sdk
BOOTLOADER = unknown
crashTime = 2014 -09 -24 05 : 39 : 21
CPU_ABI = armeabi - v 7 a
CPU_ABI 2 = armeabi
IS_DEBUGGABLE = true
ID = KRT 16 L
SERIAL = unknown
MANUFACTURER = unknown
BRAND = generic
TYPE = eng
java.lang.IllegalStateException : Could not execute method of the activity
  at android. view .View$ 1. onClick ( View.java : 3814 )
  at android. view .View.performClick ( View.java : 4424 )
  at android. view .View$PerformClick. run ( View.java : 18383 )
  at android.os.Handler.handleCallback ( Handler.java : 733 )
  at android.os.Handler.dispatchMessage ( Handler.java : 95 )
  at android.os.Looper.loop ( Looper.java : 137 )
  at android.app.ActivityThread. main ( ActivityThread.java : 4998 )
  at java.lang.reflect.Method.invokeNative ( Native Method )
  at java.lang.reflect.Method.invoke ( Method.java : 515 )
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller. run ( ZygoteInit.java : 777 )
  at com.android.internal.os.ZygoteInit. main ( ZygoteInit.java : 593 )
  at dalvik.system.NativeStart. main ( Native Method )
Caused by : java.lang.reflect.InvocationTargetException
  at java.lang.reflect.Method.invokeNative ( Native Method )
  at java.lang.reflect.Method.invoke ( Method.java : 515 )
  at android. view .View$ 1. onClick ( View.java : 3809 )
  ... 11 more
Caused by : java.lang.ArithmeticException : divide by zero
  at so.cym.crashhandlerdemo.MainActivity.generateAnr ( MainActivity.java : 20 )
  ... 14 more
java.lang.reflect.InvocationTargetException
  at java.lang.reflect.Method.invokeNative ( Native Method )
  at java.lang.reflect.Method.invoke ( Method.java : 515 )
  at android. view .View$ 1. onClick ( View.java : 3809 )
  at android. view .View.performClick ( View.java : 4424 )
  at android. view .View$PerformClick. run ( View.java : 18383 )
  at android.os.Handler.handleCallback ( Handler.java : 733 )
  at android.os.Handler.dispatchMessage ( Handler.java : 95 )
  at android.os.Looper.loop ( Looper.java : 137 )
  at android.app.ActivityThread. main ( ActivityThread.java : 4998 )
  at java.lang.reflect.Method.invokeNative ( Native Method )
  at java.lang.reflect.Method.invoke ( Method.java : 515 )
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller. run ( ZygoteInit.java : 777 )
  at com.android.internal.os.ZygoteInit. main ( ZygoteInit.java : 593 )
  at dalvik.system.NativeStart. main ( Native Method )
Caused by : java.lang.ArithmeticException : divide by zero
  at so.cym.crashhandlerdemo.MainActivity.generateAnr ( MainActivity.java : 20 )
  ... 14 more
java.lang.ArithmeticException : divide by zero
  at so.cym.crashhandlerdemo.MainActivity.generateAnr ( MainActivity.java : 20 )
  at java.lang.reflect.Method.invokeNative ( Native Method )
  at java.lang.reflect.Method.invoke ( Method.java : 515 )
  at android. view .View$ 1. onClick ( View.java : 3809 )
  at android. view .View.performClick ( View.java : 4424 )
  at android. view .View$PerformClick. run ( View.java : 18383 )
  at android.os.Handler.handleCallback ( Handler.java : 733 )
  at android.os.Handler.dispatchMessage ( Handler.java : 95 )
  at android.os.Looper.loop ( Looper.java : 137 )
  at android.app.ActivityThread. main ( ActivityThread.java : 4998 )
  at java.lang.reflect.Method.invokeNative ( Native Method )
  at java.lang.reflect.Method.invoke ( Method.java : 515 )
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller. run ( ZygoteInit.java : 777 )
  at com.android.internal.os.ZygoteInit. main ( ZygoteInit.java : 593 )
  at dalvik.system.NativeStart. main ( Native Method )
总结

通过上面的文件,我们就可以分析什么时候产生崩溃,什么机型下会产生崩溃。

Android里有一种崩溃(严格意义将不叫崩溃)是捕获不到的,那就是ANR,关于ANR的相关知识可以阅读我的另一篇博文 http://blog.saymagic.cn/2014/09/25/ANR%E5%AE%8C%E5%85%A8%E8%A7%A3%E6%9E%90.html

如果你对源码感兴趣,欢迎到此处进行star或者fork: https://gitcafe.com/saymagic/AndroidCrashHandler 。

原地址:http://blog.saymagic.cn/2014/09/25/Android%E5%B4%A9%E6%BA%83%E5%AE%8C%E5%85%A8%E8%A7%A3%E6%9E%90.html?utm_source=tuicool


举报 编辑

(1)


转载于:https://my.oschina.net/Gxhpro/blog/424121

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值