bumptech / glide

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

Error com.bumptech.glide.Registry$NoModelLoaderAvailableException: Failed to find any ModelLoaders for model: android.graphics.pdf.PdfRenderer$Page #3750

Closed roncbird closed 5 years ago

roncbird commented 5 years ago

Glide Version: 4.9.0

Integration libraries: We are using stock networking.

Device/Android Version: I don't think it is device specific, but I've tested it on a Pixel 2 emulator running API 27, on a physical Pixel 3 running API 28, and Samsung Note 8 running API 25

Issue details / Repro steps / Use case background:

I have implemented a custom ModelLoader and DataFetcher following Glides tutorial https://bumptech.github.io/glide/tut/custom-modelloader.html. From what I can tell I have implemented it correctly, but I am still getting the following error: com.bumptech.glide.Registry$NoModelLoaderAvailableException: Failed to find any ModelLoaders for model: android.graphics.pdf.PdfRenderer$Page@6e667d5

Please see the relevant classes below:

import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer.Page
import com.bumptech.glide.Glide
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule

@GlideModule
class MyAppGlideModule : AppGlideModule() {

    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        registry.prepend(Page::class.java, Bitmap::class.java, PdfModelLoaderFactory())
    }
}
import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer.Page
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoader.LoadData
import com.bumptech.glide.signature.ObjectKey

class PdfModelLoader : ModelLoader<Page?, Bitmap> {

    override fun buildLoadData(model: Page, width: Int, height: Int, options: Options): LoadData<Bitmap>? {
        return LoadData<Bitmap>(ObjectKey("pdfPage-${model.index}"), PdfPageDataFetcher(model))
    }

    override fun handles(model: Page): Boolean {
        return true
    }
}
class PdfModelLoaderFactory : ModelLoaderFactory<Page?, Bitmap> {

    override fun build(unused: MultiModelLoaderFactory): ModelLoader<Page?, Bitmap> {
        return PdfModelLoader()
    }

    override fun teardown() {
        // Do nothing.
    }
}
import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer.Page
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.data.DataFetcher

class PdfPageDataFetcher(private val currentPage: Page?) : DataFetcher<Bitmap> {

    override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {

         currentPage?.let { page ->
            // Important: the destination bitmap must be ARGB (not RGB).
            val bitmap: Bitmap = try {
                Bitmap.createBitmap(
                    page.width * 2, page.height * 2,
                    Bitmap.Config.ARGB_8888
                )
            } catch (ex: OutOfMemoryError) {
                callback.onLoadFailed(Exception(ex.localizedMessage))
                try {
                    Bitmap.createBitmap(
                        page.width, page.height,
                        Bitmap.Config.ARGB_8888
                    )
                } catch (ignore: OutOfMemoryError) {
                    callback.onLoadFailed(Exception(ignore.localizedMessage))
                    throw UnableToRenderImageException()
                }
            }

            // Here, we render the page onto the Bitmap.
            // To render a portion of the page, use the second and third parameter. Pass nulls to get
            // the default result.
            // Pass either RENDER_MODE_FOR_DISPLAY or RENDER_MODE_FOR_PRINT for the last parameter.
            page.render(bitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY)

           callback.onDataReady(bitmap)
        }
    }

    override fun cleanup() {

    }

    override fun cancel() {

    }

    override fun getDataClass(): Class<Bitmap> {
        return Bitmap::class.java
    }

    override fun getDataSource(): DataSource {
        return DataSource.LOCAL
    }

    internal inner class UnableToRenderImageException : Exception()
}

We are displaying PDF pages natively, using Android PdfRenderer to get each page and then render that page into a Bitmap. Each Bitmap is then loaded into an ImageView inside a CardView. Each CardView is displayed in a RecyclerView using Airbnb's Epoxy library. To quickly display large PDFs, we have to use Epoxy's extension function addGlidePreloader() to allow us to preload a set number of pages a head of time to allow for smooth scrolling. Here is the addGlidePreloader implementation:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        pdf_recycler_view.setHasFixedSize(true)
        pdf_recycler_view.setController(controller)
        ...
        pdf_recycler_view.addGlidePreloader(
            Glide.with(this),
            maxPreloadDistance = 10,
            preloader = glidePreloader { requestManager, model: PdfPageModel_, _ ->
                requestManager.loadImage(true, model.imagePosition, showPage = showPage)
            }
        )       
        ...
    }

Here is the RequestManager loadImage() extension function:

fun RequestManager.loadImage(isPreloading: Boolean, position: Int, showPage: (Int) -> PdfRenderer.Page?): RequestBuilder<Bitmap> {

    val options = RequestOptions
        .diskCacheStrategyOf(DiskCacheStrategy.AUTOMATIC)
        .dontAnimate()
        .signature(ObjectKey(position.toString().plus(if (isPreloading) "_preloading" else "_not_preloading")))

    return asBitmap().apply(options).load(showPage(position))
}

Here is the showPage lambda:

private val showPage : (index: Int) -> PdfRenderer.Page? = {
        // Make sure to close the current page before opening another one.
        currentPage?.close()

        // Use `openPage` to open a specific page in PDF.
        currentPage = pdfRenderer?.openPage(it)

        Timber.e("PdfFragment.showPage currentPage index = ${currentPage?.index}")

        currentPage

    }

When our fragment starts up, it makes a network call to get the PDF, then stores the file in memory, and then Epoxy attempts to use the RequestManger extension function loadImage() to get the pdfPages rendered as a Bitmap, and that is when the logs start reporting the errors.

Hopefully that makes sense. Please let me know if there are any questions, and thanks in advance for any help you can provide.

Stack trace / LogCat:

2019-07-10 09:59:55.325 31955-31955/com.varomoney.bank.dev V/Engine: Started new load in 0.172ms, key: EngineKey{model=android.graphics.pdf.PdfRenderer$Page@6f8bc92, width=-2147483648, height=-2147483648, resourceClass=class android.graphics.Bitmap, transcodeClass=class android.graphics.Bitmap, signature=ObjectKey{object=0_preloading}, hashCode=1767836725, transformations={}, options=Options{values={Option{key='com.bumptech.glide.load.resource.gif.GifOptions.DisableAnimation'}=true}}}
2019-07-10 09:59:55.326 31955-32100/com.varomoney.bank.dev E/GlideExecutor: Request threw uncaught throwable
    com.bumptech.glide.Registry$NoModelLoaderAvailableException: Failed to find any ModelLoaders for model: android.graphics.pdf.PdfRenderer$Page@6f8bc92
        at com.bumptech.glide.Registry.getModelLoaders(Registry.java:588)
        at com.bumptech.glide.load.engine.DecodeHelper.getLoadData(DecodeHelper.java:205)
        at com.bumptech.glide.load.engine.DecodeHelper.getCacheKeys(DecodeHelper.java:223)
        at com.bumptech.glide.load.engine.ResourceCacheGenerator.startNext(ResourceCacheGenerator.java:44)
        at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:309)
        at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:276)
        at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:235)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)
        at com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:446)
2019-07-10 09:59:55.333 31955-31955/com.varomoney.bank.dev E/PdfFragment$showPage: PdfFragment.showPage currentPage index = 1
2019-07-10 09:59:55.334 31955-31955/com.varomoney.bank.dev V/Engine: Started new load in 0.222ms, key: EngineKey{model=android.graphics.pdf.PdfRenderer$Page@6e667d5, width=-2147483648, height=-2147483648, resourceClass=class android.graphics.Bitmap, transcodeClass=class android.graphics.Bitmap, signature=ObjectKey{object=1_preloading}, hashCode=1917559217, transformations={}, options=Options{values={Option{key='com.bumptech.glide.load.resource.gif.GifOptions.DisableAnimation'}=true}}}
2019-07-10 09:59:55.335 31955-32112/com.varomoney.bank.dev E/GlideExecutor: Request threw uncaught throwable
    com.bumptech.glide.Registry$NoModelLoaderAvailableException: Failed to find any ModelLoaders for model: android.graphics.pdf.PdfRenderer$Page@6e667d5
        at com.bumptech.glide.Registry.getModelLoaders(Registry.java:588)
        at com.bumptech.glide.load.engine.DecodeHelper.getLoadData(DecodeHelper.java:205)
        at com.bumptech.glide.load.engine.DecodeHelper.getCacheKeys(DecodeHelper.java:223)
        at com.bumptech.glide.load.engine.ResourceCacheGenerator.startNext(ResourceCacheGenerator.java:44)
        at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:309)
        at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:276)
        at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:235)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)
        at com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:446)
roncbird commented 5 years ago

I was able to figure out the issue. We are using kotlin-dsl gradle, and I was trying to add the glide complier dependency like this annotationProcessor(Libs.com_github_bumptech_glide_compiler), which was causing the problem. I had to add the dependency like this kapt(Libs.com_github_bumptech_glide_compiler). So, instead of using annotationProcessor I had to use kapt.