Android APP应用开发中异常处理

APP开发出现异常在所难名,甚至会导致应用程序崩溃。如果在debug模式下开发的时候,是可以通过查看logcat日志来查看异常消息,从而进行处理。但是,如果我们在发布版本之后,用户在使用的时候crash掉了,就无法查看异常信息,也就很难找出bug来解决问题。

还好在java线程类中,有一个针对上述问题的解决办法:在线程中捕捉未处理的异常。因为crash时,抛出的异常就是因为没有在app中catch处理,就会抛给系统,如果我们在这个时候对这个能够对这个异常进行处理,就最好不过了,这样就能打印异常信息,就能发布给服务器,供开发人员查看。

一,写一个CrashHanler类

1
2
3
4
5
6
7
8
9
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
package  com.raise.wind.utils;
import  android.os.Environment;
import  java.io.File;
import  java.io.FileOutputStream;
import  java.io.IOException;
/**
  * Created by yu on 2015/7/15.
  */
public  class  CrashHandler  implements  Thread.UncaughtExceptionHandler {
     public  static  CrashHandler instance;
     private  CrashHandler() {
     }
     public  static  CrashHandler get_instance() {
         if  (instance ==  null )
             new  CrashHandler();
         return  instance;
     }
     public  void  init() {
         Thread.setDefaultUncaughtExceptionHandler( this );
     }
     @Override
     public  void  uncaughtException(Thread thread, Throwable ex) {
         saveFile(ex.getMessage(),  "crash.txt" );
         //退出程序
         //这里由于是我们自己处理的异常,必须手动退出程序,不然系统出一只处于crash等待状态
         android.os.Process.killProcess(android.os.Process.myPid());
         System.exit( 1 );
     }
     public  static  void  saveFile(String data, String file_name) {
         File sdPath =  new  File(Environment.getExternalStorageDirectory().getAbsolutePath()
                 + File.separator +  "aacrash"  + File.separator +  "cache" );
         if  (!sdPath.exists()) {
             sdPath.mkdirs();
         }
         File file =  new  File(sdPath, file_name);
         FileOutputStream fos =  null ;
         try  {
             fos =  new  FileOutputStream(file);
             fos.write(data.getBytes( "UTF-8" ));
         catch  (Exception e) {
             e.printStackTrace();
         finally  {
             if  (fos !=  null )
                 try  {
                     fos.close();
                 catch  (IOException e) {
                     e.printStackTrace();
                 }
         }
     }
}


二,在Application中声明

记得在Androidanifest.xml中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
package  com.raise.wind.app;
import  android.app.Application;
import  com.raise.wind.utils.CrashHandler;
/**
  * Created by yu on 2015/7/15.
  */
public  class  APP  extends  Application {
     @Override
     public  void  onCreate() {
         super .onCreate();
         CrashHandler.get_instance().init();
     }
}


三,测试

这样就声明了我们线程中出现的为捕捉异常交给CrashHandler类来处理。
现在我们写一个空指针异常来测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
package  com.raise.wind.crashproject;
import  android.os.Bundle;
import  android.support.v7.app.ActionBarActivity;
import  android.widget.TextView;
public  class  MainActivity  extends  ActionBarActivity {
     TextView textView;
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         textView.setText( "text" );
     }
}


打开app,发现程序立即crash,打开文件管理能找到在程序中保存的文件,里面有异常消息

Unable to start activity ComponentInfo{com.raise.wind.crashproject/com.raise.wind.crashproject.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference

当然,这只是异常消息的开头的提示,如果要想全部打印出来,使用下面代码:

1
2
3
4
5
6
7
8
9
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
Could not execute method of the activity
android.view.View$ 1 .onClick(View.java: 4010 )
android.view.View.performClick(View.java: 4759 )
android.view.View$PerformClick.run(View.java: 19770 )
android.os.Handler.handleCallback(Handler.java: 739 )
android.os.Handler.dispatchMessage(Handler.java: 95 )
android.os.Looper.loop(Looper.java: 135 )
android.app.ActivityThread.main(ActivityThread.java: 5235 )
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java: 372 )
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java: 906 )
com.android.internal.os.ZygoteInit.main(ZygoteInit.java: 701 )
Caused by  null
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java: 372 )
android.view.View$ 1 .onClick(View.java: 4005 )
android.view.View.performClick(View.java: 4759 )
android.view.View$PerformClick.run(View.java: 19770 )
android.os.Handler.handleCallback(Handler.java: 739 )
android.os.Handler.dispatchMessage(Handler.java: 95 )
android.os.Looper.loop(Looper.java: 135 )
android.app.ActivityThread.main(ActivityThread.java: 5235 )
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java: 372 )
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java: 906 )
com.android.internal.os.ZygoteInit.main(ZygoteInit.java: 701 )
Caused by Attempt to invoke virtual method  'void android.widget.TextView.setText(java.lang.CharSequence)'  on a  null  object reference
com.raise.wind.crashproject.MainActivity.click_1(MainActivity.java: 21 )
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java: 372 )
android.view.View$ 1 .onClick(View.java: 4005 )
android.view.View.performClick(View.java: 4759 )
android.view.View$PerformClick.run(View.java: 19770 )
android.os.Handler.handleCallback(Handler.java: 739 )
android.os.Handler.dispatchMessage(Handler.java: 95 )
android.os.Looper.loop(Looper.java: 135 )
android.app.ActivityThread.main(ActivityThread.java: 5235 )
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java: 372 )
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java: 906 )
com.android.internal.os.ZygoteInit.main(ZygoteInit.java: 701 )


