开发过程中或多或少肯定会出现异常问题,有的可以百分百复现,可以很快的定位到问题,但有的只是偶尔出现一次,定位问题会困难很多,可以使用CrashHandlerManager(主要定位会造成崩溃的异常)和ExceptionManager(主要定位不会造成崩溃的异常)来捕获异常,并打印日志文件到本地。

        注意:Android 9以上的项目,需要适配才可以打印日志到本地。


以下代码在项目里,项目地址:https://gitee.com/urasaki/RxJava2AndRetrofit2

1.先申请权限,关于如何申请权限请查看

2.写一个获取手机唯一识别码的管理类。

package com.phone.common_library.manager;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.telephony.TelephonyManager;

import androidx.core.app.ActivityCompat;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.MessageDigest;
import java.util.UUID;

public class SystemIdManager {

    private static final String TAG = SystemIdManager.class.getSimpleName();
    //保存文件的路径
//    private static final String CACHE_IMAGE_DIR = "aray/cache/devices";
//    private static final String CACHE_IMAGE_DIR = "Android/data/systemId";
    private static final String CACHE_IMAGE_DIR = "Android/systemId";
    //保存的文件 采用隐藏文件的形式进行保存
    private static final String DEVICES_FILE_NAME = ".DEVICES";

    /**
     * 获取设备唯一标识符
     *
     * @param context
     * @return
     */
    public static String getSystemId(Context context) {
        //读取保存的在sd卡中的唯一标识符
        String systemId = readSystemId(context);
        //用于生成最终的唯一标识符
        StringBuffer stringBuffer = new StringBuffer();
        //判断是否已经生成过,
        if (systemId != null && !"".equals(systemId)) {
            LogManager.i(TAG, "已经生成过");
            return systemId;
        }
        try {
            //获取IMES(也就是常说的SystemId)
            systemId = getIMIEStatus(context);
            stringBuffer.append(systemId);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            //获取设备的MACAddress地址 去掉中间相隔的冒号
            systemId = getLocalMac(context).replace(":", "");
            stringBuffer.append(systemId);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //如果以上搜没有获取相应的则自己生成相应的UUID作为相应设备唯一标识符
        if (stringBuffer == null || stringBuffer.length() <= 0) {
            UUID uuid = UUID.randomUUID();
            systemId = uuid.toString().replace("-", "");
            stringBuffer.append(systemId);
        }
        //为了统一格式对设备的唯一标识进行md5加密 最终生成32位字符串
        String md5 = getMD5(stringBuffer.toString(), false);
        if (stringBuffer.length() > 0) {
            //持久化操作, 进行保存到SD卡中
            saveSystemId(md5, context);
        } else {

        }
        return md5;
    }

    /**
     * 读取固定的文件中的内容,这里就是读取sd卡中保存的设备唯一标识符
     *
     * @param context
     * @return
     */
    public static String readSystemId(Context context) {
        File file = getSystemIdDir(context);
        if (file.exists()) {
            LogManager.i(TAG, "file.exists()");
            if (file.length() > 1) {
                LogManager.i(TAG, "file.length() > 1");
                StringBuffer buffer = new StringBuffer();
                try {
                    FileInputStream fis = new FileInputStream(file);
                    InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
                    Reader in = new BufferedReader(isr);
                    int i;
                    while ((i = in.read()) > -1) {
                        buffer.append((char) i);
                    }
                    in.close();
                    return buffer.toString();
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
            } else {
                return null;
            }
        } else {
            LogManager.i(TAG, "xfile.exists()");
            return null;
        }
    }

    /**
     * 获取手机IMEI(需要“android.permission.READ_PHONE_STATE”权限)
     * 需要 READ_PHONE_STATE 权限
     *
     * @param context
     * @return
     */
    public static String getIMIEStatus(Context context) {
        TelephonyManager telephonyManager = (TelephonyManager) context
                .getSystemService(Context.TELEPHONY_SERVICE);
        String iMIEStatus = null;
        if (Build.VERSION.SDK_INT >= 23) {
            if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                //    ActivityCompat#requestPermissions
                // here to request the missing permissions, and then overriding
                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                //                                          int[] grantResults)
                // to handle the case where the user grants the permission. See the documentation
                // for ActivityCompat#requestPermissions for more details.
                return null;
            }
            iMIEStatus = telephonyManager.getDeviceId();
            LogManager.i(TAG, "iMIEStatus*****" + iMIEStatus);
        }
        return iMIEStatus;
    }


    /**
     * 获取设备MAC 地址 由于 6.0 以后 WifiManager 得到的 MacAddress得到都是 相同的没有意义的内容
     * 所以采用以下方法获取Mac地址
     *
     * @param context
     * @return
     */
    private static String getLocalMac(Context context) {
//        WifiManager wifi = (WifiManager) context
//                .getSystemService(Context.WIFI_SERVICE);
//        WifiInfo info = wifi.getConnectionInfo();
//        return info.getMacAddress();

        String macAddress = null;
        StringBuffer buf = new StringBuffer();
        NetworkInterface networkInterface = null;
        try {
            networkInterface = NetworkInterface.getByName("eth1");
            if (networkInterface == null) {
                networkInterface = NetworkInterface.getByName("wlan0");
            }
            if (networkInterface == null) {
                return "";
            }
            byte[] addr = networkInterface.getHardwareAddress();


            for (byte b : addr) {
                buf.append(String.format("%02X:", b));
            }
            if (buf.length() > 0) {
                buf.deleteCharAt(buf.length() - 1);
            }
            macAddress = buf.toString();
        } catch (SocketException e) {
            e.printStackTrace();
            return "";
        }
        return macAddress;
    }

    /**
     * 保存 内容到 SD卡中,  这里保存的就是 设备唯一标识符
     *
     * @param str
     * @param context
     */
    public static void saveSystemId(String str, Context context) {
        File file = getSystemIdDir(context);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            Writer out = new OutputStreamWriter(fos, "UTF-8");
            out.write(str);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 对挺特定的 内容进行 md5 加密
     *
     * @param message   加密明文
     * @param upperCase 加密以后的字符串是是大写还是小写  true 大写  false 小写
     * @return
     */
    public static String getMD5(String message, boolean upperCase) {
        String md5str = "";
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");

            byte[] input = message.getBytes();

            byte[] buff = md.digest(input);

            md5str = bytesToHex(buff, upperCase);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return md5str;
    }

    public static String bytesToHex(byte[] bytes, boolean upperCase) {
        StringBuffer md5str = new StringBuffer();
        int digital;
        for (int i = 0; i < bytes.length; i++) {
            digital = bytes[i];

            if (digital < 0) {
                digital += 256;
            }
            if (digital < 16) {
                md5str.append("0");
            }
            md5str.append(Integer.toHexString(digital));
        }
        if (upperCase) {
            return md5str.toString().toUpperCase();
        }
        return md5str.toString().toLowerCase();
    }

    /**
     * 统一处理设备唯一标识 保存的文件的地址
     *
     * @param context
     * @return
     */
    private static File getSystemIdDir(Context context) {
        File file = null;
        File dirs = new File(Environment.getExternalStorageDirectory(), CACHE_IMAGE_DIR);
        if (!dirs.exists()) {
            LogManager.i(TAG, "!dir.exists()");
//            dir.mkdirs();

            //适配android 9
            boolean isCreateDirsSuccess = DocumentManager.mkdirs(context, dirs);
            LogManager.i(TAG, "getSystemIdDir isCreateDirsSuccess*****" + isCreateDirsSuccess);
        }

        file = new File(dirs, DEVICES_FILE_NAME); // 用DEVICES_FILE_NAME给文件命名
        if (file.exists()) {
            LogManager.i(TAG, "getSystemIdDir file.exists()");
        } else {
            LogManager.i(TAG, "getSystemIdDir !file.exists()");
//            try {
//                file.createNewFile();
//            } catch (IOException e) {
//                e.printStackTrace();
//            }

            //适配android 9
            boolean isCreateFileSuccess = DocumentManager.canWrite(file);
            LogManager.i(TAG, "getSystemIdDir isCreateFileSuccess*****" + isCreateFileSuccess);
        }
        return file;
    }

}

3.写一个获取系统数据的管理类。

package com.phone.common_library.manager;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.telephony.TelephonyManager;

import androidx.core.app.ActivityCompat;

import com.blankj.utilcode.util.DeviceUtils;

import java.util.Locale;
import java.util.UUID;

public class SystemManager {

    /**
     * 获取当前手机系统语言
     *
     * @return 返回当前系统语言。例如:当前设置的是“中文-中国”,则返回“zh-CN”
     */

    public static String getSystemLanguage() {
        return Locale.getDefault().getLanguage();
    }

    /**
     * 获取当前系统上的语言列表(Locale列表)
     *
     * @return 语言列表
     */

    public static Locale[] getSystemLanguageList() {
        return Locale.getAvailableLocales();
    }

    /**
     * 获取当前手机系统版本号
     *
     * @return 系统版本号
     */

    public static String getSystemVersion() {
        return android.os.Build.VERSION.RELEASE;
    }

    /**
     * 获取手机型号
     *
     * @return 手机型号
     */

    public static String getSystemModel() {
        return android.os.Build.MODEL;
    }

    /**
     * 获取手机厂商
     *
     * @return 手机厂商
     */
    public static String getDeviceBrand() {
        return android.os.Build.BRAND;
    }

    /**
     * 获取手机IMEI(需要“android.permission.READ_PHONE_STATE”权限)
     *
     * @return 手机IMEI
     */

    public static String getIMEI(Context context) {
        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Activity.TELEPHONY_SERVICE);
        if (telephonyManager != null) {
            if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                //    ActivityCompat#requestPermissions
                // here to request the missing permissions, and then overriding
                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                //                                          int[] grantResults)
                // to handle the case where the user grants the permission. See the documentation
                // for ActivityCompat#requestPermissions for more details.
                return null;
            }
            return telephonyManager.getDeviceId();
        }
        return null;
    }

