Android性能优化之捕获java crash示例解析

目录
  • 背景
  • java层crash由来
  • 为什么java层异常会导致crash
  • 捕获crash
  • 总结

背景

crash一直是影响app稳定性的大头,同时在随着项目逐渐迭代,复杂性越来越提高的同时,由于主观或者客观的的原因,都会造成意想不到的crash出现。同样的,在android的历史化过程中,就算是android系统本身,在迭代中也会存在着隐含的crash。我们常说的crash包括java层(虚拟机层)crash与native层crash,本期我们着重讲一下java层的crash。

java层crash由来

虽然说我们在开发过程中会遇到各种各样的crash,但是这个crash是如果产生的呢?我们来探讨一下一个crash是如何诞生的!

我们很容易就知道,在java中main函数是程序的开始(其实还有前置步骤),我们开发中,虽然android系统把应用的主线程创建封装在了自己的系统中,但是无论怎么封装,一个java层的线程无论再怎么强大,背后肯定是绑定了一个操作系统级别的线程,才真正得与驱动,也就是说,我们平常说的java线程,它其实是被操作系统真正的Thread的一个使用体罢了,java层的多个thread,可能会只对应着native层的一个Thread(便于区分,这里thread统一只java层的线程,Thread指的是native层的Thread。其实native的Thread也不是真正的线程,只是操作系统提供的一个api罢了,但是我们这里先简单这样定义,假设了native的线程与操作系统线程为同一个东西)

每一个java层的thread调用start方法,就会来到native层Thread的世界

public synchronized void start() {
        throw new IllegalThreadStateException();
    group.add(this);
    started = false;
    try {
        nativeCreate(this, stackSize, daemon);
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

最终调用的是一个jni方法

private native static void nativeCreate(Thread t, long stackSize, boolean daemon);

而nativeCreate最终在native层的实现是

static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size,
jboolean daemon) {
    // There are sections in the zygote that forbid thread creation.
    Runtime* runtime = Runtime::Current();
    if (runtime->IsZygote() && runtime->IsZygoteNoThreadSection()) {
        jclass internal_error = env->FindClass("java/lang/InternalError");
        CHECK(internal_error != nullptr);
        env->ThrowNew(internal_error, "Cannot create threads in zygote");
        return;
    }
     // 这里就是真正的创建线程方法
    Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE);
}

CreateNativeThread 经过了一系列的校验动作,终于到了真正创建线程的地方了,最终在CreateNativeThread方法中,通过了pthread_create创建了一个真正的Thread

Thread::CreateNativeThread 方法中
...
pthread_create_result = pthread_create(&new_pthread,
&attr,
Thread::CreateCallback,
child_thread);
CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");
if (pthread_create_result == 0) {
    // pthread_create started the new thread. The child is now responsible for managing the
    // JNIEnvExt we created.
    // Note: we can't check for tmp_jni_env == nullptr, as that would require synchronization
    //       between the threads.
    child_jni_env_ext.release();  // NOLINT pthreads API.
    return;
}
...

到这里我们就能够明白,一个java层的thread其实真正绑定的,是一个native层的Thread,有了这个知识,我们就可以回到我们的crash主题了,当发生异常的时候(即检测到一些操作不符合虚拟机规定时),注意,这个时候还是在虚拟机的控制范围之内,就可以直接调用

void Thread::ThrowNewException(const char* exception_class_descriptor,
const char* msg) {
    // Callers should either clear or call ThrowNewWrappedException.
    AssertNoPendingExceptionForNewException(msg);
    ThrowNewWrappedException(exception_class_descriptor, msg);
}

进行对exception的抛出,我们目前所有的java层crash都是如此,因为对crash的识别还属于本虚拟机所在的进程的范畴(native crash 虚拟机就没办法直接识别),比如我们常见的各种crash

然后就会调用到Thread::ThrowNewWrappedException 方法,在这个方法里面再次调用到Thread::SetException方法,成功的把当次引发异常的信息记录下来

void Thread::SetException(ObjPtr<mirror::Throwable> new_exception) {
    CHECK(new_exception != nullptr);
    // TODO: DCHECK(!IsExceptionPending());
    tlsPtr_.exception = new_exception.Ptr();
}

