一拗踢、Android Crash說明
程序因未捕獲的異常而突然終止, 系統(tǒng)會調(diào)用處理程序的接口UncaughtExceptionHandler;
處理未被程序正常捕獲的異常烁焙,只需實現(xiàn)這個接口里的UncaughtExceptionHandler方法副编,UncaughtExceptionHandler方法回傳了 Thread 和 Throwable 兩個參數(shù)。
二经瓷、實現(xiàn)思路
首先收集產(chǎn)生崩潰的手機信息汤善,因為Android的樣機種類繁多危号,很可能某些特定機型下會產(chǎn)生莫名的bug;
將手機的信息和崩潰信息寫入文件系統(tǒng)中牧愁。這樣方便后續(xù)處理;
崩潰的應用需要可以自動重啟。重啟的頁面設置成反饋頁面外莲,詢問 用戶是否需要上傳崩潰報告;
用戶同意后猪半,即將寫入的崩潰信息文件發(fā)送到自己的服務器。
三苍狰、代碼展示
-
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 * 服務器的地址办龄,根據(jù)自己的情況進行更改 **/ 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方法里來進行調(diào)用 */ 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 * 將崩潰寫入文件系統(tǒng) */ 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); } /** * 點擊按鈕后故意產(chǎn)生崩潰 * @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; /** * 發(fā)送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; } /** * 向服務器發(fā)送崩潰信息 */ public class SendCrashLog extends AsyncTask<String, String, Boolean> { public SendCrashLog() { } @Override protected Boolean doInBackground(String... params) { Log.d("TAG", "向服務器發(fā)送崩潰信息"); UploadUtil.uploadFile(new File(localFileUrl), uploadUrl); return null; } @Override protected void onPostExecute(Boolean result) { Toast.makeText(getApplicationContext(), "成功將崩潰信息發(fā)送到服務器翔忽,感謝您的反饋", 1000).show(); Log.d("TAG", "發(fā)送完成"); } } }
-
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"; //內(nèi)容類型 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; } }