    /**
     * 获取手机CPU_ABI
     *
     * @return 手机CPU_ABI
     */
    public static String getDeviceCpuAbi() {
        return android.os.Build.CPU_ABI;
    }

    /**
     * 获取手機唯一識別码(推薦使用,Android有很多雜牌手機,還有山寨機,这个方法可以統一獲取到)
     *
     * @return
     */
    public static String getSystemId(Context context) {
        return SystemIdManager.getSystemId(context);
    }

    /**
     * 获取手機唯一識別码(不推薦使用,Android有很多雜牌手機,還有山寨機,根本不能統一獲取到deviceUuid)
     *
     * @return
     */
    public static String getDeviceUUid() {
        String androidId = DeviceUtils.getAndroidID();
        UUID deviceUuid = new UUID(androidId.hashCode(), ((long) androidId.hashCode() << 32));
        return deviceUuid.toString();
    }

}

4.写一个造成App崩溃的异常管理类。

package com.phone.common_library.manager;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.util.ArrayMap;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.TreeSet;

/**
 * 造成App崩潰的異常管理類(在Application裏初始化,只要一有崩潰的異常就會被捕獲)
 */
public class CrashHandlerManager implements Thread.UncaughtExceptionHandler {

    public static final String TAG = CrashHandlerManager.class.getSimpleName();

