文档章节

关于Android中处理崩溃异常和分析日志的两种思路

shzwork
 shzwork
发布于 03/26 08:40
字数 2427
阅读 47
收藏 0

     我们写程序的时候都希望能写出一个没有任何Bug的程序,期望在任何情况下都不会发生程序崩溃。不过理想是丰满的,现实是骨感的。没有一个程序员能保证自己写的程序绝对不会出现异常崩溃。特别是针对用户数达到几十万几百万的程序,当你用户数达到一定数量级后,就算你的程序出现个别异常崩溃情况也不用惊讶。此时及时收集用户的日志成了解决问题的关键。本文从两种方式分析查看日志的方式:

1、在自测阶段或者交给测试部测试阶段出现了:

1,程序异常退出 , uncaused exception
2,程序强制关闭 ,Force Closed (简称FC)
3,程序无响应 , Application No Response (简称ANR) , 顺便,一般主线程超过5秒么有处理就会ANR

将终端的/data/anr/traces.txt文件取出(命令如:adb pull /data/anr/traces.txt C:\)拿到日志文件打开后一看"哇"好长,但是不要害怕log日志虽长,但其实它由三大块儿组成:

1、系统基本信息 ,包括 内存,CPU ,进程队列 ,虚拟内存 , 垃圾回收等信息 。
Heap: 4% free, 66MB/69MB; 164920 objects
Dumping cumulative Gc timings
Total number of allocations 617049
Total bytes allocated 86MB
Free memory 3MB
Free memory until GC 3MB
Free memory until OOME 189MB
Total memory 69MB
Max memory 256MB
Total mutator paused time: 0
Total time waiting for GC to complete: 598.247us

2、时间信息 , 也是我们主要分析的信息 。
----- pid 17845 at 2016-10-19 10:59:32 -----

3、虚拟机信息 , 包括进程的,线程的跟踪信息,这是用来跟踪进程和线程具体点的好地方 。

----- pid 17845 at 2016-10-19 10:59:32 -----
Cmd line: com.XX.XX


