一、Android Crash说明
-
程序因未捕获的异常而突然终止, 系统会调用处理程序的接口UncaughtExceptionHandler;
-
处理未被程序正常捕获的异常,只需实现这个接口里的UncaughtExceptionHandler方法,UncaughtExceptionHandler方法回传了 Thread 和 Throwable 两个参数。
二、实现思路
-
首先收集产生崩溃的手机信息,因为Android的样机种类繁多,很可能某些特定机型下会产生莫名的bug;
-
将手机的信息和崩溃信息写入文件系统中。这样方便后续处理;
-
崩溃的应用需要可以自动重启。重启的页面设置成反馈页面,询问 用户是否需要上传崩溃报告;
-
用户同意后,即将写入的崩溃信息文件发送到自己的服务器。
三、代码展示
CrashApplication.java
import android.app.Application; import android.os.Handler; import android.util.Log; public class CrashApplication extends Application{ /** TAG */ public static final String TAG = "CrashApplication"; @Override public void onCreate() { super.onCreate(); CrashHandler.getInstance().init(this); Log.v(TAG, "application created"); } }
CrashHandler.java
import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; 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.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.AsyncTask; import android.os.Build; import android.os.Environment; import android.util.Log; public class CrashHandler implements UncaughtExceptionHandler{ /** TAG */ private static final String TAG = "CrashHandler"; /** * uploadUrl * 服务器的地址,根据自己的情况进行更改 **/ private static final String uploadUrl = "http://3.saymagic.sinaapp.com/ReceiveCrash.php"; /** * localFileUrl * 本地log文件的存放地址 **/ private static String localFileUrl = ""; /** mDefaultHandler */ private Thread.UncaughtExceptionHandler defaultHandler; /** instance */ private static CrashHandler instance = new CrashHandler(); /** infos */ private Map<String, String> infos = new HashMap<String, String>(); /** formatter */ private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** context*/ private CrashApplication context; private CrashHandler() {} public static CrashHandler getInstance() { if (instance == null) { instance = new CrashHandler(); } return instance; } /** * @param ctx * 初始化,此处最好在Application的OnCreate方法里来进行调用 */ public void init(CrashApplication ctx) { this.context = ctx; defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this); } /** * uncaughtException * 在这里处理为捕获的Exception */ @Override public void uncaughtException(Thread thread, Throwable throwable) { handleException(throwable); defaultHandler.uncaughtException(thread, throwable); } private boolean handleException(Throwable ex) { if (ex == null) { return false; } Log.d("TAG", "收到崩溃"); collectDeviceInfo(context); writeCrashInfoToFile(ex); restart(); 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); 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); } } } /** * * @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 * @return 返回写入的文件路径 * 写入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; } } 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秒钟后重启应用 } }
MainActivity.java
import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.widget.Toast; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** * 点击按钮后故意产生崩溃 * @param view */ public void generateCrash(View view){ int a = 2/0; } }
SendCrashActivity.java
import java.io.File; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.Toast; /** * 发送crash的activity。该activity是在崩溃后自动重启的。 */ public class SendCrashActivity extends Activity { private static final String uploadUrl = "http://3.saymagic.sinaapp.com/ReceiveCrash.php"; /** * localFileUrl * 本地log文件的存放地址 */ private static String localFileUrl = ""; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_send_crash); //这里把刚才异常堆栈信息写入SD卡的Log日志里面 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { String sdcardPath = Environment.getExternalStorageDirectory().getPath(); localFileUrl = sdcardPath + "/cym/crash/crash.log"; } } public void sendCrash(View view){ new SendCrashLog().execute(""); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.send_crash, menu); return true; } /** * 向服务器发送崩溃信息 */ public class SendCrashLog extends AsyncTask<String, String, Boolean> { public SendCrashLog() { } @Override protected Boolean doInBackground(String... params) { Log.d("TAG", "向服务器发送崩溃信息"); UploadUtil.uploadFile(new File(localFileUrl), uploadUrl); return null; } @Override protected void onPostExecute(Boolean result) { Toast.makeText(getApplicationContext(), "成功将崩溃信息发送到服务器,感谢您的反馈", 1000).show(); Log.d("TAG", "发送完成"); } } }
UploadUtil.java
import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.UUID; import android.util.Log; public class UploadUtil { private static final String TAG = "UPLOADUTIL"; private static final int TIME_OUT = 10*1000; private static final String CHARSET = "utf-8"; 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; } }