安卓崩溃异常处理及App重启

内容:优雅地处理崩溃问题,崩溃时给予用户提醒,然后重新启动app,增强用户体验感(当然,不崩溃才是最好的体验,汗)。

未处理崩溃时的效果图:

处理后崩溃时的效果图:

就不写步骤了,直接说下逻辑思想。其实就是拦截系统的崩溃处理,自己进行处理。重点在实现UncaughtExceptionHandler和在其uncaughtException(Thread thread, Throwable ex)中实现友好提示和重启APP,通过手动结束所有活动(重点:不能直接退出,要在基类中将未销毁的Activity保存到数据中,然后统一结束。否则会出现效果图1的情况)

代码如下:

1、导入下类

package com.leixiansheng.test;

import android.annotation.SuppressLint;
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.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;


import java.io.File;
import java.io.FileOutputStream;
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;

/**
 * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
 *
 * Created by Leixiansheng 
 *
 */
public class CrashHandler implements UncaughtExceptionHandler {

	public static final String TAG = "CrashHandler";

	//系统默认的UncaughtException处理类
	private Thread.UncaughtExceptionHandler mDefaultHandler;
	//CrashHandler实例
	private static CrashHandler INSTANCE = new CrashHandler();
	//程序的App对象
	private App application;
	//用来存储设备信息和异常信息
	private Map<String, String> infos = new HashMap<String, String>();

	//用于格式化日期,作为日志文件名的一部分
	private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

	private String path = "/sdcard/crash/";

	/** 保证只有一个CrashHandler实例 */
	private CrashHandler() {
	}

	/** 获取CrashHandler实例 ,单例模式 */
	public static CrashHandler getInstance() {
		return INSTANCE;
	}

	/**
	 * 初始化
	 *
	 * @param application
	 */
	public void init(App application) {
		this.application = application;
		//获取系统默认的UncaughtException处理器
		mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		//设置该CrashHandler为程序的默认处理器
		Thread.setDefaultUncaughtExceptionHandler(this);
	}

	/**
	 * 当UncaughtException发生时会转入该函数来处理
	 */
	@SuppressLint("WrongConstant")
	@Override
	public void uncaughtException(Thread thread, Throwable ex) {
		if(!handleException(ex) && mDefaultHandler != null){
			//如果用户没有处理则让系统默认的异常处理器来处理
			mDefaultHandler.uncaughtException(thread, ex);
		}else{
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				Log.e(TAG, "error : ", e);
			}
			Intent intent = new Intent(application.getApplicationContext(), MainActivity.class);
			PendingIntent restartIntent = PendingIntent.getActivity(
					application.getApplicationContext(), 0, intent,
					Intent.FLAG_ACTIVITY_NEW_TASK);
			//退出程序
			AlarmManager mgr = (AlarmManager)application.getSystemService(Context.ALARM_SERVICE);
			mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000,
					restartIntent); // 1秒钟后重启应用
			//退出程序
			application.finishActivity();
		}
	}

	/**
	 * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
	 *
	 * @param ex
	 * @return true:如果处理了该异常信息;否则返回false.
	 */
	private boolean handleException(Throwable ex) {
		if (ex == null) {
			return false;
		}
		//使用Toast来显示异常信息
		new Thread() {
			@Override
			public void run() {
				Looper.prepare();
				Toast.makeText(application.getApplicationContext(), "很抱歉,程序出现异常,即将重启.", Toast.LENGTH_LONG).show();
				Looper.loop();
			}
		}.start();
		//收集设备参数信息
		collectDeviceInfo(application.getApplicationContext());
		//保存日志文件
		String fileName = saveCrashInfo2File(ex);
//		upload(fileName,path + fileName);
		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);
			}
		} 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
	 * @return	返回文件名称,便于将文件传送到服务器
	 */
	private String saveCrashInfo2File(Throwable ex) {

		StringBuilder sb = new StringBuilder();
		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);
		try {
			long timestamp = System.currentTimeMillis();
			String time = formatter.format(new Date());
			String fileName = "crash-" + time + "-" + timestamp + ".log";
//			String fileName = "crash" + ".log";
			if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
				File dir = new File(path);
				if (!dir.exists()) {
					dir.mkdirs();
				}
				FileOutputStream fos = new FileOutputStream(path + fileName);
				fos.write(sb.toString().getBytes());
				fos.close();
			}
			Log.e(TAG, "fileName:" + fileName);
			return fileName;
		} catch (Exception e) {
			Log.e(TAG, "an error occured while writing file...", e);
		}
		return null;
	}

	/**
	 * 上传至服务器
	 * @param fileName
	 * @param path
	 */
	private void upload(String fileName, String path){
//		new HttpUtils(mContext).uploadFile("url",fileName, path, new HttpUtils.ResponseListener() {
//			@Override
//			public void onSuccess(int code, Object data) {
//
//			}
//
//			@Override
//			public void onFailure(int code, Object data) {
//
//			}
//		});
	}
}

