魅力程序猿

  • 首页
  • Java
  • Android
  • APP
    • 扑克计分器
    • Video Wallpaper
  • 联系我
  • 关于我
  • 资助
道子
向阳而生
  1. 首页
  2. Android
  3. 正文

Android中CrashHandler的实现

2018年6月29日 4413点热度 0人点赞 0条评论

前言

相信大家在调试android应用程序包的时候经常会遇到“应用程序没有响应”这种情况,也就是当我们的应用程序出现crash情况,这种提示方式会给用户带来很不好的体验。而且,通常crash导致的退出,因为没有走正常的退出流程,会导致数据没保存、线程没有杀死、缓存没有清空等问题。这个时候就需要一个全局CrashHandler。
这个时候做过项目同学就知道有优秀的第三方SDK,譬如国内有:Umeng统计,腾讯的Bugly,国外有:fabric的Crashlytics  等吧,都不一一细说了,使用起来都很方便,接入也很简单。接下来讲讲他们的基本实现方式吧。

CrashHandler的作用

CrashHandler: 崩溃处理器,捕获Crash信息并作出相应的处理

  1. 测试使用:应用在日常的开发中,我们经常需要去Logcat测试我们的App,但由于很多原因,Android Monitor会闪屏或者Crash信息丢失。 这个时候就需要一个CrashHandler来将Crash写入到本地方便我们随时随地查看。
  2. 上线使用:应用的崩溃率是用户衡量筛选应用的重要标准,那么应用上线以后 我们无法向用户借手机来分析崩溃原因。为了减低崩溃率,这个时候需要CrashHandler 来帮我们将崩溃信息返回给后台,以便及时修复。
下面我们就手把手写一个实用、本地化、轻量级的CrashHandler吧

CrashHandler的实现

  • 实现Thread.UncaughtExceptionHandler接口,并重写uncaughtException方法,此时你的CrashHandler就具备了接收处理异常的能力了。
  • 调用Thread.setDefaultUncaughtExceptionHandler(CrashHandler) ,来使用我们自定义的CrashHandler来取代系统默认的CrashHandler
  • 结合单例模式
  • 总体三步: 捕获异常、信息数据获取、数据写入和上传

CrashHandler实现代码如下:

public class CrashHandler implements UncaughtExceptionHandler {
    private static final String TAG = CrashHandler.class.getSimpleName();
    private Context mContext;
    private String mPath;
    private static final String sFileName = "crash";
    private static final String sFileNameSuffix = ".trace";
    private DateFormat mDateFormat;
    private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
    //private ExecutorService mExecutor = Executors.newCachedThreadPool();
    private static CrashHandler mInstance;
    private UncaughtExceptionHandler mUncaughtExceptionHandler;

    private ConcurrentHashMap<String, Object> mInfoMap = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, String> mPackageInfoMap = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, String> mDeviceInfoMap = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, String> mSysInfoMap = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, String> mSecureInfoMap = new ConcurrentHashMap<>();
    private String mMemInfo;
    private String mThrowableInfo;

    /** 异常信息 */
    private static final String sThrowableInfoString = "throwable_info";
    /** 应用包信息 */
    private static final String sPackageInfoMap = "package_info_map";
    /** 设备数据信息 */
    private static final String sBuildInfoMap = "build_info_map";
    /** 系统常规配置信息 */
    private static final String sSystemInfoMap = "system_info_map";
    /** 手机安全配置信息 */
    private static final String sSecureInfoMap = "secure_info_map";
    /** 内存情况信息 */
    private static final String sMemoryInfoString = "memory_info_map";

    private CrashUploader mCrashUploader;

    public static CrashHandler getInstance() {
        if (mInstance == null) {
            synchronized (CrashHandler.class) {
                if (mInstance == null) {
                    mInstance = new CrashHandler();
                }
            }
        }
        return mInstance;
    }

