Cockroach icon indicating copy to clipboard operation
Cockroach copied to clipboard

性能检测工具和不崩溃的冲突

Open iscmd opened this issue 3 years ago • 1 comments

在一个项目中同时使用性能检测工具类和不会崩溃的App类时,会导致每次都定位至自定义的异常捕获类中的Looper.loop()。

①例如进入App时进行网络状态监听广播,并在子线程中通过使用url.openStream()方法去连接百度,检查是否真正能上网,但子线程网络请求时间过长时,就会定位置到自定义的异常捕获类中的Looper.loop()。 ②或者点击按钮调用SystemClock.sleep(2000),也会定位至自定义的异常捕获类中的Looper.loop(),请求什么方法可进行解决。

异常捕获类CrashCapture如下: Java

public class CrashCapture {

private static final String TAG = "CrashCapture";

private static CrashCapture mInstance;

private Context mContext;

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

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

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(@NonNull final Message msg) {
        super.handleMessage(msg);
        Log.d(TAG, "当前线程:" + Thread.currentThread() + " isMain:" + isMainThread());
        DialogManager.showNormalDialog(mContext, "异常捕获", (String) msg.obj, "复制");
    }
};

private CrashCapture() {

}

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

public static void init(CrashHandler crashHandler) {
    getInstance().setCrashHandler(crashHandler);
}

public void init(final Context context) {
    if (context == null) {
        Log.e(TAG, "==================================================");
        Log.e(TAG, "异常捕捉类的上下文为空,请检查是否在Application中初始化");
        Log.e(TAG, "==================================================");
        return;
    }
    mContext = context;
    final String[] log = new String[1];
    getInstance().setCrashHandler(new CrashHandler() {
        @Override
        public void uncaughtException(Thread t, final Throwable e) {
            if (isMainThread()) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        // 收集设备参数信息
                        collectDeviceInfo(context);
                        // 保存日志文件
                        log[0] = saveCrashInfo2File(e, new CrashResultListener() {
                            @Override
                            public void onCrashResult(Object result) {
                                Message message = Message.obtain();
                                message.obj = result;
                                message.what = 1;
                                mHandler.sendMessage(message);
                                Log.d(TAG, Thread.currentThread().getName() + "【线程抛出】:" + result);
                            }
                        });
                        Log.d(TAG, Thread.currentThread().getName() + "【主线程抛出】:" + log[0]);
                    }
                }).start();
            } else {
                // 收集设备参数信息
                collectDeviceInfo(mContext);
                // 保存日志文件
                log[0] = saveCrashInfo2File(e, new CrashResultListener() {
                    @Override
                    public void onCrashResult(Object result) {
                        Message message = Message.obtain();
                        message.obj = result;
                        message.what = 2;
                        mHandler.sendMessage(message);
                        Log.d(TAG, Thread.currentThread().getName() + "【线程抛出】:" + result);
                    }
                });
                Log.d(TAG, Thread.currentThread().getName() + "【子线程抛出】:" + log[0]);
            }
        }
    });
}

private void setCrashHandler(final CrashHandler crashHandler) {
    // 主线程产生异常,进行捕获操作
    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            //主线程异常拦截
            while (true) {
                try {
                    Looper.loop();      //主线程的异常会从这里抛出
                } catch (Throwable e) {
                    if (crashHandler != null) {//捕获异常处理
                        crashHandler.uncaughtException(Looper.getMainLooper().getThread(), e);
                    }
                }
            }
        }
    });

    // 子线程产生异常,进行捕获操作
    // 所有线程异常拦截,由于主线程的异常都被我们catch住了,所以下面的代码拦截到的都是子线程的异常
    Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            if (crashHandler != null) {//捕获异常处理
                crashHandler.uncaughtException(t, e);
            }
        }
    });

}

/**
 * 判断当前线程是否为主线程
 *
 * @return true为主线程,false为子线程
 */
private boolean isMainThread() {
    return Looper.getMainLooper().getThread() == Thread.currentThread();
}

/**
 * 收集设备参数信息
 *
 * @param ctx
 */
private 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 (PackageManager.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, CrashResultListener crashResultListener) {

    StringBuffer sb = new StringBuffer();
    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);
    crashResultListener.onCrashResult(sb.toString());
    try {
        long timestamp = System.currentTimeMillis();
        String time = formatter.format(new Date());
        String fileName = "crash-" + time + "-" + timestamp + ".log";
        // 应用缓存文件夹地址
        //            String path = getPath(mContext);
        // 使用应用file文件路径
        String path = getFile(mContext, "crash").getPath();
        path = new File(path, fileName).getPath();
        FileOutputStream fos = new FileOutputStream(path);
        fos.write(sb.toString().getBytes());
        fos.close();
        return fileName;
    } catch (Exception e) {
        Log.e(TAG, "an error occured while writing file...", e);
    }
    return null;
}


/**
 * 应用缓存文件夹地址
 *
 * @return
 */
