APNG4Android icon indicating copy to clipboard operation
APNG4Android copied to clipboard

我也遇到了ANR,资源应该是没问题的,ANR是偶现的

Open IamPlato opened this issue 10 months ago • 11 comments

"main" prio=5 tid=1 Waiting | group="main" sCount=1 dsCount=0 flags=1 obj=0x71e3f530 self=0xb4000076ecc6c7b0 | sysTid=23604 nice=-10 cgrp=default sched=0/0 handle=0x78735574f8 | state=S schedstat=( 718729182 148051607 813 ) utm=54 stm=17 core=3 HZ=100 | stack=0x7fe8ac6000-0x7fe8ac8000 stackSize=8192KB | held mutexes= at sun.misc.Unsafe.park(Native method)

  • waiting on an unknown object at java.util.concurrent.locks.LockSupport.park(LockSupport.java:190) at com.github.penfeizhou.animation.decode.FrameSeqDecoder.r(FrameSeqDecoder.java:6) at com.github.penfeizhou.animation.FrameAnimationDrawable.getIntrinsicWidth(FrameAnimationDrawable.java:2) at android.widget.ImageView.updateDrawable(ImageView.java:1067) at android.widget.ImageView.setImageDrawable(ImageView.java:594) at androidx.appcompat.widget.AppCompatImageView.setImageDrawable(AppCompatImageView.java:3) at com.tinnove.aispace.customize.CustomizeAvatarFragment.o0(CustomizeAvatarFragment.kt:4) at com.tinnove.aispace.customize.CustomizeAvatarFragment.r0(CustomizeAvatarFragment.kt:29) at com.tinnove.aispace.customize.CustomizeAvatarFragment.d0(CustomizeAvatarFragment.kt:3) at com.tinnove.aispace.customize.CustomizeAvatarFragment.r(CustomizeAvatarFragment.kt:-1) at com.tinnove.aispace.customize.f.onChanged(R8$$SyntheticClass:-1) at com.tinnove.network.livedata.SingleLiveEvent$a.onChanged(SingleLiveEvent.java:2) at androidx.lifecycle.LiveData.considerNotify(LiveData.java:6) at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:8) at androidx.lifecycle.LiveData.setValue(LiveData.java:4) at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:-1) at com.tinnove.network.livedata.SingleLiveEvent.setValue(SingleLiveEvent.java:2) at androidx.lifecycle.LiveData$1.run(LiveData.java:5) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7664) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

IamPlato avatar Mar 11 '25 11:03 IamPlato

我也遇到了相同的问题

wangyonghong avatar Mar 27 '25 02:03 wangyonghong

请问这个库出问题概率大吗?最近正在考虑要不要引入啊

wustwg avatar Jul 15 '25 07:07 wustwg

我也遇到了相同的问题

我把代码中 LockSupport.park 改成 LockSupport.parkUntil 了,暂时规避了 ANR,但感觉不是很好的解法

wangyonghong avatar Jul 15 '25 07:07 wangyonghong

LockSupport

请问你用多久这个库了?线上使用了吗?

wustwg avatar Jul 15 '25 07:07 wustwg

这库也太不稳定了吧 我今天测试就发现问题了,直接原因应该是这里 不直到什么导致它溢出了

Image 然后就一直卡在这个逻辑看起来像是 死循环了。

堆栈 suspend all histogram: Sum: 594us 99% C.I. 0.105us-42us Avg: 10.241us Max: 42us DALVIK THREADS (42): "FrameDecoderExecutor-0" prio=5 tid=35 Runnable | group="main" sCount=0 ucsCount=0 flags=0 obj=0x13d80548 self=0xb400006f582bac00 | sysTid=7191 nice=0 cgrp=default sched=0/0 handle=0x6f5a09fcb0 | state=R schedstat=( 60726613375 136867018 7115 ) utm=5290 stm=782 core=7 HZ=100 | stack=0x6f59f9c000-0x6f59f9e000 stackSize=1039KB | held mutexes= "mutator lock"(shared held) at java.lang.ThreadLocal$ThreadLocalMap.-$$Nest$mgetEntry(unavailable:0) at java.lang.ThreadLocal.get(ThreadLocal.java:163) at dalvik.system.BlockGuard.getThreadPolicy(BlockGuard.java:241) at java.io.FileInputStream.skip(FileInputStream.java:359) at java.io.FilterInputStream.skip(FilterInputStream.java:150) at com.github.penfeizhou.animation.io.StreamReader.skip(StreamReader.java:48) at com.github.penfeizhou.animation.io.FilterReader.skip(FilterReader.java:20) at com.github.penfeizhou.animation.io.FilterReader.skip(FilterReader.java:20) at com.github.penfeizhou.animation.gif.decode.ImageDescriptor.receive(ImageDescriptor.java:145) at com.github.penfeizhou.animation.gif.decode.GifParser.parse(GifParser.java:119) at com.github.penfeizhou.animation.gif.decode.GifDecoder.read(GifDecoder.java:77) at com.github.penfeizhou.animation.gif.decode.GifDecoder.read(GifDecoder.java:27) at com.github.penfeizhou.animation.decode.FrameSeqDecoder.innerStart(FrameSeqDecoder.java:341) at com.github.penfeizhou.animation.decode.FrameSeqDecoder.access$1200(FrameSeqDecoder.java:36) at com.github.penfeizhou.animation.decode.FrameSeqDecoder$6.run(FrameSeqDecoder.java:321) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:210) at android.os.Looper.loop(Looper.java:299) at android.os.HandlerThread.run(HandlerThread.java:67)