    //系统默认的UncaughtException处理类 
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    private static CrashHandlerManager instance;
    private Context mContext;

    /*使用Properties 来保存设备的信息和错误堆栈信息*/
    private Properties mDeviceCrashInfo = new Properties();

    //存储设备信息 
    private Map<String, String> mDevInfoMap = new ArrayMap<>();
    private SimpleDateFormat formatdate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

    private CrashHandlerManager(Context context) {
        mContext = context;
    }

    /**
     * 保证只有一个实例
     *
     * @param context
     * @return
     */
    public static CrashHandlerManager getInstance(Context context) {
        if (instance == null) {
            synchronized (CrashHandlerManager.class) {
                if (instance == null) {
                    instance = new CrashHandlerManager(context);
                }
            }
        }

        return instance;
    }

    public void init() {
        //获得默认的handle 
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //重新设置handle  设置该CrashHandler为程序的默认处理器 
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        //如果開發人員没有处理则让系统默认的异常处理器来处理
        if (!handleException(e) && mDefaultHandler != null) {
            mDefaultHandler.uncaughtException(t, e);
        } else {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException interruptedException) {
                interruptedException.printStackTrace();
                LogManager.i(TAG, "error");
            }

            //结束程序
            ActivityPageManager.getInstance().exitApp2();
        }
    }

    /**
     * Throwable 包含了其线程创建时线程执行堆栈的快照
     * 收集設備信息和保存異常日誌到文件
     *
     * @param throwable
     * @return
     */
    public boolean handleException(Throwable throwable) {
        if (throwable == null) {
            return false;
        }

//        final String msg = throwable.getLocalizedMessage();
//        new Thread() {
//            public void run() {
//                Looper.prepare();
//                Toast.makeText(mContext, "msg" + msg, Toast.LENGTH_LONG).show();
//                Looper.loop();
//            }
//        }.start();

        //收集設備信息
        collectDeviceInfo(mContext);
        //保存異常日誌到文件
        saveCrashInfoToFile(throwable);
        //使用HTTP Post發送錯誤報告
        sendCrashReportsToServer(mContext);
        return true;
    }

    public void sendPreviousReportsToServer() {
        //使用HTTP Post發送錯誤報告
        sendCrashReportsToServer(mContext);
    }

    /**
     * 使用HTTP Post發送錯誤報告
     *
     * @param mContext
     */
    private void sendCrashReportsToServer(Context mContext) {
        String[] crFiles = getCrashReportFiles(mContext);
        if (crFiles != null && crFiles.length > 0) {
            TreeSet<String> sortedFiles = new TreeSet<String>();
            sortedFiles.addAll(Arrays.asList(crFiles));
            for (String fileName : sortedFiles) {
                File cr = new File(mContext.getFilesDir(), fileName);
                postReport(cr);
                //cr.delete(); 
            }
        }
    }

    private void postReport(File cr) {
        // TODO 使用HTTP Post 发送错误报告到服务器

    }

    private static final String CRASH_REPORTER_EXTENSION = ".cr";

    private String[] getCrashReportFiles(Context mContext) {
        File filesDir = mContext.getFilesDir();
        FilenameFilter filter = new FilenameFilter() {

            public boolean accept(File dir, String filename) {
                return filename.endsWith(CRASH_REPORTER_EXTENSION);
            }
        };
        return filesDir.list(filter);
    }

    /**
     * 保存異常日誌到文件
     *
     * @param throwable
     * @return
     */
    private String saveCrashInfoToFile(Throwable throwable) {
        StringBuffer buffer = new StringBuffer();
        for (Map.Entry<String, String> entry : mDevInfoMap.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            buffer.append(key + "=" + value + '\n');
        }
        //可以用其回收在字符串缓冲区中的输出来构造字符串 
        Writer writer = new StringWriter();
        //向文本输出流打印对象的格式化表示形式 
        PrintWriter printWriter = new PrintWriter(writer);
        //将此 throwable 及其追踪输出至标准错误流 
        throwable.printStackTrace(printWriter);
        Throwable cause = throwable.getCause();
        while (cause != null) {
            //异常链 
            cause.printStackTrace();
            cause = cause.getCause();
        }
        printWriter.close();
        String result = writer.toString();
        buffer.append(result);
        try {
            long timestamp = System.currentTimeMillis();
            String time = formatdate.format(new Date(timestamp));

            String fileName = "crash-" + time + "-" + timestamp + ".txt";
            // 判断SD卡是否存在,并且是否具有读写权限 
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/crash_xy/";//     /sdcard/crash/crash-time-timestamp.log
                File dirs = new File(path);
                if (!dirs.exists()) {
                    dirs.mkdirs();
                }
                File file = new File(path, fileName);
                if (!file.exists()) {
                    file.createNewFile();
                }

//                FileOutputStream trace = new FileOutputStream(file);
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
                mDeviceCrashInfo.store(bufferedOutputStream, "");
                bufferedOutputStream.flush();
                bufferedOutputStream.close();
                //output 针对内存来说的 output到file中 
                FileOutputStream fos = new FileOutputStream(file);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                bos.write(buffer.toString().getBytes());
                bos.flush();
                bos.close();
            }
            return fileName;
        } catch (Exception e) {
            LogManager.i(TAG, "an error occured while writing file...", e);
        }
        return null;
    }

    /**
     * 保存內存不足日誌到文件(在即將殺死App之前保存)
     *
     * @param info
     * @return
     */
    public String saveTrimMemoryInfoToFile(String info) {
        collectDeviceInfo(mContext);
        LogManager.i(TAG, "saveTrimMemoryInfoToFile");
        StringBuffer buffer = new StringBuffer();
        for (Map.Entry<String, String> entry : mDevInfoMap.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            buffer.append(key + "=" + value + '\n');
        }

        buffer.append(info);
        try {
            long timestamp = System.currentTimeMillis();
            String time = formatdate.format(new Date(timestamp));

            String fileName = "aCrash-" + time + "-" + timestamp + ".txt";
            // 判断SD卡是否存在,并且是否具有读写权限
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/aCrash_xy/";//     /sdcard/crash/crash-time-timestamp.log
                File dirs = new File(path);
                if (!dirs.exists()) {
                    dirs.mkdirs();
                }
                File file = new File(path, fileName);
                if (!file.exists()) {
                    file.createNewFile();
                }

                //output 针对内存来说的 output到file中
                FileOutputStream fos = new FileOutputStream(file);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                bos.write(buffer.toString().getBytes());
                bos.flush();
                bos.close();
            }
            return fileName;
        } catch (Exception e) {
            LogManager.i(TAG, "an error occured while writing file...", e);
        }
        return null;
    }

    /**
     * 收集設備信息
     *
     * @param context
     */
    private void collectDeviceInfo(Context context) {
        try {
            PackageManager packageManager = context.getPackageManager();
            PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (packageInfo != null) {
                String versionName = packageInfo.versionName == null ? "null" : packageInfo.versionName;
                String versionCode = packageInfo.versionCode + "";
                mDevInfoMap.put("versionName", versionName);
                mDevInfoMap.put("versionCode", versionCode);
            }
        } catch (PackageManager.NameNotFoundException e) {
            LogManager.i(TAG, "NameNotFoundException");
        }

//        //使用反射 获得Build类的所有类里的变量
//        //  Class   代表类在运行时的一个映射
//        //在Build类中包含各种设备信息,
//        // 例如: 系统版本号,设备生产商 等帮助调试程序的有用信息
//        // 具体信息请参考后面的截图
//        Field[] fields = Build.class.getDeclaredFields();
//        for (Field field : fields) {
//            try {
//                field.setAccessible(true);
//                //get方法返回指定对象上此 Field 表示的字段的值
//                mDevInfoMap.put(field.getName(), field.get(null).toString());
//                LogManager.i(TAG, field.getName() + ":" + field.get(null).toString());
//            } catch (Exception e) {
//                LogManager.i(TAG, "an error occured when collect crash info", e);
//            }
//        }

        mDevInfoMap.put("手机厂商", SystemManager.getDeviceBrand());
        mDevInfoMap.put("手机型号", SystemManager.getSystemModel());
        mDevInfoMap.put("手机当前系统语言", SystemManager.getSystemLanguage());
        mDevInfoMap.put("手机系统版本号", SystemManager.getSystemVersion());
        mDevInfoMap.put("手机CPU架构", SystemManager.getDeviceCpuAbi());

//        //手机厂商
//        SystemManager.getDeviceBrand();
//        //手机型号
//        SystemManager.getSystemModel();
//        //手机当前系统语言
//        SystemManager.getSystemLanguage();
//        //手机系统版本号
//        SystemManager.getSystemVersion();
//        //手机CPU架构
//        SystemManager.getDeviceCpuAbi();
//        //手機唯一識別碼
//        SystemManager.getSystemId(mContext);
    }