2、Application中初始化CrashHandler,并且添加Activity保存、移除、结束方法

package com.leixiansheng.test;

import android.app.Activity;
import android.app.Application;


import java.util.ArrayList;

/**
 * Created by Leixiansheng on 2018/12/18.
 */

public class App extends Application {

	private ArrayList<Activity> list = new ArrayList<Activity>();

	@Override
	public void onCreate() {
		super.onCreate();
		CrashHandler.getInstance().init(this);
	}

	/**
	 * Activity关闭时,删除Activity列表中的Activity对象*/
	public void removeActivity(Activity a){
		list.remove(a);
	}

	/**
	 * 向Activity列表中添加Activity对象*/
	public void addActivity(Activity a){
		list.add(a);
	}

	/**
	 * 关闭Activity列表中的所有Activity*/
	public void finishActivity(){
		for (Activity activity : list) {
			if (null != activity) {
				activity.finish();
			}
		}
		//杀死该应用进程
		android.os.Process.killProcess(android.os.Process.myPid());
	}
}

3、在基类中实现Activity的保存和移除

package com.leixiansheng.test;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;


/**
 * Created by Leixiansheng on 2018/12/18.
 */

public abstract class BaseActivity extends AppCompatActivity {

	private App mApp;

	@Override
	protected void onCreate(@Nullable Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		mApp = (App) getApplication();
		mApp.addActivity(this);
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		mApp.removeActivity(this);
	}
}

其它类继承基类即可。

注:记得给读写权限,因为要存储崩溃信息到外部存储,未给权限可能出错

Kotlin代码如下:

package com.example.leixiansheng.kotlintest

import android.annotation.SuppressLint
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.os.Looper
import android.util.Log
import android.widget.Toast
import java.io.File
import java.io.FileOutputStream
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.*

/**
 * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
 *
 * Created by Leixiansheng
 *
 */