wustwg avatar Jul 15 '25 10:07 wustwg

贴一下图片资源

jingpeng avatar Jul 15 '25 11:07 jingpeng

贴一下图片资源

我这已经解决了 和资源没啥关系,是我这两个线程同时下载两个同样的 url,结果导致读写异常了。 不过正常情况下,即时读写异常也要报错 ,不应该死循环

我感觉是 com.github.penfeizhou.animation.io.StreamReader#peek 这里面的问题 我改成这样了 public byte peek() throws IOException { int ret1 = read(); if (ret1 == -1) { throw new IOException("peek failed. pos:" + Integer.toHexString(position)); } byte ret = (byte) ret1; position++; if (position < 0) { throw new IOException("position < 0 " + position); } return ret; } 原来是这样 public byte peek() throws IOException { byte ret = (byte) read(); position++; return ret; } 如果 read 返回-1,原来的逻辑也会返回 ff ,这就导致 外部 ImageDescriptor

@Override public void receive(GifReader reader) throws IOException { this.frameX = reader.readUInt16(); this.frameY = reader.readUInt16(); this.frameWidth = reader.readUInt16(); this.frameHeight = reader.readUInt16(); this.flag = reader.peek(); if (localColorTableFlag()) { this.localColorTable = new ColorTable(localColorTableSize()); this.localColorTable.receive(reader); } this.lzwMinimumCodeSize = reader.peek() & 0xff; imageDataOffset = reader.position(); byte blockSize; while ((blockSize = reader.peek()) != 0x0) { reader.skip(blockSize & 0xff); } }

这个逻辑一直在skip ,最终导致 position 溢出了,代码中也没有溢出的检查,然后就一直死循环,用户UI看到的就是 ANR,主进程无响应

wustwg avatar Jul 16 '25 07:07 wustwg

ChatGPT translation: ⸻

I’ve already resolved it. It wasn’t really related to the resource itself — the issue was that two threads were downloading the same URL at the same time, which led to read/write exceptions.

However, under normal circumstances, even if a read/write exception occurs, an error should be thrown, not enter an infinite loop.

I believe the problem lies in com.github.penfeizhou.animation.io.StreamReader#peek. I changed it to this:

public byte peek() throws IOException {
    int ret1 = read();
    if (ret1 == -1) {
        throw new IOException("peek failed. pos:" + Integer.toHexString(position));
    }
    byte ret = (byte) ret1;
    position++;
    if (position < 0) {
        throw new IOException("position < 0 " + position);
    }
    return ret;
}

The original was:

public byte peek() throws IOException {
    byte ret = (byte) read();
    position++;
    return ret;
}

If read() returns -1, the original logic would still return 0xFF, which caused the external ImageDescriptor logic:

@Override
public void receive(GifReader reader) throws IOException {
    this.frameX = reader.readUInt16();
    this.frameY = reader.readUInt16();
    this.frameWidth = reader.readUInt16();
    this.frameHeight = reader.readUInt16();
    this.flag = reader.peek();

    if (localColorTableFlag()) {
        this.localColorTable = new ColorTable(localColorTableSize());
        this.localColorTable.receive(reader);
    }

    this.lzwMinimumCodeSize = reader.peek() & 0xff;
    imageDataOffset = reader.position();

    byte blockSize;
    while ((blockSize = reader.peek()) != 0x0) {
        reader.skip(blockSize & 0xff);
    }
}

This logic keeps skipping, eventually causing the position to overflow. And since there’s no overflow check in the code, it enters an infinite loop, which leads to an ANR (Application Not Responding) — the main thread becomes unresponsive in the UI.

fabiendem avatar Jul 17 '25 16:07 fabiendem

Hi, we're seeing more and more reports of this ANR on Samsung devices (especially S10). We use a lot of animated WebP. If someone has a fix, please go ahead. I am also curious to know how to replicate this bug. I am using it in a React Native app, APNG4Android is used in expo-image (React Native) to support animated WebP.


Translation via ChatGPT

你好,我们在三星设备上(尤其是 S10)收到越来越多关于这个 ANR 的报告。我们的应用中使用了大量的 WebP 动图。 如果有人已经有修复方案,欢迎提交。 我也很好奇这个问题该如何复现。 我们是在一个 React Native 应用中使用它的,expo-image(React Native)中通过 APNG4Android 来支持 WebP 动图。

fabiendem avatar Jul 17 '25 16:07 fabiendem

The issue was that two of my threads were downloading the same URL at the same time, which caused a read-write exception. 问题出在我有两个线程同时下载同一个 URL,结果导致了读写异常。

Hi, do you know in which circumstances this scenario is possible? When would two threads download the same URL at the same time?? 你好,你知道在什么情况下会出现这种情况吗?什么时候会有两个线程同时下载同一个 URL 呢?

fabiendem avatar Jul 18 '25 13:07 fabiendem

其实这个ANR很好解决,提供一个设置webp宽高的方法即可,大部分都是使用Glide或Coil加载图片,他们是可以在构建WebPDrawable对象前拿到宽高的,也就不需要在FrameSeqDecoder#getBounds方法通过阻塞UI线程去获取,就避免了ANR的发生

对于没有使用Glide或Coil的场景,可以写个类去解析webp宽高,解析完成后再构建WebPDrawable即可,这其实很简单

liujingxing avatar Aug 23 '25 09:08 liujingxing