DeadObjectException解题

本文详细探讨了DeadObjectException是否可以被try-catch以及其意义。解释了Exception和Error的区别,特别是OutOfMemoryError的处理。文章分析了JVM内存模型,指出不同内存区域中哪些不会发生OOM。此外,还讨论了ServiceThread中的DeadObjectException,强调在特定场景下捕获异常的无效性,并分析了Activity.onWindowFocusChanged的调用过程和可能导致错误的原因。
摘要由CSDN通过智能技术生成

DeadObjectException解题

RemoteException occurs on reporting focusChanged, w=Window{2470935 u0 bundle_id/bundle_id.MainActivity}
    android.os.DeadObjectException
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(Binder.java:1143)
        at android.view.IWindow$Stub$Proxy.windowFocusChanged(IWindow.java:500)
        at com.android.server.wm.WindowState.reportFocusChangedSerialized(WindowState.java:3879)
        at com.android.server.wm.WindowManagerService$H.handleMessage(WindowManagerService.java:5426)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:214)
        at android.os.HandlerThread.run(HandlerThread.java:65)
        at com.android.server.ServiceThread.run(ServiceThread.java:44)

Question 1: 能否try-catch住?

想要搞清楚这个问题,我们需要对try-catch有个基础的了解。用法上面的东西,不在这里的讨论范围之内。

我们经常面临一个面试的问题。 OutOfMemoryError 可以被 try catch 吗? 对了,我不反感面试,因为面试会暴露我们知识点上的很多问题。所以,我们由这个问题来展开。

想要搞清楚这个问题。我们就要知道一些基础理论

  • 什么可以被try-catch?

针对上面的面试题,我们就有了一下的问题。

  • OutOfMemoryError 可以被 try-catch 吗?
  • 捕获OutOfMemoryError有什么意义?
  • JVM中哪一块内存不会发生OOM

回归到主题,针对DeadObjectException,我们就有了这个问题。

  • 当前这个Exception,可以被catch住么?有用么?

Question 1.1: 什么可以被try-catch

