Android 捕获错误日志的方法

前提

今天在群里聊天的时候有群友问如何捕获错误日志,我说可以自己写,也可以用第三方的比如腾讯的bugly,友盟的错误统计等等,但是那些是别人的东西,作为一个程序员当然是要知其然,并且要知其所以然。因此今天就在此写一下关于捕获错误日志的文章,希望可以给新手指导,大佬请绕行。

首先

要捕获错误日志当然是调用系统的了,这样最方便,也是大家常用的了,废话不多说,直接上图,no pic say a xx.

错误日志.png

其次

上面的图是日志信息,下面来看看代码如何编写。

捕获错误日志信息类

public class CrashHandler implements UncaughtExceptionHandler {

 private static final String TAG = "CrashHandler";
 private static final boolean DEBUG = true;

 private static final String FILE_NAME = "crash";

// log文件的后缀名
private static final String FILE_NAME_SUFFIX = ".txt";

private static CrashHandler sInstance = new CrashHandler();

// 系统默认的异常处理(默认情况下,系统会终止当前的异常程序)
private UncaughtExceptionHandler mDefaultCrashHandler;

private Context mContext;
//log路径
private String mLogPath=null;

// 构造方法私有,防止外部构造多个实例,即采用单例模式
private CrashHandler() {
}

public static CrashHandler getInstance() {
 return sInstance;
}

// 这里主要完成初始化工作
public void init(Context context,String logPath) {
 // 获取系统默认的异常处理器
 mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
 // 将当前实例设为系统默认的异常处理器
 Thread.setDefaultUncaughtExceptionHandler(this);
 // 获取Context,方便内部使用
 mContext = context.getApplicationContext();
 this.mLogPath=logPath;
}

/**
 * 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用#uncaughtException方法
 * thread为出现未捕获异常的线程,ex为未捕获的异常,有了这个ex,我们就可以得到异常信息。
 */
@Override
public void uncaughtException(Thread thread, Throwable ex) {
 try {
  // 导出异常信息到SD卡中
  dumpExceptionToSDCard(ex);
  // 这里可以通过网络上传异常信息到服务器,便于开发人员分析日志从而解决bug
  uploadExceptionToServer();
 } catch (IOException e) {
  e.printStackTrace();
 }

 // 打印出当前调用栈信息
 ex.printStackTrace();

 // 如果系统提供了默认的异常处理器,则交给系统去结束我们的程序,否则就由我们自己结束自己
 if (mDefaultCrashHandler != null) {
  mDefaultCrashHandler.uncaughtException(thread, ex);
 } else {
  Process.killProcess(Process.myPid());
 }

}

/**
 * 写入SD卡
 *
 * @param ex
 * @throws IOException
 */
@SuppressLint("SimpleDateFormat")
private void dumpExceptionToSDCard(Throwable ex) throws IOException {
 // 如果SD卡不存在或无法使用,则无法把异常信息写入SD卡
 if (!Environment.getExternalStorageState().equals(
   Environment.MEDIA_MOUNTED)) {
  if (DEBUG) {
   Log.e(TAG, "sdcard unmounted,skip dump exception");
   return;
  }
 }

 File dir = new File(mLogPath);
 if (!dir.exists()) {
  dir.mkdirs();
 }
 long current = System.currentTimeMillis();
 String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
   .format(new Date(current));
 // 以当前时间创建log文件
 File file = new File(mLogPath + FILE_NAME + time
   + FILE_NAME_SUFFIX);

 try {
  PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(
    file)));
  // 导出发生异常的时间
  pw.println(time);

  // 导出手机信息
  dumpPhoneInfo(pw);

  pw.println();
  // 导出异常的调用栈信息
  ex.printStackTrace(pw);

  pw.close();
 } catch (Exception e) {
  Log.e(TAG, "dump crash info failed");
 }
}

private void dumpPhoneInfo(PrintWriter pw) throws NameNotFoundException {
 // 应用的版本名称和版本号
 PackageManager pm = mContext.getPackageManager();
 PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(),
   PackageManager.GET_ACTIVITIES);
 pw.print("App Version: ");
 pw.print(pi.versionName);
 pw.print('_');
 pw.println(pi.versionCode);

 // android版本号
 pw.print("OS Version: ");
 pw.print(Build.VERSION.RELEASE);
 pw.print("_");
 pw.println(Build.VERSION.SDK_INT);

 // 手机制造商
 pw.print("Vendor: ");
 pw.println(Build.MANUFACTURER);

 // 手机型号
 pw.print("Model: ");
 pw.println(Build.MODEL);

 // cpu架构
 pw.print("CPU ABI: ");
 pw.println(Build.CPU_ABI);
}