//    /**
//     * 使用HTTP服务之前,需要确定网络畅通,我们可以使用下面的方式判断网络是否可用
//     *
//     * @param context
//     * @return
//     */
//    public static boolean isNetworkAvailable(Context context) {
//        ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//        NetworkInfo[] info = mgr.getAllNetworkInfo();
//        if (info != null) {
//            for (int i = 0; i < info.length; i++) {
//                if (info[i].getState() == NetworkInfo.State.CONNECTED) {
//                    return true;
//                }
//            }
//        }
//        return false;
//    }


}

5.在Application onCrate方法里初始化(只要一有崩溃的异常就会被捕获)。

CrashHandlerManager crashHandlerManager = CrashHandlerManager.getInstance(this);
        crashHandlerManager.sendPreviousReportsToServer();
        crashHandlerManager.init();

6.写三个类User、User2和User3。

        (1)User类。

package com.phone.common_library.bean;

public class User {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

        (2)User2类。

package com.phone.common_library.bean;

public class User2 extends User {

    private String userId;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    @Override
    public String toString() {
        return "User2{" +
                "userId='" + userId + '\'' +
                '}';
    }
}

        (3)User3类。

package com.phone.common_library.bean;

public class User3 extends User {

    private int userNumber;

    public int getUserNumber() {
        return userNumber;
    }