DALVIK THREADS (19):
"main" prio=5 tid=1 WaitingForDebuggerSend
  | group="main" sCount=2 dsCount=1 obj=0x87800ed0 self=0x7f85ca6000
  | sysTid=17845 nice=0 cgrp=default sched=0/0 handle=0x7f89a63e30
  | state=S schedstat=( 0 0 0 ) utm=54 stm=14 core=0 HZ=100
  | stack=0x7fc2029000-0x7fc202b000 stackSize=8MB
  | held mutexes=
  kernel: futex_wait_queue_me+0xd4/0x12c
  kernel: futex_wait+0xd8/0x1cc
  kernel: do_futex+0xc8/0x8d0
  kernel: SyS_futex+0xf8/0x174
  kernel: __sys_trace+0x30/0x34
  native: #00 pc 00019d94  /system/lib64/libc.so (syscall+28)
  native: #01 pc 000d7c00  /system/lib64/libart.so (art::ConditionVariable::Wait(art::Thread*)+140)
  native: #02 pc 0032ea88  /system/lib64/libart.so (art::ThreadList::SuspendSelfForDebugger()+268)
  native: #03 pc 002311a4  /system/lib64/libart.so (art::JDWP::JdwpState::SuspendByPolicy(art::JDWP::JdwpSuspendPolicy, unsigned long)+144)
  native: #04 pc 002319d0  /system/lib64/libart.so (art::JDWP::JdwpState::SendRequestAndPossiblySuspend(art::JDWP::ExpandBuf*, art::JDWP::JdwpSuspendPolicy, unsigned long)+632)
  native: #05 pc 00236360  /system/lib64/libart.so (art::JDWP::JdwpState::PostClassPrepare(art::mirror::Class*)+912)
  native: #06 pc 0011c218  /system/lib64/libart.so (art::ClassLinker::DefineClass(art::Thread*, char const*, unsigned long, art::Handle<art::mirror::ClassLoader>, art::DexFile const&, art::DexFile::ClassDef const&) (.part.430)+1000)
  native: #07 pc 0011c8dc  /system/lib64/libart.so (art::ClassLinker::FindClassInPathClassLoader(art::ScopedObjectAccessAlreadyRunnable&, art::Thread*, char const*, unsigned long, art::Handle<art::mirror::ClassLoader>)+748)
  native: #08 pc 0011ce4c  /system/lib64/libart.so (art::ClassLinker::FindClass(art::Thread*, char const*, art::Handle<art::mirror::ClassLoader>) (.part.431)+884)
  native: #09 pc 00121180  /system/lib64/libart.so (art::ClassLinker::ResolveType(art::DexFile const&, unsigned short, art::Handle<art::mirror::DexCache>, art::Handle<art::mirror::ClassLoader>)+244)
  native: #10 pc 003a7148  /system/lib64/libart.so (artInitializeTypeFromCode+348)
  native: #11 pc 000c9a6c  /system/lib64/libart.so (art_quick_initialize_type+60)
  native: #12 pc 002e5560  /data/dalvik-cache/arm64/data@app@com.XX.XX-2@base.apk@classes.dex (Java_com_tima_www_driver_stationquery_StationQueryActivity_000241_onItemClick__Landroid_widget_AdapterView_2Landroid_view_View_2IJ+196)
  at com.XX.XX.stationquery.StationQueryActivity$1.onItemClick(StationQueryActivity.java:73)
  at android.widget.AdapterView.performItemClick(AdapterView.java:339)
  at android.widget.AbsListView.performItemClick(AbsListView.java:1544)
  at android.widget.AbsListView$PerformClick.run(AbsListView.java:3721)
  at android.widget.AbsListView$3.run(AbsListView.java:5660)
  at android.os.Handler.handleCallback(Handler.java:739)
  at android.os.Handler.dispatchMessage(Handler.java:95)
  at android.os.Looper.loop(Looper.java:145)
  at android.app.ActivityThread.main(ActivityThread.java:6837)
  at java.lang.reflect.Method.invoke!(Native method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
从这么多大堆栈里如何分析出关键问题呢:

1,如果是ANR问题 , 则搜索“ANR”关键词 。 快速定位到关键事件信息 。
2,如果是ForceClosed 和其它异常退出信息,则搜索"Fatal" 关键词, 快速定位到关键事件信息 。
3,定位到关键事件信息后 , 如果信息不够明确的,再去搜索应用程序包的虚拟机信息 ,查看具体的进程和线程跟踪的日志,来定位到代码

该方式适合能够及时拿到终端的情况下进行日志分析,那如果是用户的终端该如何及时分析bug日志呢,采用第二种方法如下

2、应用程序出现问题时采用HTTP的方式将日志及时上报到后台进行分析:

    看了网上大部分是采用日志收集的第三方jar包来完成的,还有一种是自定义一个自己的CrashHandler实现UncaughtExceptionHandler接口来捕获闪退信息然后上传到自己的服务器。这样的实现方式总觉得如果用户误删了闪退的日志文件那么就会导致无法及时上报闪退日志,也就无从分析隐藏的Bug了,于是采用另一种实现思路,即:在程序发生异常时提醒用户发生了什么样的异常,同时把本次捕获的Exception的字段写入到自己定义的log文件中,然后上报异常字段到自己的异常服务器上,从手机端或者后台都可以看到发生的异常堆栈。日志记录系统不借助与任何第三方jar包。说干就干,

项目目录如下:

关键代码如下:

    1、定义IExceptionHandler接口


<pre name="code" class="java">/**
 *异常处理类
 *
 */
 
public interface IExceptionHandler {
    /**
     * 异常处理类.
     * @param t
     * @param db
     */
    public String handlerException(Throwable t, SQLiteDatabase db);
 
    /**
     * 异常处理类.
     * @param t
     */
    public String handlerException(Throwable t);
 
    /**
     * 异常处理类.
     * @param t
     */
    public void sendException(Throwable t);
 
    /**
     * 给用户提示
     * @param errorMsg
     */
    public void showTipMessgae(String errorMsg);
 
}
2、定义ExceptionHandler实现自IExceptionHandler
/**
 * 异常处理类
 */
 
public class ExceptionHandler implements IExceptionHandler{
 
    private Context context;
 
    private ExceptionSender exceptionSender;
 
    public ExceptionHandler(Context context)
    {
        this.context = context;
        exceptionSender = new ExceptionSender(context);
    }
 
    @Override
    public String handlerException(Throwable t) {
        return handlerException(t, null);
    }
 
    @Override
    public String handlerException(Throwable exception, SQLiteDatabase db) {
        String message = "";
        if (exception instanceof ConnectException)
        {
            message = "网络连接有问题!";
        }
        else if (exception instanceof SocketTimeoutException)
        {
            message = "连接超时!";
        }
        else if (exception instanceof NullPointerException)
        {
            message = "程序出错,空指针错误!";
        }
        else if (exception instanceof IllegalArgumentException)
        {
            message = "程序出错,错误的请求参数!";
        }
        else if (exception instanceof ArithmeticException)
        {
            message = "程序出错,数值计算出错!";
        }
        else if (exception instanceof NetworkOnMainThreadException)
        {
            message = "程序出错,网络请求放在主线程中运行!";
        }
        else if (exception instanceof IllegalStateException)
        {
            message = "程序出错,网络请求要放在主线程中运行!";
        }
        else if (exception instanceof IndexOutOfBoundsException)
        {
            message = "数组越界异常!";
        }
        else if (exception instanceof IOException)
        {
            message = "程序出错,IO错误!";
        }
        else
        {
            message = exception.getMessage();
        }
        showTipMessgae(message);
        exceptionSender.send(exception);
        return message;
    }
 
    @Override
    public void sendException(Throwable t) {
        exceptionSender.send(t);
    }
 
    @Override
    public void showTipMessgae(final String msg)
    {
        if (context != null)
        {
            Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
        }
    }
}
3、在AndroidManifest.xml里面配置自己的Application:IApplication,并在里面初始化app日志文件目录

public class IApplication extends Application{
 
    /**
     * 应用根目录
     */
    private File rootFile;
    /**
     * log目录
     */
    private File logFile;
 
    private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
 
    private static IApplication INSTANCE = null;
 
    public static IApplication getInstance()
    {
        return INSTANCE;
    }
 
    private FileLog fileLog;
 
    @Override
    public void onCreate() {
        super.onCreate();
        INSTANCE = this;
        initDataRoot();
        File log = new File(logFile, df.format(new Date()) + ".log");
        PrintWriter pw = null;
        try {
            pw = new PrintWriter(new FileOutputStream(log, true), true);
        } catch (FileNotFoundException e1) {
        }
        fileLog = new FileLog(pw);
        fileLog.setLevel(FileLog.LEVEL_INFO);//设置日志系统的等级
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init(this);
    }
 
    /**
     * 初始化log目录
     */
    private void initDataRoot() {
        String state = android.os.Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            rootFile =new File(Environment.getExternalStorageDirectory(),"11");
            if (!rootFile.exists()) {
                rootFile.mkdirs();
            }
        }else {
            rootFile = getDatabasePath("11");
        }
        logFile = new File(rootFile, "logs");
        if (!logFile.exists()) {
            logFile.mkdirs();
        }
    }
 
    public FileLog getFileLog() {
        return fileLog;
    }
}
4、采用HTTP方式将实时catch的异常发送到异常服务器,并将异常的堆栈信息写入到SDcard中

