bumptech / glide

An image loading and caching library for Android focused on smooth scrolling
https://bumptech.github.io/glide/
Other
34.66k stars 6.12k forks source link

Glide crashed when there are many big images #466

Closed ruanml closed 8 years ago

ruanml commented 9 years ago

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:

Glide.with(activity).load(pathOfFile).placeholder(loading_drawable).error(error_drawable).into(photo_thumb_image);;

Image View:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="104dp"
    android:layout_height="104dp"
    android:background="@drawable/photo_grid_item_background_selector"
   >
    <ImageView
        android:id="@+id/photo_thumb_image"
        android:adjustViewBounds="true"
        android:layout_width="102dp"
        android:layout_height="102dp"
        android:scaleType="centerCrop"
        android:layout_gravity="center"
        android:background="@drawable/photo_nowloading"
    />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="right" >

        <ImageView
            android:contentDescription="@null"
            android:id="@+id/photo_check_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="2dp"
            android:layout_marginRight="2dp"
            android:clickable="false"
            android:focusable="false"
            android:focusableInTouchMode="false"
            android:layout_gravity="center_vertical"
            android:src="@drawable/check"
            />

    </LinearLayout>

    <LinearLayout
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
         android:orientation="vertical">
        <TextView 
            android:id="@+id/itemPosition"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textColor="@color/Chara_00"/>
    </LinearLayout>

    <View
        android:id="@+id/backgroundView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"  
        android:layout_gravity="center"
        android:visibility="gone"
        android:background="@android:color/transparent"
        />
</FrameLayout>

Stack trace:

part of Log
05-20 13:06:10.118 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.128 2373-2373/package D/AbsListView﹕ unregisterIRListener() is called
05-20 13:06:10.128 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.138 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.138 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.148 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.158 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.168 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.188 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.188 2373-2373/package D/AbsListView﹕ unregisterIRListener() is called
05-20 13:06:10.198 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.198 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.198 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.208 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.218 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.228 2373-2373/package E/qdmemalloc﹕ ion: Failed to map memory in the client: Out of memory
05-20 13:06:10.228 2373-2373/package E/qdgralloc﹕ Could not mmap handle 0x7a48cdd0, fd=65 (Out of memory)
05-20 13:06:10.228 2373-2373/package E/qdgralloc﹕ gralloc_register_buffer: gralloc_map failed
05-20 13:06:10.228 2373-2373/package W/GraphicBufferMapper﹕ registerBuffer(0x7a48cdd0) failed -12 (Out of memory)
05-20 13:06:10.228 2373-2373/package E/GraphicBuffer﹕ unflatten: registerBuffer failed: Out of memory (-12)
05-20 13:06:10.228 2373-2373/package W/Adreno-EGLSUB﹕ SyncBackBuffer:3087: handle base address is NULL
05-20 13:06:10.228 2373-2373/package W/Adreno-EGLSUB﹕ SyncBackBuffer:3087: handle base address is NULL
05-20 13:06:10.228 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.238 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.238 2373-2373/package W/Adreno-ES20﹕ : GL_OUT_OF_MEMORY
05-20 13:06:10.238 2373-2373/package W/Adreno-EGL﹕ : EGL_BAD_ALLOC
05-20 13:06:10.238 2373-2373/package W/HardwareRenderer﹕ EGL error: EGL_BAD_ALLOC
05-20 13:06:10.238 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.248 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.248 2373-2373/package W/HardwareRenderer﹕ Mountain View, we've had a problem here. Switching back to software rendering.
05-20 13:06:10.248 2373-2373/package E/ViewRootImpl﹕ sendUserActionEvent() mView == null
05-20 13:06:10.248 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:10.248 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:10.248 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:10.248 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.258 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.278 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.278 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.308 2373-2373/package D/AbsListView﹕ unregisterIRListener() is called
05-20 13:06:10.308 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.318 2373-2453/package A/libc﹕ Fatal signal 11 (SIGSEGV) at 0xffffffff (code=1), thread 2453 (fifo-pool-threa)
05-20 13:06:10.318 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.328 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.338 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:10.348 2373-2459/package D/skia﹕ --- decoder->decode returned false
05-20 13:06:10.348 2373-2459/package E/MediaMetadataRetrieverJNI﹕ getFrameAtTime: videoFrame is a NULL pointer
05-20 13:06:23.731 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:23.731 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:23.731 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:23.731 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:23.731 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:23.731 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:23.731 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:23.731 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:23.741 2373-2373/package D/GenericRequest﹕ load failed
05-20 13:06:23.741 2373-2373/package D/GenericRequest﹕ load failed
updateNdefPushMessageCallback(disable)

05-20 13:06:23.761 2373-2373/package D/AbsListView﹕ onDetachedFromWindow
05-20 13:06:23.761 2373-2373/package D/AbsListView﹕ unregisterIRListener() is called
05-20 13:06:23.771 2373-2373/package D/AbsListView﹕ unregisterIRListener() is called

05-20 13:07:15.542 2666-2666/package I/Adreno-EGL﹕ : EGL 1.4 QUALCOMM build: (CL3869936)
OpenGL ES Shader Compiler Version: 17.01.11.SPL
Build Date: 01/17/14 Fri
Local Branch:
Remote Branch:
Local Patches:
Reconstruct Branch:
ruanml commented 9 years ago

I put the sample image to GoogleDrive https://drive.google.com/file/d/0B6kFDyLnqV5FXzBieGpoYUxET1k/view?usp=sharing

TWiStErRob commented 9 years ago

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.

ruanml commented 9 years ago

@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().

sjudd commented 9 years ago

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.

ruanml commented 9 years ago

@sjudd Sorry. photo_thumb_image is the ImageView loading into. I tried calling override(), but not worked.

sjudd commented 9 years ago

And what format of images are these?

ruanml commented 9 years ago

@sjudd Format of these images is JPEG

sjudd commented 9 years ago

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.

ruanml commented 9 years ago

@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

sjudd commented 9 years ago

In your sample app you're using Glide 3.4, have you tried this with 3.6?

ruanml commented 9 years ago

yes. i tired it. but not worked.

eicm commented 9 years ago

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.

ruanml commented 9 years ago

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.

ruanml commented 9 years ago

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.

sjudd commented 9 years ago

I'm still not clear: is the using Glide 3.4? Or 3.6?

ruanml commented 9 years ago

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.

Dawish commented 8 years ago

Excuse me, did you solve your problem? I also met

ruanml commented 8 years ago

@Dawish Sorry, not yet. I had to use other library to show large size images.

TWiStErRob commented 8 years ago

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 onExceptions 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));
    }
}
huluwa-dev commented 8 years ago

@ruanml What library are you using now? Is it working well?

TWiStErRob commented 8 years ago

@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.

sjudd commented 8 years ago

@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 ?

TWiStErRob commented 8 years ago

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.

ruanml commented 8 years ago

@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.

TWiStErRob commented 8 years ago

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.

ruanml commented 8 years ago

Thank you for your advice. I'll try it. Actually, I like Glide better than UIL.

sjudd commented 8 years ago

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.

TWiStErRob commented 8 years ago

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.