    public void setUserNumber(int userNumber) {
        this.userNumber = userNumber;
    }

    @Override
    public String toString() {
        return "User3{" +
                "userNumber=" + userNumber +
                '}';
    }
}

7.在Activity中onCreate里写一个会造成崩溃的异常(模拟开发中出现的异常),就会被CrashHandlerManager捕获到,并打印日志文件到本地。

//製造一個不會造成App崩潰的異常(类强制转换异常java.lang.ClassCastException)
            User user = new User2();
            User3 user3 = (User3) user;
            LogManager.i(TAG, user3.toString());

8.写一个不会造成App崩溃的异常(被try(){}catch拋出来的异常)管理类。

package com.phone.common_library.manager;

import android.content.Context;

/**
 * 不會造成App崩潰的異常(被try(){}catch拋出來的異常)管理類
 */
public class ExceptionManager {

    private static final String TAG = ExceptionManager.class.getSimpleName();
    private static ExceptionManager exceptionManager;

    private ExceptionManager() {
    }

    public static ExceptionManager getInstance() {
        if (exceptionManager == null) {
            synchronized (ExceptionManager.class) {
                if (exceptionManager == null) {
                    exceptionManager = new ExceptionManager();
                }
            }
        }

        return exceptionManager;
    }

    /**
     * 打印異常的堆棧日誌
     *
     * @param throwable
     */
    public void throwException(Context context, Throwable throwable) {
//        //1.调试打印堆栈而不退出(推薦使用,開發人員用Log就可以把異常日誌打印出來)
//        LogManager.i(TAG, Log.getStackTraceString(throwable));

        //2.打印异常堆栈(推薦使用,讓系統把異常日誌打印出來)
        throwable.printStackTrace();

//        //3.获取当前线程的堆栈(不推薦使用,打印的不是很詳細,報錯具體哪行沒打印出來,故不要使用這種方法)
//        for (StackTraceElement i : Thread.currentThread().getStackTrace()) {
//            LogManager.i(TAG, i.toString());
//        }

//        //4.打印异常堆栈(不推薦使用,打印的不是很詳細,報錯具體哪行沒打印出來,故不要使用這種方法)
//        throwable.fillInStackTrace();
//        LogManager.i(TAG, "stackTrace", throwable);

        CrashHandlerManager crashHandlerManager = CrashHandlerManager.getInstance(context.getApplicationContext());
        //收集設備信息和保存異常日誌到文件
        crashHandlerManager.handleException(throwable);
    }


}

9.在Activity中onCreate里写一个不会造成崩溃的异常(模拟开发中出现的异常,这种try(){}catch拋出來的异常,就不会造成App崩溃),就会被ExceptionManager捕获到,并打印日志文件到本地。

try {
            //製造一個不會造成App崩潰的異常(类强制转换异常java.lang.ClassCastException)
            User user = new User2();
            User3 user3 = (User3) user;
            LogManager.i(TAG, user3.toString());
        } catch (Exception e) {
            ExceptionManager.getInstance().throwException(rxAppCompatActivity, e);
        }