<pre name="code" class="java">/**
 * 异常发送类
 */
 
public class ExceptionSender {
 
    private Context context;
 
    private String url="你的收集异常信息的服务器的地址";
    //版本名称
    private String app_name;
    //版本号
    private String app_version;
    //设备名称
    private String device_name;
    //操作系统
    private String os_name;
    //操作系统版本号
    private String os_version;
 
    private ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
 
    private FileLog fileLog;
 
    public ExceptionSender(Context context)
    {
        init(context);
        this.context = context;
        this.fileLog = IApplication.getInstance().getFileLog();
    }
 
    private void init(Context context)
    {
        PackageInfo packInfo;
        try
        {
            packInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            if (packInfo != null)
            {
                app_name = packInfo.packageName;
                app_version = packInfo.versionName;
            }
            device_name = android.os.Build.MODEL;
            os_name = "Android";
            os_version = android.os.Build.VERSION.RELEASE;
        }
        catch (PackageManager.NameNotFoundException e)
        {
        }
    }
 
    /**
     * 发送错误信息到错误收集中心.
     *
     * @param ex
     */
    public void send(final Throwable ex)
    {
        fileLog.e(ex.getClass().getSimpleName(), ex);
        cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                try
                {
                    DefaultHttpClient httpClient = new DefaultHttpClient();
                    HttpPost httpPost = new HttpPost(url);
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    PrintStream ps = new PrintStream(out);
                    ex.printStackTrace(ps);
                    ps.flush();
                    String err_msg = new String(out.toByteArray(), "UTF-8");
                    Map<String, String> params = new HashMap<String, String>();
                    List<NameValuePair> parameters = new ArrayList<NameValuePair>();
                    addParameter(params, parameters, "class_name", ex.getClass().getSimpleName());
                    addParameter(params, parameters, "app_name", app_name);
                    addParameter(params, parameters, "app_version", app_version);
                    addParameter(params, parameters, "device_name", device_name);
                    addParameter(params, parameters, "os_name", os_name);
                    addParameter(params, parameters, "os_version", os_version);
                    parameters.add(new BasicNameValuePair("err_msg", err_msg));
                    parameters.add(new BasicNameValuePair("stack_msg", ""));
                    addParameter(params, parameters, "error_time", Long.toString(System.currentTimeMillis()));
                    httpPost.setEntity(new UrlEncodedFormEntity(parameters, HTTP.UTF_8));
                    httpClient.execute(httpPost);
                }
                catch (Throwable e)
                {
                }
            }
        });
    }
 
    private void addParameter(Map<String, String> params, List<NameValuePair> parameters, String key, String value)
    {
        value = value == null ? "" : value;
        params.put(key, value);
        parameters.add(new BasicNameValuePair(key, value));
    }
}