这个类一般结合log4j来记录日志,并且在发生crash时,将log文件发送到服务器。
这样程序就可以查看用户手机端的crash消息了,方便我们处理在debug模式开发时未发现的异常。


Android APP级异常捕获实现方式 

描述:App级异常捕获,并记录下CrashLog到文件。

以下,代码。

在Application的,onCreate中,初始化自定义的CrashHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import  android.app.Application;
import  com.tjd.appexceptioncatch.exception.CrashHandler;
public  class  MyApplication  extends  Application {
     private  static  MyApplication instance;
     @Override
     public  void  onCreate() {
         super .onCreate();
         CrashHandler.getInstance().init(getApplicationContext());
     }
     public  static  MyApplication getInstance() {
         if  (instance ==  null ) {
             instance =  new  MyApplication();
         }
         return  instance;
     }
}


自定义CrashHandler如下

1
2
3
4
5
6
7
8
9
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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;
import  com.tjd.appexceptioncatch.application.MyApplication;
/**
  * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
  * 需要在Application中注册,为了要在程序启动器就监控整个程序。
  */
public  class  CrashHandler  implements  UncaughtExceptionHandler {
     public  static  final  String TAG =  "CrashHandler" ;
     //系统默认的UncaughtException处理类
     private  Thread.UncaughtExceptionHandler mDefaultHandler;
     //CrashHandler实例
     private  static  CrashHandler instance;
     //程序的Context对象
     private  Context mContext;
     //用来存储设备信息和异常信息
     private  Map infos =  new  HashMap();
     //用于格式化日期,作为日志文件名的一部分
     private  DateFormat formatter =  new  SimpleDateFormat( "yyyy-MM-dd-HH-mm-ss" );
     /** 保证只有一个CrashHandler实例 */
     private  CrashHandler() {
     }
     /** 获取CrashHandler实例 ,单例模式 */
     public  static  CrashHandler getInstance() {
         if  (instance ==  null )
             instance =  new  CrashHandler();
         return  instance;
     }
     /**
      * 初始化
      */
     public  void  init(Context context) {
         mContext = context;
         //获取系统默认的UncaughtException处理器
         mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
         //设置该CrashHandler为程序的默认处理器    
         Thread.setDefaultUncaughtExceptionHandler( this );
     }
     /**
      * 当UncaughtException发生时会转入该函数来处理
      */
     @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 );
         }
     }
     /**
      * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
      * @param ex
      * @return true:如果处理了该异常信息;否则返回false.
      */
     private  boolean  handleException(Throwable ex) {
         if  (ex ==  null ) {
             return  false ;
         }
         //收集设备参数信息
         collectDeviceInfo(mContext);
         //使用Toast来显示异常信息
         new  Thread() {
             @Override
             public  void  run() {
                 Looper.prepare();
                 Toast.makeText(mContext,  "很抱歉,程序出现异常,即将退出." , Toast.LENGTH_SHORT).show();
                 Looper.loop();
             }
         }.start();
         //保存日志文件     
         saveCatchInfo2File(ex);
         return  true ;
     }
     /**
      * 收集设备参数信息
      * @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);
             }
         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 getFilePath() {
         String file_dir =  "" ;
         // SD卡是否存在
         boolean  isSDCardExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
         // Environment.getExternalStorageDirectory()相当于File file=new File("/sdcard")
         boolean  isRootDirExist = Environment.getExternalStorageDirectory().exists();
         if  (isSDCardExist && isRootDirExist) {
             file_dir = Environment.getExternalStorageDirectory().getAbsolutePath() +  "/crashlog/" ;
         else  {
             // MyApplication.getInstance().getFilesDir()返回的路劲为/data/data/PACKAGE_NAME/files,其中的包就是我们建立的主Activity所在的包
             file_dir = MyApplication.getInstance().getFilesDir().getAbsolutePath() +  "/crashlog/" ;
         }
         return  file_dir;
     }
     /**
      * 保存错误信息到文件中
      * @param ex
      * @return 返回文件名称,便于将文件传送到服务器
      */
     private  String saveCatchInfo2File(Throwable ex) {
         StringBuffer sb =  new  StringBuffer();
         for  (Map.Entry 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" ;
             String file_dir = getFilePath();
             //            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
             File dir =  new  File(file_dir);
             if  (!dir.exists()) {
                 dir.mkdirs();
             }
             File file =  new  File(file_dir + fileName);
             if  (!file.exists()) {
                 file.createNewFile();
             }
             FileOutputStream fos =  new  FileOutputStream(file);
             fos.write(sb.toString().getBytes());
             //发送给开发人员
             sendCrashLog2PM(file_dir + fileName);
             fos.close();
             //            }
             return  fileName;
         catch  (Exception e) {
             Log.e(TAG,  "an error occured while writing file..." , e);
         }
         return  null ;
     }
     /**
      * 将捕获的导致崩溃的错误信息发送给开发人员
      * 目前只将log日志保存在sdcard 和输出到LogCat中,并未发送给后台。
      */
     private  void  sendCrashLog2PM(String fileName) {
         //        if (!new File(fileName).exists()) {
         //            Toast.makeText(mContext, "日志文件不存在!", Toast.LENGTH_SHORT).show();
         //            return;
         //        }
         //        FileInputStream fis = null;
         //        BufferedReader reader = null;
         //        String s = null;
         //        try {
         //            fis = new FileInputStream(fileName);
         //            reader = new BufferedReader(new InputStreamReader(fis, "GBK"));
         //            while (true) {
         //                s = reader.readLine();
         //                if (s == null)
         //                    break;
         //                //由于目前尚未确定以何种方式发送,所以先打出log日志。
         //                Log.i("info", s.toString());
         //            }
         //        } catch (FileNotFoundException e) {
         //            e.printStackTrace();
         //        } catch (IOException e) {
         //            e.printStackTrace();
         //        } finally { // 关闭流
         //            try {
         //                reader.close();
         //                fis.close();
         //            } catch (IOException e) {
         //                e.printStackTrace();
         //            }
         //        }
     }
}


在MainActivity中触发异常

1
2
3
4
5
6
7
8
9
10
11

你可能感兴趣的:(Android)