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:
我当前在尝试使用OkHttp下载大文件,然后使用Kotlin的Copto()方法将该文件写入我的定制目标文件位置时,遇到了java.lang.OutOfMemoyError异常。还有更好的选择吗?出于某种原因,我不想使用Android DownloadManager。以下是我的代码:
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]
更多回答
Please also include the full stack trace as text in your question.
请将完整的堆栈跟踪作为文本包含在您的问题中。
Interesting, it seems like you are getting an OOME not because you have used too much RAM but because your RAM is heavily fragmented so that there is not even a block with 32 bytes as a block left. Are you performing other tasks in background in your app? On what phone(s) do you see this error?
有趣的是,你似乎得到了一个OOME,不是因为你使用了太多的内存,而是因为你的内存严重碎片化,以至于没有一个32字节的块作为一个块。您是否在应用程序的后台执行其他任务?你在什么手机(S)上看到这个错误?
@Robert Galaxy S22 Ultra. There is one Service active from which the CustomDownloader's startDownload method is called
@Robert Galaxy S22 Ultra.有一个服务处于活动状态,从中调用CustomDownLoader的startDownLoad方法
Yeah, this is interesting. It doesn't really seem caused by reading the whole file into the memory. To be honest, it looks like some problem in okio. Do you use the latest version of okhttp/okio?
是的,这很有趣。它看起来不像是由将整个文件读入内存引起的。老实说,它看起来像一些问题,在奥基奥。您是否使用最新版本的okhttp/okio?
@broot Yes of course :( it's really weird as I tried every possible option and still ending up looking for some third-party library
@布罗特:当然是的:(这真的很奇怪,因为我尝试了所有可能的选择,但最终仍然在寻找一些第三方库
优秀答案推荐
you are reading the data in the memory and it gets large!
you should use a buffer to read & write smaller amount of data and avoid reading a large amount of data! because it is being saved to memory and you will get out of memory
您正在读取内存中的数据,它变得很大!您应该使用缓冲区来读取和写入较少的数据量,避免读取大量数据!因为它正被保存到内存中,而您将耗尽内存
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!
我做的每件事都是正确的,inputStream.CopyTo(OutputStream)没有问题,是okhttp拦截器导致了错误。我使用的是ChuckerInterceptor,它下载整个文件并将其保存到内存中,所以我所做的就是从OkHttp客户端删除该文件,问题就解决了!
The problem is java.lang.OutOfMemoryError
because you are trying to download a larger file.
问题是java.lang.OutOfMemoryError,因为您正在尝试下载一个较大的文件。
So basically, you are trying to download a larger file into your Android Phone's RAM with OkHttp
. That's the problem.
所以基本上,你是在尝试用OkHttp将一个更大的文件下载到你的Android手机的RAM中。这就是问题所在。
更多回答
Is there any solution?
有什么解决办法吗?
It's always better to use the Download Manager comes with the OS itself. As, you are trying not using it, you can use try store it in BufferedReader
.
使用操作系统本身附带的下载管理器总是更好。当您尝试不使用它时,可以使用try将其存储在BufferedReader中。
Already tried it :( I need to use custom okhttpclient and since I cannot do that with DownloadManager, I have to find something else
我已经试过了:(我需要使用定制的okhttp客户端,因为我不能用DownloadManager做到这一点,所以我必须找到其他东西
我是一名优秀的程序员,十分优秀!