我们可以知道,Android的可抛出类(即Throwable),可以有两个子类, 异常Exception和错误Error。而所有的Thrwoable都可以被捕捉。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tUzs8BWS-1670401244216)(https://picx.zhimg.com/80/v2-712ccd82f388ea55eba4e8958984f479_1440w.webp)]

Exception

**异常Exception:一般指可以或应该捕获和处理的异常。**它的两个直接子类IOExceptionRuntimeException及其子类都是我们在代码中经常遇到的一些错误。AWTException表示,抽象的窗口工具发生了异常,该Exception,在java.awt中抛出。java.awtJAVA提供的用于创建界面以及图形绘制的软件包。而在Android开发中,我们基本不会遇到该Exception

RuntimeException是在程序运行中可能发生的异常,我们可以不捕获它,但可能带来Crash的代价,但是过多的捕获异常又不利于暴露和调试异常情况。在开发过程中,我们更多的应该及时暴露问题。

运行时异常和受检异常

运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOExceptionSQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

常见异常中英对照
异常类型 具体显示
算数异常类 ArithmeticExecption
空指针异常类型 NullPointerException
类型强制转换类型 ClassCastException
数组负下标异常 NegativeArrayException
数组下标越界异常 ArrayIndexOutOfBoundsException
文件已结束异常 EOFException
文件未找到异常 FileNotFoundException
字符串转换为数字异常 NumberFormatException
操作数据库异常 SQLException
输入输出异常 IOException
方法未找到异常 NoSuchMethodException
下标越界异常 IndexOutOfBoundsExecption
系统异常 SystemException
创建一个大小为负数的数组错误异常 NegativeArraySizeException
数据格式异常 NumberFormatException
安全异常 SecurityException
不支持的操作异常 UnsupportedOperationException
Error

错误 Error :一般指非正常状态的,比较严重的,不应该被捕获的系统错误。 我们常见的Error就是OOMStackOverflowError

Question 1.2: OutOfMemoryError 可以被 try catch 吗?

根据上面所得到的信息。OOM其实是可以被捕捉的。

Question 1.3: 捕获 OutOfMemoryError 有什么意义?

一般情况下,捕获OutOfMemoryError并没有什么太大意义,在开发中也几乎没有写过这种代码。

如果把捕获OOM当做处理OOM的一种手段,无疑是不合适的。因为无法保证catch的代码就是导致OOM的原因,可能它只是压死骆驼的最后一根稻草,甚至也无法保证catch代码块中不会再次触发OOM

Android源码中有这样的操作。在View.javabuildDrawingCacheImpl()方法中有这么一段代码.

try {
   
    bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
                        width, height, quality);
    bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
    if (autoScale) {
   
        mDrawingCache = bitmap;
     } else {
   
         mUnscaledDrawingCache = bitmap;
     }
     if (opaque && use32BitCache) bitmap.setHasAlpha(false);
} catch (OutOfMemoryError e) {
   
    // If there is not enough memory to create the bitmap cache, just
    // ignore the issue as bitmap caches are not required to draw the
    // view hierarchy
    if (autoScale) {
   
        mDrawingCache = null;
    } else {
   
        mUnscaledDrawingCache = null;
}
mCachingFailed = true;

buildDrawingCacheImpl()方法的大致作用是为当前View生成一个Bitmap缓存。在构建Bitmap对象的时候,如果捕捉到了OOM ,就放弃生成Bitmap缓存,因为在View的绘制过程中Bitmap Cache并不是必须存在的。所以在这里没有必要抛出OOM,而是自己捕获就可以了。

在你自己明确知道可能发生OOM的情况下设置一个兜底策略,这可能是捕获OOM的唯一意义了.

Question 1.4: JVM中哪一块内存不会发生OOM?

要了解这个问题。需要先了解JVM模型是什么样的。

简单版本如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qRnVfXG7-1670401244217)(https://picx.zhimg.com/80/v2-279cac84feaaca095a49f75f19f76a47_1440w.webp)]

还有一个终极版本:

JVM结构终极版

Java虚拟机栈

每个方法被执行的时候,Java虚拟机栈都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每个方法被调用直到执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果 Java 虚拟机栈支持动态扩展,当栈扩展时无法申请到足够的内存会排抛出OutOfMemoryError异常。

本地方法栈

本地方法栈为虚拟机使用到的Native方法服务。《Java 虚拟机规范》对本地方法栈中方法使用的语言、使用方式和数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它。

Hotspot将本地方法栈和虚拟机栈合二为一。本地方法栈也会在栈深度溢出和栈扩展失败时分别抛出 StackOverflowErrorOutOfMemoryError

Java 堆

所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里 “几乎” 所有的对象实例都在这里分配内存。

在 《Java 虚拟机规范》中对Java堆的描述是:“所有的对象实例以及数组都应当在堆上分配”。Java 堆以处于物理上不连续的内存空间,但在逻辑上它应该被视为连续的。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。

Java 堆既可以被实现成固定大小,也可以是扩展的。如果在Java堆中没有内存完成实例分配,并且堆无法再扩展时Java虚拟机将会抛出OutOfMemoryError

方法区

方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。Hotspot设计之初选择把垃圾收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区而已,使得HotSpot的 GC 能够像管理 Java 堆一样管理这部分内存,但导致 Java 应用更容易遇到内存溢出的问题。

在 JDK 8 中,彻底废弃了永久代的概念。如果方法区无法满足新的内存分配的需求时,将抛出 OutOfMemoryError 。

运行时常量池。

方法区的一部分。Class 文件的常量池表,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后方法方法去的运行时常量池。运行时常量池具有动态性,运行期间也可以将新的常量放入池中,如 String.intern() 。常量池受到方法区的限制,当无法再申请到内存时,会抛出 OutOfMemoryError 。

Question 1.5: 当前这个Exception,可以被catch住么?有用么?

我们从上面的只是可以知道,DeadObjectException 可以被catch住。但是有用么?也是要具体问题具体分析的。如果报在我们自己的代码里,try-catch是有用的。

结论

首先我们要来看,在哪里报错的错

ServiceThread

我在Androidframeworks里面,一个不知名的版本中,找到了这样的一个注释。

/**
 * Special handler thread that we create for system services that require their own loopers.
 */
public class ServiceThread extends HandlerThread

其实这玩意就是,一个给需要有一个Lopper的系统服务用的HandlerThread

所以

这玩意是个系统服务。要通过Binder来给我们的App分发windowFocusChanged的时候报错了。首先,我们肯定无法去底层做try-catch的。而且,我也相信,这一定是做了try-catch的。

下面,我们就要讨论,如果在引发点,来做try-catch有没有用。其实是没用的。因为,对于我们的程序来说,接收到用户的操作,给系统发送请求。引发了页面变化。对我们的程序来说,try-catch只到通过Binder调用远端的接口。这个错误报在里远端的Looper里,对我们来说,已经圆满完成任务了。

所以,try-catch对于这个报错来说,在我们自己代码里是无用的。

Question 2: 这个BUG是怎么引起的?

我们就要看懂这个报错,就要知道,这个BUG是怎么引起的。什么时候会爆这个错。这个BUG是报在了windowFocusChanged过程中。所以,我们要了解Activity.onWindowFocusChanged()的调用流程是怎样的。并且,用法是什么样的。

Activity.onWindowFocusChanged 用法

方法原型如下:

public void onWindowFocusChanged (boolean hasFocus) 

触发onWindowFocusChanged的情况有多种,比如应用前后台来回切换、软键盘弹出或者隐藏、首次进入一个Activity后会在onResume方法之后调用等。

那么,这个方法有什么用处呢?

Activity生命周期中,onStart, onResume, onCreate都不是真正visible的时间点, 真正的visible时间点是onWindowFocusChanged()函数被执行时。从onWindowFocusChanged被执行起,用户可以与应用进行交互了, 而这之前,对用户的操作需要做一点限制,常见的就是测量控件,获取控件的宽高。

如果我们不用onWindowFocusChanged 怎么获取View宽高?

延后调用?监听Layout?

Activity.onWindowFocusChanged 调用过程

我们以ActivityThread.handleResumeActivity()方法作为切入点开始分析。

public final class ActivityThread extends ClientTransactionHandler
 
    //省略其他方法
       ...
    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
   
       
        //省略部分代码
           ...
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        final Activity a = r.activity;
        //省略部分代码
        ...
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
           
        wm.addView(decor, l);
               
        //省略部分代码
          ..
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值