开发中,app线上版本经常会出现这样那样的问题,那我们如何知道出现了什么问题呢?并且能快速定位到错误并有效解决呢?这里就涉及到错误日志或叫崩溃日志了。一般情况下,我们都会继承友盟等第三方的错误日志,来抓到错误。但是抓错误原理是什么呢?接下来介绍一下,如何抓错误日志,并上报到服务器。
Android中提供了一个全局异常的捕获,方式如下:
1.定义一个类实现UncaughtExceptionHandler
2.在Application中初始化,Thread.setDefaultUncaughtExceptionHandler(handler);
3.将抓取到的错误保存到本地txt文件
4.上传到服务器
具体代码如下:
(1) 定义一个CrashHandler
public class CrashHandler implements UncaughtExceptionHandler {
private static CrashHandler myCrashHandler;
private CrashHandler() {
};
private Context context;
public final static String LOGPATH =Environment.getExternalStorageDirectory() + “/usershopping/crash.txt” ;
private UncaughtExceptionHandler defaultExceptionHandler;
public synchronized static CrashHandler getInstance() {
if (myCrashHandler == null) {
myCrashHandler = new CrashHandler();
}
return myCrashHandler;
}
public void init(Context context) {
this.context = context;
defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
public void uncaughtException(Thread thread, Throwable ex) {
if(!sdCardIsAvailable()){
ex.printStackTrace();
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(context, “异常退出”, Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
new Thread() {
@Override
public void run() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
android.os.Process.killProcess(android.os.Process.myPid());
}
}.start();
return;
}
//(3)将错误日志保存到本地txt文件
try {
// 在throwable的参数里面保存的有程序的异常信息
StringBuffer sb = new StringBuffer();
// 1.得到手机的版本信息 硬件信息
Field[] fields = Build.class.getDeclaredFields();
for (Field filed : fields) {
filed.setAccessible(true); // 暴力反射
String name = filed.getName();
String value = filed.get(null).toString();
sb.append(name);
sb.append(“=”);
sb.append(value);
sb.append(“\n”);
}
// 2.得到当前程序的版本号
PackageInfo info = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
sb.append(info.versionName);
sb.append("\n");
// 3.得到当前程序的异常信息
Writer writer = new StringWriter();
PrintWriter printwriter = new PrintWriter(writer);
ex.printStackTrace(printwriter);
printwriter.flush();
printwriter.close();
sb.append(writer.toString());
File ff = new File(LOGPATH);
ff.createNewFile();
FileWriter fw = new FileWriter(new File(LOGPATH));
fw.write(sb.toString());
// System.out.println(sb);
fw.close();
} catch (Exception e1) {
e1.printStackTrace();
}
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(context, "异常退出", Toast.LENGTH_LONG).show();
Looper.loop();
}
}.start();
//出现异常后,退出app
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
android.os.Process.killProcess(android.os.Process.myPid());
}
}.start();
}
/**
* 检测sdcard是否可用
*
* @return true为可用,否则为不可用
*/
public static boolean sdCardIsAvailable() {
String status = Environment.getExternalStorageState();
if (!status.equals(Environment.MEDIA_MOUNTED))
return false;
return true;
}
}
(2)在Application中开一个子线程,初始化
//抓错误日志
new Thread(){
@Override
public void run() {
//把异常处理的handler设置到主线程里面
CrashHandler ch = CrashHandler.getInstance();
ch.init(getApplicationContext());
}
}.start();
(4)上传到服务器
前几步,是只要发生崩溃,我们就会将崩溃日志保存到本地,然后强制退出app,接下来我们就可以在启动app的首个activity中,即闪屏页里,判断本地是否有错误日志txt,有就开启服务上传,上传成功后就要删除。这样做可以避免多个错误日志覆盖的问题。
//检查cash.txt是否存在 存在就开启服务 上传到服务器
String path= Environment.getExternalStorageDirectory() + "/usershopping/crash.txt";
final File cashFile=new File(path);
if (cashFile.exists()){
startService(new Intent(context, UploadCashService.class));
}
//这个服务是intentService 自动开启异步,执行结束后自动销毁
public class UploadCashService extends IntentService {
public UploadCashService(String name) {
super(name);
}
public UploadCashService() {
super("UploadCashService");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
}
@SuppressWarnings("deprecation")
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//开启上传
upLoadCash(getApplicationContext());
return super.onStartCommand(intent, flags, startId);
}
private void upLoadCash(final Context context) {
String path= Environment.getExternalStorageDirectory() + "/usershopping/crash.txt";
final File file=new File(path);
final Map<String, String> headers = new HashMap<String, String>();
final Map<String, String> params = new HashMap<String, String>();
params.put("v1", "true");
headers.put(App.TOKEN_NAME, SharedPreferencesUtils.getString(context, "TOKEN"));
headers.put("ContentType", "multipart/form-data");
headers.put("dcode",CommonUtil.getDeviceInfo(context));//手机唯一标识
headers.put("brand", android.os.Build.BRAND); //手机品牌
headers.put("model", Build.MODEL);//手机型号
headers.put("ver",android.os.Build.VERSION.RELEASE);//系统版本号
headers.put("appver", String.valueOf(CommonUtil.getVersion(context)));//app版本号
headers.put("verName", String.valueOf(CommonUtil.getVersionName(context)));//app版本名
headers.put("appname","CLIENT");
PostFormBuilder post = OkHttpUtils.post()
.url(new Constant(context).CASHUPLOAD)
.params(params)
.headers(headers);
post.addFile("mFile" , System.currentTimeMillis() + Math.random() * 10 + ".txt",
file);
post.build().execute(new Callback<Object>() {
@Override
public Object parseNetworkResponse(
Response response)
throws Exception {
// TODO Auto-generated method stub
if (file.exists()){
file.delete();
}
return null;
}
@Override
public void onError(Call call,
Exception e) {
// TODO Auto-generated method stub
MyToast.showToast(context, "错误日志上传失败");
}
@Override
public void inProgress(float progress) {
super.inProgress(progress);
}
@Override
public void onResponse(Object response) {
// TODO Auto-generated method stub
}
});
}
}