private static String getPath(Context context) {
    String path;

    boolean isExternalStorageLegacy = false;
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            isExternalStorageLegacy = Environment.isExternalStorageLegacy();
        }
    } catch (Exception ignored) {

    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !isExternalStorageLegacy) {
        path = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() + File.separator + "crashcapture" + File.separator + "crash";
    } else {
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            path = context.getExternalCacheDir().getPath() + File.separator + "crashcapture" + File.separator + "crash";
        } else {
            path = context.getCacheDir().getPath() + File.separator + "crashcapture" + File.separator + "crash";
        }
    }
    File dir = new File(path);
    dir.mkdirs();
    Log.d(TAG, path);
    return path;
}

/**
 * 应用file文件夹
 *
 * @return
 */
private static File getFile(Context context, String dirName) {
    File file;
    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
            || !Environment.isExternalStorageRemovable()) {
        //外部存储可用
        file = context.getExternalFilesDir(File.separator + "crashcapture" + File.separator + dirName);
    } else {
        //外部存储不可用
        String filePath = context.getFilesDir().getAbsolutePath() + File.separator + "crashcapture";
        file = new File(filePath, dirName);
        if (!file.exists()) {
            boolean canMake = file.mkdirs();
            if (!canMake) {
                file = null;
            }
        }
    }
    return file;
}

public interface CrashHandler {
    void uncaughtException(Thread t, Throwable e);
}

public interface CrashResultListener {
    void onCrashResult(Object result);
}

} java

性能检测工具类PerformanceMonitorUtil如下: Java public class PerformanceMonitorUtil {

  //使用 printerStart 进行判断是方法执行前还是执行后调用 println
  private boolean printerStart = true;
  //记录方法执行前的时间
  private long printerStartTime = 0L;
  //方法执行时间超过该值才输出
  private long minTime = 1000L;

  // 创建一个Handler对象
  private Handler delayHandler;
  //获取栈信息进行输出
  private StringBuilder stringBuilder = new StringBuilder();
  // 将获取栈消息功能单独抽取出来
  private Runnable runnable = new Runnable() {
      @Override
      public void run() {
          //获取栈信息进行记录
          Log.i("Printer", "【线程列表】:" + Thread.currentThread().toString());
          Log.i("Printer", "【线程】:id = " + Thread.currentThread().getId() + ", name = " +  Thread.currentThread().getName());
          for (StackTraceElement stackTraceElement : Looper.getMainLooper().getThread().getStackTrace()) {
              stringBuilder.append(BlockInfo.SEPARATOR)
                      .append(stackTraceElement);
          }
      }
  };

  volatile private static PerformanceMonitorUtil mInstance;

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

  private PerformanceMonitorUtil() {
      initDelayHandle();
  }

  // 重写Printer()方法
  public void initPrinter() {
      Looper.getMainLooper().setMessageLogging(new Printer() {
          @Override
          public void println(String x) {
              if (printerStart) {
                  printerStart = false;
                  printerStartTime = System.currentTimeMillis();
                  // runnable的销毁
                  delayHandler.removeCallbacks(runnable);
                  //延迟minTime * 0.8发送,用于记录阻塞时的栈信息
                  delayHandler.postDelayed(runnable, (long) (minTime * 0.8));
                  //                    delayHandler.postDelayed(runnable, (long) (minTime * 1.0));
              } else {
                  printerStart = true;
                  // runnable的销毁
                  delayHandler.removeCallbacks(runnable);
                  long temp;
                  if ((temp = System.currentTimeMillis() - printerStartTime) >= minTime) {
                      Log.i("Printer", "方法运行的总时长:" + temp);
                      Log.i("Printer", "StackTrace:" + stringBuilder.toString());
                      // 清空StringBuilder
                      stringBuilder.delete(0, stringBuilder.length());
                  }
              }
          }
      });
  }

  // 初始化Handler,并让Handler的消息在子线程中运行
  private void initDelayHandle() {
      // 生成一个HandlerThread对象
      HandlerThread handlerThread = new HandlerThread("DelayThread");
      // 在使用HandlerThread的getLooper()方法之前,必须先调用该类的start(),同时开启一个新线程;
      handlerThread.start();
      // 将由HandlerThread获取的Looper传递给Handler对象,即由处于另外线程的Looper代替handler初始化时默认绑定的消息队列来处理消息。
      // HandlerThread顾名思义就是可以处理消息循环的线程,它是一个拥有Looper的线程,可以处理消息循环;
      // 其实与其说Handler和一个线程绑定,倒不如说Handler和Looper是一一对应的。
      delayHandler = new Handler(handlerThread.getLooper());
  }

}

Java

学习链接如下: 1、来,带你手写个性能检测工具[https://blog.csdn.net/m0_46278918/article/details/114952287#comments_15603973] 2、制作一个永远不会崩溃的App[https://blog.csdn.net/m0_46278918/article/details/115339393?spm=1001.2014.3001.5501]

iscmd avatar Apr 16 '21 03:04 iscmd

用的是master分支的方案?可以使用X分支的方案 @what-isit

android-notes avatar May 01 '21 14:05 android-notes