Search code examples
androidkotlinfileokhttp

OutOfMemoryError while using Kotlin's copyTo()


I'm currently facing java.lang.OutOfMemoryError exception while trying to download large file with OkHttp and then write that file to my custom target file location with Kotlin's copyTo() method. Is there any better option? I don't want to use Android DownloadManager for some reason. Here is my code:

  override suspend fun download(
        url: String,
        targetFile: File,
        onProgressChanged: (Int) -> Unit
    ) {

        if (!targetFile.exists()) {
            targetFile.parentFile?.mkdirs()
        }

        okHttpClient.newCall(
            Request.Builder()
                .get()
                .url(url)
                .build()
        ).execute().body?.let {
            startDownload(it.byteStream(), targetFile)
        }
    }

    private suspend fun startDownload(source: InputStream, targetFile: File) =
        withContext(Dispatchers.IO) {
            source.use { ins ->
                ins.copyTo(targetFile.outputStream())
            }
        } 

EDIT: Here is my full stack trace:

         Uncaught exception thrown by finalizer
java.lang.OutOfMemoryError: Failed to allocate a 24 byte allocation with 10373280 free bytes and 10130KB until OOM, target footprint 536870912, growth limit 536870912; failed due to fragmentation (largest possible contiguous allocation 0 bytes). Number of 256KB sized free regions are: 0
    at com.android.internal.os.BinderInternal$GcWatcher.finalize(BinderInternal.java:64)
    at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:319)
    at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:306)
    at java.lang.Daemons$Daemon.run(Daemons.java:140)
    at java.lang.Thread.run(Thread.java:1012)
FATAL EXCEPTION: DefaultDispatcher-worker-7
Process: ge.mydownloder.mobile.debug, PID: 23901
java.lang.OutOfMemoryError: Failed to allocate a 32 byte allocation with 10373328 free bytes and 10130KB until OOM, target footprint 536870912, growth limit 536870912; failed due to fragmentation (largest possible contiguous allocation 0 bytes). Number of 256KB sized free regions are: 0
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.addWaiter(AbstractQueuedSynchronizer.java:651)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1240)
    at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:267)
    at okio.AsyncTimeout$Companion.scheduleTimeout(AsyncTimeout.kt:230)
    at okio.AsyncTimeout$Companion.access$scheduleTimeout(AsyncTimeout.kt:204)
    at okio.AsyncTimeout.enter(AsyncTimeout.kt:57)
    at okio.AsyncTimeout$source$1.read(AsyncTimeout.kt:333)
    at okio.RealBufferedSource.read(RealBufferedSource.kt:189)
    at okhttp3.internal.http1.Http1ExchangeCodec$AbstractSource.read(Http1ExchangeCodec.kt:339)
    at okhttp3.internal.http1.Http1ExchangeCodec$FixedLengthSource.read(Http1ExchangeCodec.kt:376)
    at okhttp3.internal.connection.Exchange$ResponseBodySource.read(Exchange.kt:281)
    at okio.RealBufferedSource.read(RealBufferedSource.kt:189)
    at com.chuckerteam.chucker.internal.support.TeeSource.read(TeeSource.kt:24)
    at okio.RealBufferedSource$inputStream$1.read(RealBufferedSource.kt:158)
    at java.io.InputStream.read(InputStream.java:205)
    at kotlin.io.ByteStreamsKt.copyTo(IOStreams.kt:110)
    at kotlin.io.ByteStreamsKt.copyTo$default(IOStreams.kt:103)
    at ge.mydownloder.data.downloader.CustomDownloader$startDownload$2.invokeSuspend(CustomDownloader.kt:65)
    at ge.mydownloder.data.downloader.CustomDownloader$startDownload$2.invoke(Unknown Source:8)
    at ge.mydownloder.data.downloader.CustomDownloader$startDownload$2.invoke(Unknown Source:4)
    at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:78)
    at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:167)
    at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
    at ge.mydownloder.data.downloader.CustomDownloader.startDownload(CustomDownloader.kt:64)
    at ge.mydownloder.data.downloader.CustomDownloader.access$startDownload(CustomDownloader.kt:24)
    at ge.mydownloder.data.downloader.CustomDownloader$download$2$1$1.invokeSuspend(CustomDownloader.kt:49)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
    Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@7ae1d98, Dispatchers.IO]

Solution

  • I was doing everything right, there was no problem with inputStream.copyTo(outputStream), it was okhttpinterceptor which was causing the error. I was using ChuckerInterceptor which was downloading and saving a whole file into the memory, So what I did was just removing that from OkHttp client and the problem is gone!