    public void init(Context context, CrashUploader crashUploader) {
        mContext = context.getApplicationContext();
        mCrashUploader = crashUploader;
        mUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
        mDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());
        File crashDir = mContext.getExternalFilesDir(sFileName);
        if (crashDir != null){
            if (!crashDir.exists()) {
                crashDir.mkdirs();
            }
            mPath = crashDir.getAbsolutePath();
        }
    }

    public void deleteCrashFile(){
        clearInfo();
        deleteDirectory(mPath);
    }

    /**
     * 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用uncaughtException方法
     *
     * @param t 出现未捕获异常的线程
     * @param e 未捕获的异常,有了这个ex,我们就可以得到异常信息
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        if (!handleException(e) && mUncaughtExceptionHandler != null) {
            //如果用户没有处理则让系统默认的异常处理器来处理
            mUncaughtExceptionHandler.uncaughtException(t, e);
        }else {
            killProcess();
        }
    }

    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "Sorry, The program is abnormal and will exit soon.", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        });
        collectInfo(ex);
        saveCrashInfoToFile();
        uploadCrashMessage();
        return true;
    }

    /**
     * 退出应用
     */
    private void killProcess() {
        MLog.e(TAG, "killProcess");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Intent startMain = new Intent(Intent.ACTION_MAIN);
        startMain.addCategory(Intent.CATEGORY_HOME);
        startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(startMain);
        //Process.killProcess(Process.myPid());
        System.exit(1);
    }

    /**
     * 收集设备参数信息
     *
     * @param ex
     */
    private void collectInfo(Throwable ex){
        clearInfo();

        collectThrowableInfo(ex);
        collectPackageInfo();
        collectBuildInfo();
        collectSystemInfo();
        collectSecureInfo();
        collectMemInfo();

        mInfoMap.put(sThrowableInfoString, mThrowableInfo);
        mInfoMap.put(sPackageInfoMap, mPackageInfoMap);
        mInfoMap.put(sBuildInfoMap, mDeviceInfoMap);
        mInfoMap.put(sSystemInfoMap, mSysInfoMap);
        mInfoMap.put(sSecureInfoMap, mSecureInfoMap);
        mInfoMap.put(sMemoryInfoString, mMemInfo);
    }

    private void clearInfo(){
        mMemInfo = "";
        mPackageInfoMap.clear();
        mDeviceInfoMap.clear();
        mSysInfoMap.clear();
        mSecureInfoMap.clear();
        mInfoMap.clear();
        mThrowableInfo = "";
    }

    /**
     * 保存日志文件
     */
    private void saveCrashInfoToFile(){
        StringBuffer mStringBuffer = infoMapToStrBuffer(mPackageInfoMap);
        mStringBuffer.append(infoMapToStrBuffer(mDeviceInfoMap));
        mStringBuffer.append(infoMapToStrBuffer(mSysInfoMap));
        mStringBuffer.append(infoMapToStrBuffer(mSecureInfoMap));
        mStringBuffer.append(mMemInfo);
        mStringBuffer.append(mThrowableInfo);
        String mTime = mDateFormat.format(new Date());
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            try {
                String filePath = addSlash(mPath) + mTime + sFileName + sFileNameSuffix;
                FileOutputStream mFileOutputStream = new FileOutputStream(filePath);
                mFileOutputStream.write(mStringBuffer.toString().getBytes());
                mFileOutputStream.close();
                MLog.d(TAG, filePath);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 上传崩溃信息
     */
    private void uploadCrashMessage(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                //http 上传服务端

                //deleteCrashFile();
                //成功
                Looper.prepare();
                mCrashUploader.uploadSuccess();
                Looper.loop();

                //失败
                Looper.prepare();
                mCrashUploader.uploadError("");
                Looper.loop();
            }
        }).start();
    }

    /**
     * 获取捕获异常的信息
     *
     * @param ex
     */
    private void collectThrowableInfo(Throwable ex) {
        Writer mWriter = new StringWriter();
        PrintWriter mPrintWriter = new PrintWriter(mWriter);
        ex.printStackTrace(mPrintWriter);
        ex.printStackTrace();
        Throwable throwable = ex.getCause();
        while (throwable != null) {
            throwable.printStackTrace(mPrintWriter);
            mPrintWriter.append("\r\n");
            throwable = throwable.getCause();
        }
        mPrintWriter.close();
        mThrowableInfo = mWriter.toString();
    }


    /**
     * 获取APP进程内存信息
     */
    private void collectMemInfo() {
        BufferedReader bufferedReader = null;
        StringBuffer stringBuffer = new StringBuffer();

        ArrayList<String> commandLine = new ArrayList<>();
        commandLine.add("dumpsys");
        commandLine.add("meminfo");
        commandLine.add(Integer.toString(Process.myPid()));
        try {
            java.lang.Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()]));
            bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 4 * 1024);
            while (true) {
                String line = bufferedReader.readLine();
                if (line == null) {
                    break;
                }
                stringBuffer.append(line);
                stringBuffer.append("\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        mMemInfo = stringBuffer.toString();
    }

    /**
     * 获取系统安全设置信息
     */
    private void collectSecureInfo() {
        Field[] fields = Settings.Secure.class.getFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class && field.getName().startsWith("WIFI_AP")) {
                try {
                    String value = Settings.Secure.getString(mContext.getContentResolver(), (String) field.get(null));
                    if (value != null) {
                        mSecureInfoMap.put(field.getName(), value);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 获取系统常规设定属性
     */
    private void collectSystemInfo() {
        Field[] fields = Settings.System.class.getFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class) {
                try {
                    String value = Settings.System.getString(mContext.getContentResolver(), (String) field.get(null));
                    if (value != null) {
                        mSysInfoMap.put(field.getName(), value);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 从系统属性中提取设备硬件和版本信息
     *
     * 迭代Build的字段key-value 此处的信息主要是为了在服务器端手机各种版本手机报错的原因
     *
     */
    private void collectBuildInfo() {
        Field[] mFields = Build.class.getDeclaredFields();
        for (Field field : mFields) {
            try {
                field.setAccessible(true);
                mDeviceInfoMap.put(field.getName(), field.get("").toString());
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 获取应用包参数信息
     */
    private void collectPackageInfo() {
        try {
            PackageManager mPackageManager = mContext.getPackageManager();
            PackageInfo mPackageInfo = mPackageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (mPackageInfo != null) {
                mPackageInfoMap.put("PackageName", TextUtils.isEmpty(mPackageInfo.packageName) ? "unknown" : mPackageInfo.packageName);
                mPackageInfoMap.put("VersionName", TextUtils.isEmpty(mPackageInfo.versionName) ? "unknown" : mPackageInfo.versionName);
                mPackageInfoMap.put("VersionCode", mPackageInfo.versionCode + "");
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 将HashMap遍历转换成StringBuffer
     */
    @NonNull
    public static StringBuffer infoMapToStrBuffer(ConcurrentHashMap<String, String> infoMap) {
        StringBuffer mStringBuffer = new StringBuffer();
        for (Map.Entry<String, String> entry : infoMap.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            mStringBuffer.append(key + "=" + value + "\r\n");
        }
        return mStringBuffer;
    }

    private String addSlash(String pPath) {
        if (!TextUtils.isEmpty(pPath)) {
            if (!pPath.endsWith(File.separator)) {
                pPath = pPath + File.separator;
            }
        }else {
            pPath = File.separator;
        }
        return pPath;
    }

    private boolean deleteDirectory(String pDir){
        File dirFile = new File(addSlash(pDir));
        if (!dirFile.exists() || !dirFile.isDirectory()) {
            return false;
        }
        boolean delFlag = true;
        File[] files = dirFile.listFiles();
        for (File file : files) {
            if (file.isFile()) {
                delFlag = deleteFile(file.getAbsolutePath());
                if (!delFlag) {
                    break;
                }
            } else {
                delFlag = deleteDirectory(file.getAbsolutePath());
                if (!delFlag) {
                    break;
                }
            }
        }
        if (!delFlag) {
            return false;
        }
        if (dirFile.delete()) {
            return true;
        } else {
            return false;
        }
    }

    private boolean deleteFile(String pFileName){
        try {
            File file = new File(pFileName);
            if (file.isFile() && file.exists()) {
                file.delete();
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 崩溃信息上传接口回调
     */
    public interface CrashUploader {
        void uploadSuccess();
        void uploadError(String error);
    }
}
在Application中使用CrashHandler:
@Override
public void onCreate() {
    super.onCreate();
    registerCrashHandler();
    MLog.setShowLog(BuildConfig.LOG_DEBUG);
    initUmeng();
    //监听APP进入后台或切回前台
    addAppForegroundListener();
}

private void registerCrashHandler(){
CrashHandler.getInstance().init(getApplicationContext(), new CrashHandler.CrashUploader() {
@Override
public void uploadSuccess() {

Toast.makeText(getContext(), "uploadSuccess",Toast.LENGTH_SHORT).show();
CrashHandler.getInstance().deleteCrashFile();

}

@Override
public void uploadError(String error) {

}
});
}
结果查看/sdrard/Android/data/com.aoaoyi.poker/files/crash/,里的文件内容如下:

附加

1. 需要在AndroidManifest.xml加入权限声明,Android6.0以上实现动态权限申请。

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

2. System.exit(0)和Process.killProcess(Process.myPid())

相同点
1、APP进程都会被直接杀掉
我们通过adb shell ps可以查看当前手机所有运行的进程状态,
在执行完这两种方式之后,APP进程都会消失。
2、生命周期都不会被调用
onPause()、onStop()和onDestory(),也包括onSaveInstanceState,这些生命周期方法都不会被调用。
3、App被杀死,然后都安排会立即重启进程,并重启之前状态对应的Activity、Service、ContentProvider等。
不同点
1、影响范围不同
System.exit(0)
只会影响当前的程序;
Process.killProcess(Process.myPid())
会杀掉所有PID一样的进程,比如那些拥有相同UID的应用,统统都会被杀掉。
2、方式不一样
System.exit(0)
是停止程序的虚拟机;
Process.killProcess(Process.myPid())
是通过PID去杀死进程。
3. System.exit(0)和System.exit(1)
看官方的方法注释,如下:
Use 0 to signal success to the calling process and 1 to signal failure.1
0:代表成功的信号(正常退出);
1:代表失败的信号(异常退出),常用于捕获到异常执行。
从上面可以看出,这两种方式都比较暴力,不推荐使用,尤其是现在的手机配置都很好的情况下。而且进程都被杀掉了,相应的Service也会被干掉.
真要用的话,也得注意:如果程序有多个Activity,最好在主Activity中的OnBackPressed()中使用,如果在其它Activity中使用的话,APP会直接退出,退出后会重启应用,这样对于用户体验不是很好。
4. 稍微接近完美的退出方案
Intent startMain = new Intent(Intent.ACTION_MAIN);
startMain.addCategory(Intent.CATEGORY_HOME);
startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(startMain);
System.exit(1);
先回到系统Home界面,在进行异常退出。
标签: CrashHandler的实现 UncaughtExceptionHandler
最后更新:2018年6月29日

daozi

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复
搜索
联系方式

QQ群:179730949
QQ群:114559024
欢迎您加入Android大家庭
本人QQ:136049925

赐我一丝安慰
给我一点鼓励

COPYRIGHT © 2023 魅力程序猿. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

豫ICP备15000477号