Use streams to encode and decode the JSON backups

This significantly reduces memory usage.

(We buffer the OutputStream because kotlinx.serialization flushes its
internal buffers excessively[1]. We also buffer the InputStream for
consistency, even though kotlinx.serialization uses adequate
buffering; there is no performance impact because BufferedInputStream
cascades harmlessly[2].)

The functions encodeToStream() and decodeFromStream() are marked as
experimental, but this is a pretty fundamental use case so surely it
will continue to be supported in the future (maybe with minor
changes).

Fixes #6.

[1] https://github.com/Kotlin/kotlinx.serialization/blob/v1.6.3/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JvmJsonStreams.kt#L46
[2] https://github.com/openjdk/jdk/blob/jdk-23%2B14/src/java.base/share/classes/java/io/BufferedInputStream.java#L339
This commit is contained in:
Tom Levy 2024-03-18 17:28:41 +00:00
parent 9534a1031a
commit a7edeae6f3
2 changed files with 8 additions and 10 deletions

View file

@ -7,8 +7,8 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.DocumentsContract import android.provider.DocumentsContract
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToStream
import org.fossify.commons.activities.ManageBlockedNumbersActivity import org.fossify.commons.activities.ManageBlockedNumbersActivity
import org.fossify.commons.dialogs.* import org.fossify.commons.dialogs.*
import org.fossify.commons.extensions.* import org.fossify.commons.extensions.*
@ -118,6 +118,7 @@ class SettingsActivity : SimpleActivity() {
} }
} }
@OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
private fun exportMessages(uri: Uri) { private fun exportMessages(uri: Uri) {
ensureBackgroundThread { ensureBackgroundThread {
var success = false var success = false
@ -128,11 +129,8 @@ class SettingsActivity : SimpleActivity() {
return@getMessagesToExport return@getMessagesToExport
} }
val json = Json { encodeDefaults = true } val json = Json { encodeDefaults = true }
val jsonString = json.encodeToString(messagesToExport) contentResolver.openOutputStream(uri)!!.buffered().use { outputStream ->
val outputStream = contentResolver.openOutputStream(uri)!! json.encodeToStream(messagesToExport, outputStream)
outputStream.use {
it.write(jsonString.toByteArray())
} }
success = true success = true
toast(org.fossify.commons.R.string.exporting_successful) toast(org.fossify.commons.R.string.exporting_successful)

View file

@ -4,6 +4,7 @@ import android.net.Uri
import android.util.Xml import android.util.Xml
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.fossify.commons.extensions.showErrorToast import org.fossify.commons.extensions.showErrorToast
import org.fossify.commons.extensions.toast import org.fossify.commons.extensions.toast
import org.fossify.commons.helpers.ensureBackgroundThread import org.fossify.commons.helpers.ensureBackgroundThread
@ -37,13 +38,12 @@ class MessagesImporter(private val activity: SimpleActivity) {
} }
} }
@OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
private fun importJson(uri: Uri) { private fun importJson(uri: Uri) {
try { try {
val jsonString = activity.contentResolver.openInputStream(uri)!!.use { inputStream -> val deserializedList = activity.contentResolver.openInputStream(uri)!!.buffered().use { inputStream ->
inputStream.bufferedReader().readText() Json.decodeFromStream<List<MessagesBackup>>(inputStream)
} }
val deserializedList = Json.decodeFromString<List<MessagesBackup>>(jsonString)
if (deserializedList.isEmpty()) { if (deserializedList.isEmpty()) {
activity.toast(org.fossify.commons.R.string.no_entries_for_importing) activity.toast(org.fossify.commons.R.string.no_entries_for_importing)
return return