I need to download files from a list of URLs. The size of the list URLs is between 3000 and 6000. These files are small in size with the largest being 25KB. I have a function that can download a file but when used for multiple files as in this case, it puts pressure on the heap as object instances are created on the fly for each URL to be downloaded. The function is below:
fun download(
dest: File,
url: String,
filename: String,
onPreExecute: () -> Unit = {},
onPostExecute: (Any) -> Unit = {},
onProgress: (DownloadState) -> Unit = {},
): Job {
return appScope.executeAsyncTask(
onPreExecute = onPreExecute,
onPostExecute = onPostExecute,
onProgress = onProgress,
doInBackground = { reportProgress: suspend (progress: DownloadState) -> Unit ->
try {
val httpUrl = URL(url)
val connection = httpUrl.openConnection()
connection.connect()
val totalBytes = connection.contentLengthLong
httpUrl.openStream().use { ins ->
BufferedInputStream(ins).use { bis ->
FileOutputStream(File(dest, filename)).use { fos ->
var bytesRead: Int
var totalBytesRead = 0L
val buffer = ByteArray(4096)
// Downloading started
val ds = DownloadState(
downloading = true,
fileSizeBytes = totalBytes
)
while (
bis.read(
/* b = */ buffer,
/* off = */0,
/* len = */buffer.size
).also { bytesRead = it } != -1
) {
totalBytesRead += bytesRead
// Report download progress
reportProgress(
ds.copy(
bytesDownloaded = totalBytesRead,
progress = (totalBytesRead / totalBytes.toFloat())
)
)
fos.write(buffer, 0, bytesRead)
}
// Downloading finished
reportProgress(ds.copy(downloading = false))
}
}
}
} catch (e: Exception) {
e.printStackTrace()
Log.e(TAG, e.message.toString())
reportProgress(DownloadState(error = e, downloading = false))
throw e
}
}
)
}
I call that function in a view model as follows:
private fun download(directory: File) {
viewModelScope.launch {
try {
val audios = listOf(/* Audio(...) */)
if (isExternalStorageWritable()) {
audios.forEachIndexed { i, s ->
val count = i + 1
val id = s.id
val done = count == audios.size
val filename = audio.getFilename(id)
val dest = File(directory, audio.getDirectory())
if (!dest.exists()) dest.mkdirs()
// If file already exists skip it
if (File(dest, filename).exists()) {
_uiState.update {
val audioCount = it.audioCount + 1
it.copy(
audioCount = audioCount,
syncCompleted = done,
audioDownloadDone = done,
)
}
return@forEach
}
downloadClient.download(
dest = dest,
filename = filename,
url = audio.url,
onPostExecute = {
_uiState.update {
val audioCount = it.audioCount + 1
it.copy(
audioCount = audioCount,
syncCompleted = done,
audioDownloadDone = done,
)
}
}
)
}
}
} catch (e: Exception) {
_uiState.update {
it.copy(
syncCompleted = true,
audioDownloadErred = true,
)
}
}
}
}
After a few downloads has completed, the GC logs a message like waiting for allocated...
then the system kills the app with a signal 9.
How do I go about fixing this issue, so the download completes without the app getting killed?
Please keep in mind, I also need to update the UI with the download progress.
Feel free to suggest other options/libraries with code that would help get around this issue.