/**
 * 上传到服务器(这里需要实现)
 */
private void uploadExceptionToServer() {
}

}

APP(自定义的Application)

public class APP extends Application {
 //log路径
 private static final String LOG_PATH= Environment
  .getExternalStorageDirectory().getPath() + File.separator + "Live" + File.separator
  + "log" + File.separator;

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

MainActivity

class MainActivity : AppCompatActivity(){

var mBtnSecond:Button?=null;

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

fun initView(){
 mBtnSecond=findViewById(R.id.btn_second)
 mBtnSecond?.setOnClickListener{
  var intent= Intent(this,SecondActivity::class.java)
  startActivity(intent)
 }
}

/**
 * 6.0以下版本(系统自动申请) 不会弹框
 * 有些厂商修改了6.0系统申请机制,他们修改成系统自动申请权限了
 */
private fun checkPermission(){
 val permissionItems = ArrayList<PermissionItem>()
 permissionItems.add(PermissionItem(Manifest.permission.READ_EXTERNAL_STORAGE, "读取空间", R.drawable.permission_ic_phone))
 permissionItems.add(PermissionItem(Manifest.permission.WRITE_EXTERNAL_STORAGE,"存储空间",R.drawable.permission_ic_storage))
 HiPermission.create(this)
   .title("亲爱的上帝")
   .msg("为了能够正常使用,请开启这些权限吧!")
   .permissions(permissionItems)
   .style(R.style.PermissionDefaultBlueStyle)
   .animStyle(R.style.PermissionAnimScale)
   .checkMutiPermission(object : PermissionCallback {
    override fun onClose() {
     Toast.makeText(this@MainActivity,"用户关闭了权限",Toast.LENGTH_LONG).show();
    }

    override fun onFinish() {
     Toast.makeText(this@MainActivity,"初始化完毕!",Toast.LENGTH_LONG).show();
    }

    override fun onDeny(permission: String, position: Int) {
    }

    override fun onGuarantee(permission: String, position: Int) {
    }
   })
}

 }

MainActivity.png

CrashActivity

public class CrashActivity extends AppCompatActivity {

Button mBtnCrash;

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_second);
 mBtnCrash=findViewById(R.id.btn_crash);
 mBtnCrash.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
   Toast.makeText(CrashActivity.this,"出现异常了",Toast.LENGTH_LONG).show();
    throw new RuntimeException(toUtf8("出现异常了"));
  }
 });
}

public static String toUtf8(String str) {
 String result = null;
 try {
  result = new String(str.getBytes("UTF-8"), "UTF-8");
 } catch (UnsupportedEncodingException e) {
  e.printStackTrace();
 }
 return result;
}
}

CrashActivity.png

最后

这里需要注意的是,在MainActivity中用的是Kotlin写的权限控制,也就是运行时权限

implementation 'me.weyye.hipermission:library:1.0.7'

要保存日志当然需要SD卡的读写权限。

项目地址:https://gitee.com/1032200695/CrashException