class CrashHandler

    /** 保证只有一个CrashHandler实例  */
    private constructor() : Thread.UncaughtExceptionHandler {

    //系统默认的UncaughtException处理类
    private var mDefaultHandler: Thread.UncaughtExceptionHandler? = null

    //程序的App对象
    private var application: App? = null

    //用来存储设备信息和异常信息
    private val infos = HashMap<String, String>()

    //用于格式化日期,作为日志文件名的一部分
    private val formatter = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")

    private val path = "/sdcard/crash/"

    /**
     * 初始化
     *
     * @param application
     */
    fun init(application: App) {
        this.application = application
        //获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
        //设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this)
    }

    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @SuppressLint("WrongConstant")
    override fun uncaughtException(thread: Thread, ex: Throwable) {
        if (!handleException(ex) && mDefaultHandler != null) {
            //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler!!.uncaughtException(thread, ex)
        } else {
            try {
                Thread.sleep(2000)
            } catch (e: InterruptedException) {
                Log.e(TAG, "error : ", e)
            }

            val intent = Intent(application!!.applicationContext, MainActivity::class.java)
            val restartIntent = PendingIntent.getActivity(
                    application!!.applicationContext, 0, intent,
                    Intent.FLAG_ACTIVITY_NEW_TASK)
            //退出程序
            val mgr = application!!.getSystemService(Context.ALARM_SERVICE) as AlarmManager
            mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000,
                    restartIntent) // 1秒钟后重启应用
            //退出程序
            application!!.finishActivity()
        }
    }

    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private fun handleException(ex: Throwable?): Boolean {
        if (ex == null) {
            return false
        }
        //使用Toast来显示异常信息
        object : Thread() {
            override fun run() {
                Looper.prepare()
                Toast.makeText(application!!.applicationContext, "很抱歉,程序出现异常,即将重启.", Toast.LENGTH_LONG).show()
                Looper.loop()
            }
        }.start()
        //收集设备参数信息
        collectDeviceInfo(application!!.applicationContext)
        //保存日志文件
        val fileName = saveCrashInfo2File(ex)
        //		upload(fileName,path + fileName);
        return true
    }

    /**
     * 收集设备参数信息
     * @param ctx
     */
    fun collectDeviceInfo(ctx: Context) {
        try {
            val pm = ctx.packageManager
            val pi = pm.getPackageInfo(ctx.packageName, PackageManager.GET_ACTIVITIES)
            if (pi != null) {
                val versionName = if (pi.versionName == null) "null" else pi.versionName
                val versionCode = pi.versionCode.toString() + ""
                infos["versionName"] = versionName
                infos["versionCode"] = versionCode
            }
        } catch (e: PackageManager.NameNotFoundException) {
            Log.e(TAG, "an error occured when collect package info", e)
        }

        val fields = Build::class.java.declaredFields
        for (field in fields) {
            try {
                field.isAccessible = true
                infos[field.name] = field.get(null).toString()
                Log.d(TAG, field.name + " : " + field.get(null))
            } catch (e: Exception) {
                Log.e(TAG, "an error occured when collect crash info", e)
            }

        }
    }

    /**
     * 保存错误信息到文件中
     *
     * @param ex
     * @return    返回文件名称,便于将文件传送到服务器
     */
    private fun saveCrashInfo2File(ex: Throwable): String? {

        val sb = StringBuilder()
        for ((key, value) in infos) {
            sb.append("$key=$value\n")
        }

        val writer = StringWriter()
        val printWriter = PrintWriter(writer)
        ex.printStackTrace(printWriter)
        var cause: Throwable? = ex.cause
        while (cause != null) {
            cause.printStackTrace(printWriter)
            cause = cause.cause
        }
        printWriter.close()
        val result = writer.toString()
        sb.append(result)
        try {
            val timestamp = System.currentTimeMillis()
            val time = formatter.format(Date())
            val fileName = "crash-$time-$timestamp.log"
            //			String fileName = "crash" + ".log";
            if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
                val dir = File(path)
                if (!dir.exists()) {
                    dir.mkdirs()
                }
                val fos = FileOutputStream(path + fileName)
                fos.write(sb.toString().toByteArray())
                fos.close()
            }
            Log.e(TAG, "fileName:$fileName")
            return fileName
        } catch (e: Exception) {
            Log.e(TAG, "an error occured while writing file...", e)
        }

        return null
    }

    /**
     * 上传至服务器
     * @param fileName
     * @param path
     */
    private fun upload(fileName: String, path: String) {
        //		new HttpUtils(mContext).uploadFile("url",fileName, path, new HttpUtils.ResponseListener() {
        //			@Override
        //			public void onSuccess(int code, Object data) {
        //
        //			}
        //
        //			@Override
        //			public void onFailure(int code, Object data) {
        //
        //			}
        //		});
    }

    companion object {

        val TAG = "CrashHandler"
        //CrashHandler实例
        /** 获取CrashHandler实例 ,单例模式  */
        val instance = CrashHandler()
    }
}
package com.example.leixiansheng.kotlintest

import android.app.Activity
import android.app.Application
import java.util.ArrayList

/**
 * Created by Leixiansheng on 2018/12/19.
 */
class App : Application() {

    private val list = ArrayList<Activity>()

    override fun onCreate() {
        super.onCreate()
        CrashHandler.instance.init(this)
    }

    /**
     * Activity关闭时,删除Activity列表中的Activity对象 */
    fun removeActivity(a: Activity) {
        list.remove(a)
    }

    /**
     * 向Activity列表中添加Activity对象 */
    fun addActivity(a: Activity) {
        list.add(a)
    }

    /**
     * 关闭Activity列表中的所有Activity */
    fun finishActivity() {
        for (activity in list) {
            activity.finish()
        }
        //杀死该应用进程
        android.os.Process.killProcess(android.os.Process.myPid())
    }
}
package com.example.leixiansheng.kotlintest

import android.os.Bundle
import android.support.v7.app.AppCompatActivity

/**
 * Created by Leixiansheng on 2018/12/19.
 */
abstract class BaseActivity : AppCompatActivity() {

    private var mApp: App? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mApp = application as App
        mApp!!.addActivity(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        mApp!!.removeActivity(this)
    }
}
package com.example.leixiansheng.kotlintest

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : BaseActivity() {

     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //权限判断
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            //申请授权
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
            return
        } else {
            initEvent()
        }
    }

    private fun initEvent() {
        btnCrash.setOnClickListener { 1 / 0 }
        btnNext.setOnClickListener{ startActivity(Intent(this, SecondActivity::class.java)) }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        when (requestCode) {
            1 -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                initEvent()
            } else {
                Toast.makeText(this, "Need Permission", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/Mr_Leixiansheng/article/details/85091500
今日推荐