4、使用方式:在程序里面采用try catch捕获可能会出现的异常的代码块(界面给出提示,程序不闪退),另一种是没有进行try catch应用程序直接闪退


<pre name="code" class="java">public class MainActivity extends AppCompatActivity implements View.OnClickListener{
 
    private IExceptionHandler exceptionHandler;
 
    private Button btn1, btn2;
 
    private Student student;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        exceptionHandler = new ExceptionHandler(getApplicationContext());
        btn1 = (Button)findViewById(R.id.button1);
        btn1.setOnClickListener(this);
        btn2 = (Button)findViewById(R.id.button2);
        btn2.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button1:
                try{//捕获了异常,异常的堆栈信息写入了SDcard中同时也上报到了后台
                    int m = 1/0;
                    System.out.print(m);
                }catch (Exception e){
                    exceptionHandler..handlerException(e);
                }
                break;
            case R.id.button2://没有捕获异常,应用程序直接退了,异常的堆栈信息写入了SDcard中同时也上报到了后台
                System.out.print(student.getId());
                break;
        }
    }
}


这样友好的提示也给了用户,异常信息也及时上报了。但是该方式也有自己的缺点:大量的try catch会导致代码的效率不高。
详细代码点击下载
--------------------- 
作者:Ztw2017 
来源:CSDN 
原文:https://blog.csdn.net/zhangteng22/article/details/52947992 
版权声明:本文为博主原创文章,转载请附上博文链接!

本文转载自:https://blog.csdn.net/zhangteng22/article/details/52947992

shzwork
粉丝 15
博文 825
码字总数 11168
作品 0
厦门
私信 提问
(转)Android平台的崩溃捕获机制及实现

作者简介:贾志凯,Testin崩溃分析(http://apm.testin.cn/)项目研发工程师,负责客户端SDK相关技术研发工作。5年移动互联网开发经验,曾任职于中科院、Symantec、Opera,对移动App的测试、分...

so1per
2016/01/18
4.1K
0
Android App中多进程问题:捕获异常信息并重启应用

App crash原因以及解决办法- https://blog.csdn.net/yangtuxiaojie/article/details/47123243 Android在程序崩溃或者捕获异常之后重新启动app- https://www.cnblogs.com/dingxiansen/p/8628......

desaco
2018/11/21
0
0
每日一问:context.startActivity() 不设置 FLAG 一定会崩溃么

坚持原创日更,短平快的 Android 进阶系列,敬请直接在微信公众号搜索:nanchen,直接关注并设为星标,精彩不容错过。 想必大多数人都知道在我们使用非 的 的时候,都需要指定 ,如果没有指定...

nanchen2251
07/15
0
0
Android开源中国客户端学习 异常处理模块

不得不说Osc的客户端的一些模块和商业系统很接近,这也是值得我们学习的地方。 osc的异常处理模块就是这样的一个模块,虽然个人认为功能有些耦合,但是不得不说还是很有参考价值的。 OSC的异...

SuShine
2013/09/09
1K
7
Android开发高手课之崩溃优化

《Android开发高手课》是极客时间上为数不多的质量高的课程,通过学习确实让我开拓了眼界,之前对于Android的优化可能仅仅停留在基础的阶段,通过对这个课程的学习,确实了解了更多的监测手段...

小菜鸟程序媛
02/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Android OkHttp + Retrofit 取消请求的方法

本文链接 前言 在某一个界面,用户发起了一个网络请求,因为某种原因用户在网络请求完成前离开了当前界面,比较好的做法是取消这个网络请求。对于OkHttp来说,具体是调用Call的cancel方法。 ...

shzwork
37分钟前
6
0
并发编程之Callable异步,Future模式

Callable 在Java中,创建线程一般有两种方式,一种是继承Thread类,一种是实现Runnable接口。然而,这两种方式的缺点是在线程任务执行结束后,无法获取执行结果。我们一般只能采用共享变量或...

codeobj
今天
6
0
Ubuntu环境下安装PaddlePaddle

开篇 深度学习技术是目前非常热门的技术,笔者在闲暇之余决定学习一下这门技术,入门选择了百度开源的PaddlePaddle框架。 paddlepaddle介绍 飞桨(PaddlePaddle) 是国际领先的端到端开源深度学...

豫华商
今天
6
0
LeetCode 第 287 号问题:寻找重复数,一道非常简单的数组遍历题,加上四个条件后感觉无从下手

今天分享的题目来源于 LeetCode 第 287 号问题:寻找重复数。 题目描述 给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只...

五分钟学算法
今天
6
0
vuex mapActions

本文转载于:专业的前端网站➧vuex mapActions 在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在...

前端老手
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部