本地下载:CrashException_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android高性能日志写入方案的实现

    前言 公司目前在做一款企业级智能客服系统,对于系统稳定性要求很高,不过难保用户在使用中不会出现问题,而 Android SDK 集成在客户的 APP 中,同时由于 Android 碎片化的问题,对于 SDK 的问题排查就显得尤为困难,因此记录下用户的操作日志就显得极为重要. 初始方案 一开始,SDK 记录日志的方式是直接通过写文件,当有一条日志要写入的时候,首先,打开文件,然后写入日志,最后关闭文件.这样做的问题就在于频繁的IO操作,影响程序的性能,而且 SDK 为了保证消息的及时性,还维护了一

  • Android 日志系统Logger源代码详细介绍

    我们知道,在Android系统中,提供了一个轻量级的日志系统,这个日志系统是以驱动程序的形式实现在内核空间的,而在用户空间分别提供了Java接口和C/C++接口来使用这个日志系统,取决于你编写的是Android应用程序还是系统组件.在前面的文章浅谈Android系统开发中LOG的使用中,已经简要地介绍了在Android应用程序开发中Log的使用方法,在这一篇文章中,我们将更进一步地分析Logger驱动程序的源代码,使得我们对Android日志系统有一个深刻的认识. 既然Android 日志系统是

  • Android中如何安全地打印日志详解

    前言 在Android开发过程中,不管是写Demo还是实战项目中,都会打印一些日志用于记录数据,调试来着,Android中的日志工具类是Log,这个类提供了一些方法来打印日志.五个级别,v.d.i.w.e,各有不同的重载. 当谈到如何打印日志?很多人会想这不是很简单,直接使用android.util.Log这个类不就行了?然而,日志属于非常敏感的信息:逆向工程师在逆向你的程序的时候,本来需要捕捉你程序的各种输出,然后进行推测,顺藤摸瓜然后得到需要的信息:一旦你的日志泄漏,无异于门户洞开,破解你的

  • android 捕获系统异常并上传日志具体实现

    在做项目时,经常会把错误利用异常抛出去,这样在开发时就可以通过手机抛出的异常排查错误.但是当程序开发完毕,版本稳定,需要上线时,为了避免抛出异常影响用户感受,可以用UncaughtExceptionHandler捕获全局异常,对异常做出处理.比如我们可以获取到抛出异常的时间.手机的硬件信息.错误的堆栈信息,然后将获取到的所有的信息发送到服务器中,也可以发送到指定的邮件中,以便及时修改bug. 示例: 自定义异常类实现UncaughtExceptionHandler接口,当某个页面出现异常就会调用

  • Android adb logcat 命令查看日志详细介绍

    Android 开发的程序员开发程序的时候,一定为log而苦恼过吧.Eclipse老是Log找不到,是不是很让人不爽,虽然Android Studio的Logcat功能很不错,但也没有在命令行terminal中打印出来更方便. 使用 logcat 命令 查看和跟踪系统日志缓冲区的命令logcat的一般用法是: [adb] logcat [<option>] ... [<filter-spec>] ... 下文介绍过滤器和命令选项,详细内容可参见Listing of logcat C

  • Android 日志工具(log)的使用方法

    使用Android的日志工具Log 方法: Android中的日志工具类为Log,这个类提供了如下方法来供我们打印日志: 使用方法: Log.d("MainActivity","onCreate execute"); 第一个参数tag:一般传入当前类名就好,主要用于队打印信息进行过滤. 第二个参数:msg,具体想打印的内容. 如: public class MainActivity extends AppCompatActivity { protected void

  • Android 捕获错误日志的方法

    前提 今天在群里聊天的时候有群友问如何捕获错误日志,我说可以自己写,也可以用第三方的比如腾讯的bugly,友盟的错误统计等等,但是那些是别人的东西,作为一个程序员当然是要知其然,并且要知其所以然.因此今天就在此写一下关于捕获错误日志的文章,希望可以给新手指导,大佬请绕行. 首先 要捕获错误日志当然是调用系统的了,这样最方便,也是大家常用的了,废话不多说,直接上图,no pic say a xx. 错误日志.png 其次 上面的图是日志信息,下面来看看代码如何编写. 捕获错误日志信息类 publi

  • Vue全局监测错误并生成错误日志实现方法介绍

    目录 一.准备工作 (1)规定错误码 (2)设置错误处理函数 (3)保存错误日志 二.监听错误 (1)JS错误与静态资源加载错误 (2)Vue逻辑错误 (3)请求错误与Promise错误 三.效果演示 四.完整代码 一.准备工作 (1)规定错误码 像是请求码(404.500)一样,我觉得错误都应该规定好对应的错误码.个人喜好. // 错误代码 const errCode = new Map([ // 本地系统错误 ['E1001', '系统未知错误'], ['E1002', 'vue逻辑错误']

  • CodeIgniter记录错误日志的方法全面总结

    本文实例讲述了CodeIgniter记录错误日志的方法.分享给大家供大家参考,具体如下: CI工作流程: 所有的入口都从根目录下的index.php进入,确定应用所在目录后,加载 codeigniter/CodeIgniter.php 文件,该文件会顺序加载以下文件执行整个流程. index.php:检测文件路径,加载codeigniter.php文件 codeigniter.php: 加载 Common/constants....文件.获取文件模式.设置计时器.实例化类(错误类.扩展类.钩子类

  • NodeJS读取分析Nginx错误日志的方法

    网上很少看到有用NodeJS运维系列文章,后续我会更新一些NodeJS运维相关的内容又或者说让我们更加的深入了解一些服务器的知识以及自动化运维方面的基础知识 为什么要做错误日志分析,因为网上这方面的工具不多我找到一个goaccess但是都是分析成功日志以及用户访问趋势,找了半天没找着自己想要的索性就自己利用Node造一个 错误日志分析 首先我们要读取Nginx日志,我们可以看到Nginx的错误日志格式一般都是这样子,需要注意的是Nginx的错误日志格式是差不多的因为无法设置日志格式只能设置日志错

  • MS SQL Server数据库清理错误日志的方法

    SQL错误日志记录了数据库运行过程的遇到的各种问题及一些重要信息,作为排错需要,我们通常都不会主动去清理这些日志文件,只有每次重启服务器时,SQL会自动删除时间最老的日志文件,并新生成一个日志文件.通过在服务器上查看数据库的日志文件,发现存在大量的query notification dialog的信息,而且出现的频率非常的高,导致日志文件增大非常快.通过google了解到这个错误跟service broker的消息机制由关系,可以通过使用跟踪标记:DBCC TraceOn(4133,-1)可消

  • SQL Server代理:理解SQL代理错误日志处理方法

    SQL Server代理是所有实时数据库的核心.代理有很多不明显的用法,因此系统的知识,对于开发人员还是DBA都是有用的.这系列文章会通俗介绍它的很多用法. 如我们在这个系列的前几篇文章所见,SQL Server代理是由一系列的作业步骤组成,每个步骤是不同类型将要进行的工作.如果你在第4篇所见,SQL Server代理也提供使用数据库邮件发送提醒的能力.如果出现问题,不管怎样, 你必须去查看下数据库邮件错误日志.在这篇文章里,你会学到如何理解和查看SQL Server错误日志的所有相关知识.你会

  • 查看MySQL的错误日志的方法

    MySQL的错误信息是在data目录下的,且文件名为<hostname>.err(<hostname>指的是主机名),但由于每个人安装的环境不一样,或你忘记了data目录的所在位置,你可以通过下面方法查找. #hostname //获得主机名 <hostname> #find / -name <hostname>.err ..... #cd ... #vi <hostname>.err

  • php错误日志简单配置方法

    本文实例讲述了php配置错误日志的方法.分享给大家供大家参考,具体如下: php.ini: ; 错误日志 log_errors = On ; 显示错误 display_errors = Off ; 日志路径 error_log = "/usr/local/lnmp/php/var/log/error_log" ; 错误等级 error_reporting = E_ALL&~E_NOTICE php-fpm.conf: [global] ; php-fpm pid文件 pid =

  • 实现Nginx中使用PHP-FPM时记录PHP错误日志的配置方法

    nginx与apache不一样,在apache中可以直接指定php的错误日志,那样在php执行中的错误信息就直接输入到php的错误日志中,可以方便查询. 在nginx中事情就变成了这样:nginx只对页面的访问做access记录日志.不会有php的error log 信息.nginx把对php的请求发给php-fpm fastcgi进程来处理,默认的php-fpm只会输出php-fpm的错误信息,在php-fpm的errors log里也看不到php的errorlog. 原因是php-fpm的配

  • asp.net错误捕获(错误处理)page_error事件使用方法

    ASP.NET 提供了三种用于在出现错误时捕获和响应错误的主要方法:page_error事件.application_error 事件以及应用程序配置文件 (Web.config). 这三种方法的主要作用无非是创建自定义的错误报告,创建自定义错误报告的作用无非是:出于安全方面的考虑,还有就是为了向用户展示友好的错误页面,提高程序的友好性. Page_Error 事件,现举例说明如下: 此示例在浏览器中显示了详细的错误信息,提供此示例只是为了进行说明.向应用程序的最终用户展示出错的详细信息是非常危

随机推荐