此时,此时就会调用Thread的Destroy方法,这个时候,线程就会在里面判断,本次的异常该怎么去处理

void Thread::Destroy() {
   ...
    if (tlsPtr_.opeer != nullptr) {
        ScopedObjectAccess soa(self);
        // We may need to call user-supplied managed code, do this before final clean-up.
        HandleUncaughtExceptions(soa);
        RemoveFromThreadGroup(soa);
        Runtime* runtime = Runtime::Current();
        if (runtime != nullptr) {
                runtime->GetRuntimeCallbacks()->ThreadDeath(self);
        }

HandleUncaughtExceptions 这个方式就是处理的函数,我们继续看一下这个异常处理函数

void Thread::HandleUncaughtExceptions(ScopedObjectAccessAlreadyRunnable& soa) {
    if (!IsExceptionPending()) {
        return;
    }
    ScopedLocalRef<jobject> peer(tlsPtr_.jni_env, soa.AddLocalReference<jobject>(tlsPtr_.opeer));
    ScopedThreadStateChange tsc(this, ThreadState::kNative);
    // Get and clear the exception.
    ScopedLocalRef<jthrowable> exception(tlsPtr_.jni_env, tlsPtr_.jni_env->ExceptionOccurred());
    tlsPtr_.jni_env->ExceptionClear();
    // Call the Thread instance's dispatchUncaughtException(Throwable)
    // 关键点就在此,回到java层
    tlsPtr_.jni_env->CallVoidMethod(peer.get(),
    WellKnownClasses::java_lang_Thread_dispatchUncaughtException,
    exception.get());
    // If the dispatchUncaughtException threw, clear that exception too.
    tlsPtr_.jni_env->ExceptionClear();
}

到这里,我们就接近尾声了,可以看到我们的处理函数最终通过jni,再次回到了java层的世界,而这个连接的java层函数就是dispatchUncaughtException(java_lang_Thread_dispatchUncaughtException)

public final void dispatchUncaughtException(Throwable e) {
    // BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
    Thread.UncaughtExceptionHandler initialUeh =
            Thread.getUncaughtExceptionPreHandler();
    if (initialUeh != null) {
        try {
            initialUeh.uncaughtException(this, e);
        } catch (RuntimeException | Error ignored) {
            // Throwables thrown by the initial handler are ignored
        }
    }
    // END Android-added: uncaughtExceptionPreHandler for use by platform.
    getUncaughtExceptionHandler().uncaughtException(this, e);
}

到这里,我们就彻底了解到了一个java层异常的产生过程!

为什么java层异常会导致crash

从上面我们文章我们能够看到,一个异常是怎么产生的,可能细心的读者会了解到,笔者一直在用异常这个词,而不是crash,因为异常发生了,crash是不一定产生的!我们可以看到dispatchUncaughtException方法最终会尝试着调用UncaughtExceptionHandler去处理本次异常,好家伙!那么UncaughtExceptionHandler是在什么时候设置的?其实就是在Init中,由系统提前设置好的!frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

protected static final void commonInit() {
    if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
    /*
     * set handlers; these apply to all threads in the VM. Apps can replace
     * the default handler, but not the pre handler.
     */
    LoggingHandler loggingHandler = new LoggingHandler();
    RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
    Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
    /*
     * Install a time zone supplier that uses the Android persistent time zone system property.
     */
    RuntimeHooks.setTimeZoneIdSupplier(() -> SystemProperties.get("persist.sys.timezone"));
    LogManager.getLogManager().reset();
    new AndroidConfig();
    /*
     * Sets the default HTTP User-Agent used by HttpURLConnection.
     */
    String userAgent = getDefaultUserAgent();
    System.setProperty("http.agent", userAgent);
    /*
     * Wire socket tagging to traffic stats.
     */
    TrafficStats.attachSocketTagger();
    initialized = true;
}

好家伙,原来是KillApplicationHandler“捣蛋”,在异常到来时,就会通过KillApplicationHandler去处理,而这里的处理就是,杀死app!!

private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
    private final LoggingHandler mLoggingHandler;
    public KillApplicationHandler(LoggingHandler loggingHandler) {
        this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
    }
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        try {
            ensureLogging(t, e);
            // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
            if (mCrashing) return;
            mCrashing = true;
            if (ActivityThread.currentActivityThread() != null) {
                ActivityThread.currentActivityThread().stopProfiling();
            }
            // Bring up crash dialog, wait for it to be dismissed
            ActivityManager.getService().handleApplicationCrash(
                    mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
        } catch (Throwable t2) {
            if (t2 instanceof DeadObjectException) {
                // System process is dead; ignore
            } else {
                try {
                    Clog_e(TAG, "Error reporting crash", t2);
                } catch (Throwable t3) {
                    // Even Clog_e() fails!  Oh well.
                }
            }
        } finally {
            // Try everything to make sure this process goes away.
            Process.killProcess(Process.myPid());
            System.exit(10);
        }
    }
    private void ensureLogging(Thread t, Throwable e) {
        if (!mLoggingHandler.mTriggered) {
            try {
                mLoggingHandler.uncaughtException(t, e);
            } catch (Throwable loggingThrowable) {
                // Ignored.
            }
        }
    }
}

看到了吗!异常的产生导致的crash,真正的源头就是在此了!

捕获crash

通过对前文的阅读,我们了解到了crash的源头就是KillApplicationHandler,因为它默认处理就是杀死app,此时我们也注意到,它是继承于UncaughtExceptionHandler的。当然,有异常及时抛出解决,是一件好事,但是我们也可能有一些异常,比如android系统sdk的问题,或者其他没那么重要的异常,直接崩溃app,这个处理就不是那么好了。但是不要紧,java虚拟机开发者也肯定注意到了这点,所以提供

Thread.java
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)

