我也遇到了ANR,资源应该是没问题的,ANR是偶现的
"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)
我也遇到了相同的问题
请问这个库出问题概率大吗?最近正在考虑要不要引入啊
我也遇到了相同的问题
我把代码中 LockSupport.park 改成 LockSupport.parkUntil 了,暂时规避了 ANR,但感觉不是很好的解法
LockSupport
请问你用多久这个库了?线上使用了吗?
这库也太不稳定了吧 我今天测试就发现问题了,直接原因应该是这里 不直到什么导致它溢出了
然后就一直卡在这个逻辑看起来像是 死循环了。
堆栈 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)
贴一下图片资源
贴一下图片资源
我这已经解决了 和资源没啥关系,是我这两个线程同时下载两个同样的 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,主进程无响应
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.
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 动图。
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 呢?
其实这个ANR很好解决,提供一个设置webp宽高的方法即可,大部分都是使用Glide或Coil加载图片,他们是可以在构建WebPDrawable对象前拿到宽高的,也就不需要在FrameSeqDecoder#getBounds方法通过阻塞UI线程去获取,就避免了ANR的发生
对于没有使用Glide或Coil的场景,可以写个类去解析webp宽高,解析完成后再构建WebPDrawable即可,这其实很简单