一、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;
}
}