项目github地址:https://github.com/CameloeAnthony/DatingBall
在我们的实际开发中,我们都避免不了遇到我们程序crash直接崩掉的情况,这对用户来说是很不友好的 ,当然也是我们开发者不想看到的。所以我们希望当我们的程序发生异常crash的时候,我们能够得治用户的crash信息,我们也可以在程序crash的时候弹出一个对话框告诉用户程序crash了。然后再退出,这样比闪退会好一些。
在安卓中我们可以利用CrashHandler来监视我们应用的crash信息。这里也将CrashHander引入到我们的框架中去。
package com.nsu.library.utils.log; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Environment; import java.io.*; import java.util.Calendar; import java.util.Locale; /** * Create By Anthony on 2016/1/16 * 当前类注释:异常处理类,将我们的异常信息保存到本地SD卡上面或者上传到服务器 */ public class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { private static final boolean DEBUG =true; private static final String TAG ="UncaughtExceptionHandler"; private Context mContext; private Thread.UncaughtExceptionHandler mDefaultHandler; public UncaughtExceptionHandler(Context context, Thread.UncaughtExceptionHandler defaultHandler){ this.mDefaultHandler = defaultHandler; this.mContext = context; } @Override public void uncaughtException(Thread thread, Throwable ex) { Log.e("Crash", "Application crash", ex); writeFile(thread, ex);//将异常信息保存到SD卡上面 //TODO 在这里写方法将异常信息上传到服务器 mDefaultHandler.uncaughtException(thread, ex); } private void writeFile(final Thread thread, final Throwable ex){ //如果SD卡不存在则无法写入 if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ if(DEBUG){ Log.w(TAG,"SDCard unmounted, skip write exception to file"); return ; } } try { OutputStream os = getLogStream(); os.write(getExceptionInformation(thread, ex).getBytes("utf-8")); os.flush(); os.close(); android.os.Process.killProcess(android.os.Process.myPid()); } catch(Exception e){ e.printStackTrace(); } } private OutputStream getLogStream() throws IOException { //crash_log_pkgname.log String fileName = String.format("crash_%s.log", mContext.getPackageName()); File file = new File(Environment.getExternalStorageDirectory(), fileName); if(!file.exists()){ file.createNewFile(); } OutputStream os = new FileOutputStream(file, true); return os; } private String getExceptionInformation(Thread thread, Throwable ex){ long current = System.currentTimeMillis(); StringBuilder sb = new StringBuilder().append('\n'); sb.append("THREAD: ").append(thread).append('\n'); sb.append("BOARD: ").append(Build.BOARD).append('\n'); sb.append("BOOTLOADER: ").append(Build.BOOTLOADER).append('\n'); sb.append("BRAND: ").append(Build.BRAND).append('\n'); sb.append("CPU_ABI: ").append(Build.CPU_ABI).append('\n'); sb.append("CPU_ABI2: ").append(Build.CPU_ABI2).append('\n'); sb.append("DEVICE: ").append(Build.DEVICE).append('\n'); sb.append("DISPLAY: ").append(Build.DISPLAY).append('\n'); sb.append("FINGERPRINT: ").append(Build.FINGERPRINT).append('\n'); sb.append("HARDWARE: ").append(Build.HARDWARE).append('\n'); sb.append("HOST: ").append(Build.HOST).append('\n'); sb.append("ID: ").append(Build.ID).append('\n'); sb.append("MANUFACTURER: ").append(Build.MANUFACTURER).append('\n'); sb.append("MODEL: ").append(Build.MODEL).append('\n'); sb.append("PRODUCT: ").append(Build.PRODUCT).append('\n'); sb.append("SERIAL: ").append(Build.SERIAL).append('\n'); sb.append("TAGS: ").append(Build.TAGS).append('\n'); sb.append("TIME: ").append(Build.TIME).append(' ').append(toDateString(Build.TIME)).append('\n'); sb.append("TYPE: ").append(Build.TYPE).append('\n'); sb.append("USER: ").append(Build.USER).append('\n'); sb.append("VERSION.CODENAME: ").append(Build.VERSION.CODENAME).append('\n'); sb.append("VERSION.INCREMENTAL: ").append(Build.VERSION.INCREMENTAL).append('\n'); sb.append("VERSION.RELEASE: ").append(Build.VERSION.RELEASE).append('\n'); sb.append("VERSION.SDK_INT: ").append(Build.VERSION.SDK_INT).append('\n'); sb.append("LANG: ").append(mContext.getResources().getConfiguration().locale.getLanguage()).append('\n'); sb.append("APP.VERSION.NAME: ").append(getVersionName()).append('\n'); sb.append("APP.VERSION.CODE: ").append(getVersionCode()).append('\n'); sb.append("CURRENT: ").append(current).append(' ').append(toDateString(current)).append('\n'); sb.append(getErrorInformation(ex)); return sb.toString(); } private String getVersionName(){ PackageManager packageManager = mContext.getPackageManager(); PackageInfo packInfo = null; try { packInfo = packageManager.getPackageInfo(mContext.getPackageName(),0); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } String version = packInfo.versionName; return version; } private int getVersionCode(){ PackageManager packageManager = mContext.getPackageManager(); PackageInfo packInfo = null; try { packInfo = packageManager.getPackageInfo(mContext.getPackageName(),0); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } int version = packInfo.versionCode; return version; } private String getErrorInformation(Throwable t){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintWriter writter = new PrintWriter(baos); t.printStackTrace(writter); writter.flush(); String result = new String(baos.toByteArray()); writter.close(); return result; } private String toDateString(long timeMilli){ Calendar calc = Calendar.getInstance(); calc.setTimeInMillis(timeMilli); return String.format(Locale.CHINESE, "%04d.%02d.%02d %02d:%02d:%02d:%03d", calc.get(Calendar.YEAR), calc.get(Calendar.MONTH) + 1, calc.get(Calendar.DAY_OF_MONTH), calc.get(Calendar.HOUR_OF_DAY), calc.get(Calendar.MINUTE), calc.get(Calendar.SECOND), calc.get(Calendar.MILLISECOND)); } }在这里我们只需要在我们的Application的父类:AbsApplication中调用:
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(this, Thread.getDefaultUncaughtExceptionHandler()));
package com.nsu.library.app; import android.app.Application; import com.nsu.library.utils.log.UncaughtExceptionHandler; /** * Create By Anthony on 2016/1/15 * 当前类注释:Application的子类,本项目中的Application将继承本类。 * 当前功能:单例模式,异常捕获,由子类实现的获取url */ public abstract class AbsApplication extends Application { private static AbsApplication sInstance; public static AbsApplication app(){ return sInstance; } @Override public void onCreate() { super.onCreate(); sInstance = this; Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(this, Thread.getDefaultUncaughtExceptionHandler())); } /***************** About URL ***********************/ public static enum SourceType{ JSON, XML,SOAP } abstract public String getApplicationConfigUrl(); abstract public SourceType getSourceType(); abstract public String getFirstClassUrl(); }
这里在以前的项目开发中也用到过另外一种情况,我们直接告诉用户我们的程序异常退出了,需不需要重启:
import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.view.View; import com.trs.xizang.voice.activity.VtibetSplashActivity; import com.trs.xizang.voice.view.VAlertDialog; /** * Created by Anthony on 2015/11/18. */ public class CrashHandler implements Thread.UncaughtExceptionHandler { private static CrashHandler INSTANCE = new CrashHandler(); private Context mContext; private Thread.UncaughtExceptionHandler mDefaultHandler; private CrashHandler() { } public static CrashHandler getInstance() { return INSTANCE; } public void init(Context ctx) { mContext = ctx; mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this); } @Override public void uncaughtException(Thread thread, Throwable ex) { ex.printStackTrace(); new Thread() { @Override public void run() { Looper.prepare(); final AlertDialog dialog = new AlertDialog(mContext); dialog.setMessage("程序异常退出,是否需要重新启动应用?")); dialog.setPositiveListener(new View.OnClickListener() { @Override public void onClick(View v) { dialog.dismiss(); Intent intent = new Intent(mContext.getApplicationContext(), VtibetSplashActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); System.exit(0); } }); dialog.setNegativeListener(new View.OnClickListener() { @Override public void onClick(View v) { dialog.dismiss(); System.exit(0); } }); dialog.show(); Looper.loop(); } }.start(); } }上面这个处理的CrashHandler里面直接调用了Thread.setDefaultUncaughtExceptionHandler(this),然而在我们开始介绍的那个CrashHanler里面是在Application里面调用的。 所以使用上面这个CrashHandler的方式是在Application的onCreate里面调用:
CrashHandler.getInstance().init(this);okay!!!