方式,导入一个我们自定义的实现了UncaughtExceptionHandler接口的类

public interface UncaughtExceptionHandler {
    /**
     * Method invoked when the given thread terminates due to the
     * given uncaught exception.
     * <p>Any exception thrown by this method will be ignored by the
     * Java Virtual Machine.
     * @param t the thread
     * @param e the exception
     */
    void uncaughtException(Thread t, Throwable e);
}

此时我们只需要写一个类,模仿KillApplicationHandler一样,就能写出一个自己的异常处理类,去处理我们程序中的异常(或者Android系统中特定版本的异常)。例子demo比如

class MyExceptionHandler:Thread.UncaughtExceptionHandler {
    override fun uncaughtException(t: Thread, e: Throwable) {
        // 做自己的逻辑
        Log.i("hello",e.toString())
    }
}

总结

到这里,我们能够了解到了一个java crash是怎么产生的了,同时我们也了解到了常用的UncaughtExceptionHandler为什么可以拦截一些我们不希望产生crash的异常,在接下来的android性能优化系列中,会持续带来相关的其他分享,感谢观看

更多关于Android捕获java crash的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android广播事件流程与广播ANR原理深入刨析

    目录 序言 一.基本流程和概念 二.无序广播流程 注册广播接收者流程 广播通知流程 三.有序广播流程 四.广播ANR流程 五.总结 六.扩展问题 序言 本想写广播流程中ANR是如何触发的,但是如果想讲清楚ANR的流程,就不得不提整个广播事件的流程,所以就把两块内容合并在一起讲了. 本文会讲内容如下: 1.动态注册广播的整个分发流程,从广播发出,一直到广播注册者接收. 2.广播类型ANR的判断流程和原理. PS:本文基于android13的源码进行讲解. 一.基本流程和概念 动态广播的流程其实是很

  • Android app会crash的原因及解决方法

    android main入口的commonInit()方法内处,有这么一句话, Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler)); 如果没有这句话,app就不会crash.不信,你往里面看, public KillApplicationHandler(LoggingHandler loggingHandler) { @Override public void uncaught

  • Android性能优化之ANR问题定位分析

    目录 前言 1 ANR原因总结 1.1 KeyDispatchTimeout 1.2 BroadCastTimeout 1.3 ServiceTimeout 1.4 ContentProviderTimeout 2 ANR问题解决 2.1 线下问题解决 2.2 线上问题解决 2.2.1 Bugly 2.2.2 FileObserver 2.2.3 WatchDog 前言 ANR(Application Not Response)应用程序未响应,当主线程被阻塞时,就会弹出如下弹窗 要么关闭当前ap

  • 解析Android ANR问题

    一.ANR介绍 ANR 由消息处理机制保证,Android 在系统层实现了一套精密的机制来发现 ANR,核心原理是消息调度和超时处理.ANR 机制主体实现在系统层,所有与 ANR 相关的消息,都会经过系统进程system_server调度,具体是ActivityManagerService服务,然后派发到应用进程完成对消息的实际处理,同时,系统进程设计了不同的超时限制来跟踪消息的处理. 一旦应用程序处理消息不当,超时限制就起作用了,它收集一些系统状态,譬如 CPU/IO 使用情况.进程函数调用栈

  • Android Crash与ANR详细介绍

    目录 Crash 空指针 角标越界 集合元素删除操作 异步操作后对界面元素的处理 Intent传递数据过大 在子线程中操作UI ANR Crash Crash是指程序闪退,导致APP不能正常使用.Crash产生的原因有很多,下面只是列举了一些常见原因. 空指针 空指针应该是项目中最容易产生crash的情况了,举个例子,我们获取某个对象的属性或方法时,这个对象为Null时,如何没有判空,则会出现空指针异常NullPointException,所以这就要求使用对象的时候进行非空判断,在这点,我觉得k

  • 浅谈Android ANR的信息收集过程

    目录 一. ANR场景 二. appNotResponding处理流程 三. 总结 一. ANR场景 无论是四大组件或者进程等只要发生ANR,最终都会调用AMS.appNotResponding()方法,下面从这个方法说起. 以下场景都会触发调用AMS.appNotResponding方法: Service Timeout:比如前台服务在20s内未执行完成: BroadcastQueue Timeout:比如前台广播在10s内未执行完成 InputDispatching Timeout: 输入事

  • 深入学习Android ANR 的原理分析及解决办法

    目录 一.ANR说明和原因 1.1 简介 1.2 原因 1.3 避免 二.ANR分析办法 2.1 ANR重现 2.2 ANR分析办法一:Log 2.3 ANR分析办法二:traces.txt 2.4 ANR分析办法三:Java线程调用分析 2.5 ANR分析办法四:DDMS分析ANR问题 三.造成ANR的原因及解决办法 四.ANR源码分析 4.1 Service造成的Service Timeout 4.2 BroadcastReceiver造成的BroadcastQueue Timeout 4.

  • Android Java crash 处理流程详解

    目录 一.背景 二.App端Crash注册 2.1 commonInit() 2.2 KillApplicationHandler 类 2.2.1 ensureLogging() 2.2.2 ApplicationErrorReport 三.AMS端处理崩溃逻辑 3.1 AMS.handleApplicationCrash 3.1.1 AMS.handleApplicationCrashInner() 3.2 addErrorToDropBox() 3.3 AppErrors.crashAppl

  • Android 应用Crash 后自动重启的方法小结

    前提 首先,我们肯定要在Application里面注册一个CrashHandler,监听应用crash public class TestApplication extends MultiDexApplication { private static TestApplication mInstance; @Override public void onCreate() { super.onCreate(); Thread.setDefaultUncaughtExceptionHandler(ne

  • Android性能优化之捕获java crash示例解析

    目录 背景 java层crash由来 为什么java层异常会导致crash 捕获crash 总结 背景 crash一直是影响app稳定性的大头,同时在随着项目逐渐迭代,复杂性越来越提高的同时,由于主观或者客观的的原因,都会造成意想不到的crash出现.同样的,在android的历史化过程中,就算是android系统本身,在迭代中也会存在着隐含的crash.我们常说的crash包括java层(虚拟机层)crash与native层crash,本期我们着重讲一下java层的crash. java层cr

  • Android性能优化大图治理示例详解

    目录 引言 1 自定义大图View 1.1 准备工作 1.2 图片宽高适配 1.3 BitmapRegionDecoder 2 大图View的手势事件处理 2.1 GestureDetector 2.2 双击放大效果处理 2.3 手指放大效果处理 引言 在实际的Android项目开发中,图片是必不可少的元素,几乎所有的界面都是由图片构成的:像列表页.查看大图页等,都是需要展示图片,而且这两者是有共同点的,列表展示的Item数量多,如果全部加载进来势必会造成OOM,因此列表页通常采用分页加载,加上

  • Android性能优化之图片大小,尺寸压缩综合解决方案

    目录 前言 常见的图片压缩方法 质量压缩 尺寸压缩 libjpeg 图片压缩流程 总结 前言 在Android中我们经常会遇到图片压缩的场景,比如给服务端上传图片,包括个人信息的用户头像,有时候人脸识别也需要捕获图片等等.这种情况下,我们都需要对图片做一定的处理,比如大小,尺寸等的压缩. 常见的图片压缩方法 质量压缩 尺寸压缩 libjpeg 质量压缩 首先我们要介绍一个api--Bitmap.compress() @WorkerThread public boolean compress(Co

  • Android性能优化全局异常处理详情

    目录 前言 1 UncaughtExceptionHandler 1.1 替代Android异常机制 1.2 可选择的异常处理 2 日志上传 2.1 日志收集 2.2 日志存储 3 策略设计模式实现上传功能 前言 异常崩溃,是Android项目中一项比较棘手的问题,即便做了很多的try - catch处理,也不能保证上线不会崩,而且一旦出现崩溃,就会出现下图的弹窗,xx应用停止运行了,这种体验对用户来说是非常差的,因此已经很明显地提示,我们做的app崩溃了. 像现在企业应用,有的在发生崩溃的时候

  • Android 性能优化实现全量编译提速的黑科技

    目录 一.背景描述 二.效果展示 2.1.测试项目介绍 三.思路问题分析与模块搭建: 3.1.思路问题分析 3.2.模块搭建 四.问题解决与实 编译流程启动,需要找到哪一个 module做了修改 module 依赖关系获取 module 依赖关系 project 替换成 aar 技术方案 hook 编译流程 五.一天一个小惊喜( bug 较多) 5.1 output 没有打包出 aar 5.2 发现运行起来后存在多个 jar 包重复问题 5.3 发现 aar/jar 存在多种依赖方式 5.4 发

  • Android性能优化之弱网优化详解

    目录 弱网优化 1.Serializable原理 1.1 分析过程 1.2 Serializable接口 1.3 ObjectOutputStream 1.4 序列化后二进制文件的一点解读 1.5 常见的集合类的序列化问题 1.5.1 HashMap 1.5.2 ArrayList 2.Parcelable 2.1 Parcel的简介 2.2 Parcelable的三大过程介绍(序列化.反序列化.描述) 2.2.1 描述 2.2.2 序列化 2.2.3 反序列化 2.3 Parcelable的实

  • Android性能优化以及数据优化方法

    Android性能优化-布局优化 今天,继续Android性能优化 一 编码细节优化. 编码细节,对于程序的运行效率也是有很多的影响的.今天这篇主题由于技术能力有限,所以也不敢在深层去和大家分享.我将这篇主题分为以下几个小节: (1)缓存 (2)数据 (3)延迟加载和优先加载 1> 缓存 在Android中缓存可以用在很多的地方:对象.IO.网络.DB等等..对象缓存能减少内存分配,IO缓存能对磁盘的读写访问,网络缓存能减少对网络的访问,DB缓存能减少对数据库的操作. 缓存针对的场景在Andro

  • 浅谈Android性能优化之内存优化

    1.Android内存管理机制 1.1 Java内存分配模型 先上一张JVM将内存划分区域的图 程序计数器:存储当前线程执行目标方法执行到第几行. 栈内存:Java栈中存放的是一个个栈帧,每个栈帧对应一个被调用的方法.栈帧包括局部标量表, 操作数栈. 本地方法栈:本地方法栈主要是为执行本地方法服务的.而Java栈是为执行Java方法服务的. 方法区:该区域被线程共享.主要存储每个类的信息(类名,方法信息,字段信息等).静态变量,常量,以及编译器编译后的代码等. 堆:Java中的堆是被线程共享的,

  • 详解Android性能优化之启动优化

    1.为什么要进行启动优化 网上流行一种说法,就是8秒定律,意思是说,如果用户在打开一个页面,在8秒的时间内还没有打开,那么用户大概的会放弃掉,意味着一个用户的流失.从这里就可以看出,启动优化的重要性了. 2.启动的分类 2.1 冷启动 先来看看冷启动的流程图 从图中可以看出,APP启动的过程是:ActivityManagerProxy 通过IPC来调用AMS(ActivityManagerService),AMS通过IPC启动一个APP进程,ApplicationThread通过反射来创建App

随机推荐