Closed ruanml closed 8 years ago
I put the sample image to GoogleDrive https://drive.google.com/file/d/0B6kFDyLnqV5FXzBieGpoYUxET1k/view?usp=sharing
It's worth trying asBitmap() and check your target's size and scaleType (see #464). An image of this size is bigger than 200MB in memory, even without alpha, so it needs a correct inSampleSize when loading.
@TWiStErRob Thank you for your advice. But not worked. I changed the issue. I using GridView to show these big images. Using Glide in getView().
Which view is the ImageView you're loading into? If you're using wrap_content with large images, it's not going to work well because downsampling doesn't know what size to choose. If you're using fixed size views, you should be ok.
You can try calling override()
with a relatively small width and height and see if that fixes the problem.
@sjudd Sorry. photo_thumb_image is the ImageView loading into. I tried calling override(), but not worked.
And what format of images are these?
@sjudd Format of these images is JPEG
Ok, there's a known issue for .bmp files, but not with JPEGs. I'll try and take a look with the sample image you uploaded. If you have a small sample app that would also be helpful.
@sjudd Thank you, Sam https://github.com/ruanml/SampleMisplaced You can use this sample app. First: make 200 copies of the big image and put them into device Second: delete other images in deivce Third: open this sample app
In your sample app you're using Glide 3.4, have you tried this with 3.6?
yes. i tired it. but not worked.
Just to verify, I have test albums w 20~25 MB DSLR images (around 150 images) and haven't faced any out of memory issues even on low end devices.
hello, eicm.
I will take a video and upload it to YouTube tomorrow. When you start this sample app, the thing you need to do is scroll the screen to the end quickly and immediately. And you can face the gilde break down.
Hi, I just take a video and uploaded it to youtube. https://youtu.be/cTvFquyb6uo I put the sample images(around 250 images) https://drive.google.com/file/d/0B6kFDyLnqV5FXzBieGpoYUxET1k/view?usp=sharing into my phone and delete any others.
When I start sample app, break down.
I'm still not clear: is the using Glide 3.4? Or 3.6?
I tired Glide3.4 and 3.6. Both of them broken down. It crashed using SamsungS4, but when using Nexus6 and Nexus9, it not crashed, but show nothing.
Excuse me, did you solve your problem? I also met
@Dawish Sorry, not yet. I had to use other library to show large size images.
I get this crash when I tried pre-compiled the APK with the test image on my Galaxy S4 4.4.2 with both Glide 3.4 and 3.6.1:
pid: 27316, tid: 27413, name: fifo-pool-threa >>> com.sample.samplemisplaced <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr ffffffff
#00 pc 000221fe /system/lib/libc.so (__memcpy_base+133)
#01 pc 00030b40 /system/lib/libjpeg.so
#02 pc 000159ac /system/lib/libjpeg.so
#03 pc 00014840 /system/lib/libjpeg.so (jpeg_start_decompress+128)
#04 pc 000f20c1 /system/lib/libskia.so (SkJPEGImageDecoder::onDecode(SkStream*, SkBitmap*, SkImageDecoder::Mode)+200)
#05 pc 000ef921 /system/lib/libskia.so (SkImageDecoder::decode(SkStream*, SkBitmap*, SkBitmap::Config, SkImageDecoder::Mode)+36)
#06 pc 0007adf1 /system/lib/libandroid_runtime.so
#07 pc 0007b07f /system/lib/libandroid_runtime.so
#08 pc 00020d4c /system/lib/libdvm.so (dvmPlatformInvoke+112)
#09 pc 000519ef /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+398)
#10 pc 0002a1e0 /system/lib/libdvm.so
#11 pc 00031690 /system/lib/libdvm.so (dvmMterpStd(Thread*)+76)
#12 pc 0002ed28 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+184)
#13 pc 00063e55 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+336)
#14 pc 00063e79 /system/lib/libdvm.so (dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...)+20)
#15 pc 00058b4b /system/lib/libdvm.so
#16 pc 0000d278 /system/lib/libc.so (__thread_entry+72)
#17 pc 0000d410 /system/lib/libc.so (pthread_create+240)
So it looks like a concurrency problem in BitmapFactory.
I played around a little and managed to make a smaller repro (than the full project):
File folder = new File("/storage/extSdCard/DCIM/Camera");
BitmapRequestBuilder<File, Bitmap> base = Glide
.with(context)
.fromFile()
.asBitmap() // cleaner to debug
.diskCacheStrategy(DiskCacheStrategy.NONE) // force reload
.skipMemoryCache(true) // force reload
.listener(new LoggingListener<File, Bitmap>())
.override(100, 100) // behave like an adapter (same size for each item)
;
// 2-3 is enough to crash, but 5 makes it almost every time
base.load(new File(folder, "001.jpg")).preload();
base.load(new File(folder, "002.jpg")).preload();
base.load(new File(folder, "003.jpg")).preload();
base.load(new File(folder, "004.jpg")).preload();
base.load(new File(folder, "005.jpg")).preload();
Here's a log, the number of onException
s varies:
05.605 15643-15715 D/skia: --- decoder->decode returned false
05.635 15643-15715 E/MediaMetadataRetrieverJNI: getFrameAtTime: videoFrame is a NULL pointer
05.695 15643-15643 D/OpenGLRenderer: Enabling debug mode 0
05.775 15643-15643 D/GLIDE: .onException(null, /storage/extSdCard/DCIM/Camera/003.jpg, PreloadTarget@42af1240, true)
05.955 15643-15714 A/libc: Fatal signal 11 (SIGSEGV) at 0xffffffff (code=1), thread 15714 (fifo-pool-threa)
05.985 15643-15715 A/libc: Fatal signal 11 (SIGSEGV) at 0xffffffff (code=1), thread 15715 (fifo-pool-threa)
I went farther and I removed Glide from the picture, here's the Android bug report. @sjudd I guess this is can't/won't fix then? The only workaround I could think of is this:
@Override public void applyOptions(Context context, GlideBuilder builder) {
if (phone is Galaxy S4 && SDK_INT == KITKAT) { // and similarly for others where it fails
// 2 was the first when it didn't crash; 1 surely won't crash, but is extremely slow
builder.setResizeService(new FifoPriorityThreadPoolExecutor(2));
}
}
@ruanml What library are you using now? Is it working well?
@EricInCD I think no library that uses parallel loading will save you as I've proven that the bug is in BitmapFactory
. If you're calling it from multiple threads it may fail, read http://b.android.com/198391. You can continue to use Glide just reduce the number of threads on those devices; though I would be curious to see if it works with other image loaders which use 2 < parallel loaders.
@TWiStErRob I'd suspect that this crash will continue to happen with two threads, it's just less likely. Didn't we implement a locking mechanism for certain devices and versions of Android to prevent a similar set of issues with canvas?
Is this the same as #738 / #758 ?
Yes, it's just less likely, the point of the workaround is to minimize crashes while keeping some performance.
It's similar but there's no Canvas here, just BitmapFactory. Indeed we could do a locking, but there's no clean way of knowing which devices AND the crash highly depends on the set of images.
For example on my S4 the sample image crashes, but no other image in the past two years crashed! So I wouldn't really like to be constrained to 1 thread or serial decode just because there're a few images out there which may crash... too much uncertainty and small likelyhood there.
@EricInCD @TWiStErRob @sjudd I used UIL. The UIL crashed also. But I override ImageDecoder as following:
ImageDecoder imageDecoder = new ImageDecoder() {
@Override
public Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException {
InputStream inputStream = imageDecodingInfo.getDownloader().getStream(imageDecodingInfo.getOriginalImageUri(), imageDecodingInfo.getExtraForDownloader());
final String srcId = imageDecodingInfo.getOriginalImageUri(); // id in database
Bitmap bm = mLruMemoryCache.get(srcId);
if (bm != null) {
return bm;
}
ContentResolver _contentResolver = mContentResolver;
if (_contentResolver == null) {
_contentResolver = Preconditions.checkNotNull(PhotoSelectActivity.this.getApplicationContext()).getContentResolver();
}
bm = MediaStore.Images.Thumbnails.getThumbnail(_contentResolver, Long.decode(imageDecodingInfo.getOriginalImageUri()), MediaStore.Images.Thumbnails.MINI_KIND, mThumbnailOptions);
if (bm == null) {
if (!mErrorImageList.contains(imageDecodingInfo.getOriginalImageUri())) {
mErrorImageList.add(imageDecodingInfo.getOriginalImageUri());
}
bm = BitmapFactory.decodeStream(inputStream);
} else {
mLruMemoryCache.put(srcId, bm);
}
return bm;
}
};
Using MediaStore.Images.Thumbnails.getThumbnail
works well.
To achieve a similar flow in Glide you just need to change the following in ImageAdapter:
Glide.with(mActivity).load(_imageInfo.getmPath())
to
Glide.with(mActivity).loadFromMediaStore(_imageInfo.getmUri())
and it doesn't crash any more, because it starts using MediaStoreThumbFetcher
If you want more control over the load like you did with UIL (except all that cache and error management) you can do the following in Glide v4:
// usage:
Glide.with(mActivity).load(_imageInfo)....
// in GlideModule.registerComponents
registry.prepend(ImageInfo.class, ImageInfo.class, new UnitModelLoader.Factory<ImageInfo>());
registry.prepend(ImageInfo.class, Bitmap.class, new ImageInfoBitmapDecoder(context));
class ImageInfoBitmapDecoder implements ResourceDecoder<ImageInfo, Bitmap> {
private final ContentResolver contentResolver;
private final BitmapPool pool;
public ImageInfoBitmapDecoder(Context context) {
this.contentResolver = context.getContentResolver();
this.pool = Glide.get(context).getBitmapPool();
}
@Override public boolean handles(ImageInfo source, Options options) { return true; }
@Override public @Nullable Resource<Bitmap> decode(ImageInfo source, int width, int height, Options options) {
Bitmap thumb = Thumbnails.getThumbnail(contentResolver, source.getmId(), Thumbnails.MINI_KIND, null);
return BitmapResource.obtain(thumb, pool);
}
}
The same is possible in v3, you just need a lot more boilerplate.
While writing this I also noticed that the media store's thumbnail loader likely uses the FileDescriptor version of the BitmapFactory, so that may worth following up: testing if it fails the same way as decodeFile
does in http://b.android.com/198391. Though it is more likely that it works because the thumbnails are generated sequentially at first access and then served from thumbnail cache.
Thank you for your advice. I'll try it. Actually, I like Glide better than UIL.
Some brief follow up, it looks like the image @TWiStErRob tested was a progressive JPEG? If so, those require a lot amount of memory to decode. It appears as though memory pressure is the cause of the crash, rather than any particular issue with concurrency.
As both Sam (owner) and "msar..." (Android and skia maintainer) agree it's the huge file that is progressively encoded causing the issue I think we cannot really do anything about this in Glide. Progressive images are for the web, not Android; we can count it as a similar limitation that no Bitmap can be drawn that is larger than the max texture size on a particular device. I don't think it's even possible to fail gracefully, because the crash is native that we can't catch: http://b.android.com/198391 is tracking some better error handling ideas, but it'll never be in older versions anyway. I think the best course of action here is that if you know you'll be handling these huge progressive images is to fall back to a single decoder thread in a GlideModule setup; or better yet if you have control over the content either do a server-side resize to a reasonable size or disable progressive encoding. Just as an example: the sample image is huge, but has no quality advantage over a 4 times as small image (16 times area), just look at how blurry/banded/noisy it is.
Hi
Glide Version/Integration library (if any): 3.5.2 or 3.6.0 Device/Android Version: Nexus6 5.1 (not crashed, but show a few of images only) Samsung S4 4.4.4 (crashed) Issue details/Repro steps: when I put 200 images which is 9600 X 7200(size is 7M) into device, and try to show them on ImageView, crashed I put the sample image to GoogleDrive https://drive.google.com/file/d/0B6kFDyLnqV5FXzBieGpoYUxET1k/view?usp=sharing
Glide load line:
Image View:
Stack trace: