文件句柄泄漏:Toast.show()引发的RuntimeException: Could not read input channel file descriptors from parce

最近友盟总是收到这样的异常:

java.lang.RuntimeException: Could not read input channel file descriptors from parcel.
    at android.view.InputChannel.nativeReadFromParcel(Native Method)
    at android.view.InputChannel.readFromParcel(InputChannel.java:148)
    at android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:759)
    at android.view.ViewRootImpl.setView(ViewRootImpl.java:531)
    at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:310)
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:86)
    at android.widget.Toast$TN.handleShow(Toast.java:425)
    at android.widget.Toast$TN$1.run(Toast.java:331)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:148)
    at android.app.ActivityThread.main(ActivityThread.java:5417)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

根据经验,单纯的Toast.show()很难引发异常,由于刚开始频次较低没有留意

但是后来发现该异常逐渐频繁起来,于是研究一下

首先看这个异常堆栈,里面没有任何我们自己的代码,很难确定异常代码位置

但是里面可以看到,有调用Toast$TN.handleShow()方法

该方法是应用中调用Toast.show()时,内部进行aidl调用,最终在内部类TN.handleShow()时,向WindowManager添加View

最终执行到nativeReadFromParcel方法:

static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj,
        jobject parcelObj) {
    if (android_view_InputChannel_getNativeInputChannel(env, obj) != NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "This object already has a native input channel.");
        return;
    }
 
    Parcel* parcel = parcelForJavaObject(env, parcelObj);
    if (parcel) {
        bool isInitialized = parcel->readInt32();
        if (isInitialized) {
            String8 name = parcel->readString8();
            int rawFd = parcel->readFileDescriptor();
            int dupFd = dup(rawFd);
            if (dupFd < 0) {
                ALOGE("Error %d dup channel fd %d.", errno, rawFd);//此处还会打印错误的原因
                jniThrowRuntimeException(env,
                        "Could not read input channel file descriptors from parcel.");
                return;
            }//异常就是从这里抛出的。
 
            InputChannel* inputChannel = new InputChannel(name, dupFd);
            NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel);
 
            android_view_InputChannel_setNativeInputChannel(env, obj, nativeInputChannel);
        }
    }
}
 

发现是文件句柄超出了最大数量导致的(安卓软件最大文件句柄是1024)

于是推测异常原因可能是:某处读写文件/打开连接等操作未及时释放资源,导致file descriptors 泄漏,最终在弹出toast时报出该异常

结合之前有用户反映过,在使用我们应用的打印标价签功能时,发生闪退

查看打印模块,发现在向usb设备发送数据时有一段这样的代码:

UsbDeviceConnection usbDeviceConnection = usbManager.openDevice(usbDevice);
if (usbDeviceConnection == null) {
    return false;
} else {
    usbDeviceConnection.claimInterface(usbInterface, true);
    if (usbDeviceConnection.bulkTransfer(UsbEndpoint, command, command.length, 10000) >= 0) {
        return true;
    } else {
        return false;
    }
}

UsbManager.openDevice()内实现:

public UsbDeviceConnection openDevice(UsbDevice device) {
    try {
        String deviceName = device.getDeviceName();
        ParcelFileDescriptor pfd = mService.openDevice(deviceName);
        if (pfd != null) {
            UsbDeviceConnection connection = new UsbDeviceConnection(device);
            boolean result = connection.open(deviceName, pfd);
            pfd.close();
            if (result) {
                return connection;
            }
        }
    } catch (Exception e) {
        Log.e(TAG, "exception in UsbManager.openDevice", e);
    }
    return null;
}

可以看到使用完连接后没有及时关闭,而是直接return,导致连接泄漏

我们假设此时file descriptors 已达上限,那么openDevice()方法不会直接抛出异常,而是内部做了拦截,然后返回null

那么外部得到null之后,继续返回给应用层,应用层会弹出Toast提示用户打印失败

而此时,由于Toast.show()最终也需要打开file descriptors,但此时file descriptors已达上限

所以最终报出异常的位置是Toast,而不是真正引发异常的usb连接泄漏

 

整个引发异常真的是逻辑严密,环环紧扣,安卓源码中捕获了openDevice的异常,导致我们上层应用直接就不晓得具体异常原因

这一点我觉得底层源码中的UsbManager.openDevice()方法写的有点问题,不应该直接内部捕获异常,而是把异常抛出给上层

这样应用层就可以直接接触到该异常进行上报,就不需要绕这么一大圈子才能发现原因

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值