Merge pull request #3937 from CalebKL/task/anr-fix-zimfile-reader

Task/anr fix zimfile reader
This commit is contained in:
Kelson 2025-01-27 14:17:51 +01:00 committed by GitHub
commit 8bd23938bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 76 additions and 20 deletions

View File

@ -159,12 +159,25 @@ class KiwixReaderFragment : CoreReaderFragment() {
override fun openHomeScreen() {
Handler(Looper.getMainLooper()).postDelayed({
if (webViewList.size == 0) {
hideTabSwitcher()
hideTabSwitcher(false)
}
}, HIDE_TAB_SWITCHER_DELAY)
}
override fun hideTabSwitcher() {
/**
* Hides the tab switcher and optionally closes the ZIM book based on the `shouldCloseZimBook` parameter.
*
* @param shouldCloseZimBook If `true`, the ZIM book will be closed, and the `ZimFileReader` will be set to `null`.
* If `false`, it skips setting the `ZimFileReader` to `null`. This is particularly useful when restoring tabs,
* as setting the `ZimFileReader` to `null` would require re-creating it, which is a resource-intensive operation,
* especially for large ZIM files.
*
* Refer to the following methods for more details:
* @See exitBook
* @see closeTab
* @see closeAllTabs
*/
override fun hideTabSwitcher(shouldCloseZimBook: Boolean) {
actionBar?.let { actionBar ->
actionBar.setDisplayShowTitleEnabled(true)
toolbar?.let { activity?.setupDrawerToggle(it, true) }
@ -181,7 +194,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
}
mainMenu?.showWebViewOptions(true)
if (webViewList.isEmpty()) {
exitBook()
exitBook(shouldCloseZimBook)
} else {
// Reset the top margin of web views to 0 to remove any previously set margin
// This ensures that the web views are displayed without any additional

View File

@ -19,9 +19,12 @@
package org.kiwix.kiwixmobile.core
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
@ -45,7 +48,17 @@ class StorageObserver @Inject constructor(
): Flowable<List<BookOnDisk>> {
return scanFiles(scanningProgressListener)
.withLatestFrom(downloadRoomDao.downloads(), BiFunction(::toFilesThatAreNotDownloading))
.map { it.mapNotNull(::convertToBookOnDisk) }
.flatMapSingle { files ->
Single.create { emitter ->
CoroutineScope(Dispatchers.IO).launch {
try {
emitter.onSuccess(files.mapNotNull { convertToBookOnDisk(it) })
} catch (ignore: Exception) {
emitter.onError(ignore)
}
}
}
}
}
private fun scanFiles(scanningProgressListener: ScanningProgressListener) =
@ -57,7 +70,7 @@ class StorageObserver @Inject constructor(
private fun fileHasNoMatchingDownload(downloads: List<DownloadModel>, file: File) =
downloads.firstOrNull { file.absolutePath.endsWith(it.fileNameFromUrl) } == null
private fun convertToBookOnDisk(file: File) = runBlocking {
private suspend fun convertToBookOnDisk(file: File) =
zimReaderFactory.create(ZimReaderSource(file))
?.let { zimFileReader ->
BookOnDisk(zimFileReader).also {
@ -66,5 +79,4 @@ class StorageObserver @Inject constructor(
zimFileReader.dispose()
}
}
}
}

View File

@ -129,10 +129,10 @@ class LibkiwixBookmarks @Inject constructor(
} ?: emptyList()
}
fun bookmarkUrlsForCurrentBook(zimFileReader: ZimFileReader): Flowable<List<String>> =
fun bookmarkUrlsForCurrentBook(zimId: String): Flowable<List<String>> =
flowableBookmarkList()
.map { bookmarksList ->
bookmarksList.filter { it.zimId == zimFileReader.id }
bookmarksList.filter { it.zimId == zimId }
.map(LibkiwixBookmarkItem::bookmarkUrl)
}
.subscribeOn(Schedulers.io())

View File

@ -860,7 +860,13 @@ abstract class CoreReaderFragment :
view?.startAnimation(AnimationUtils.loadAnimation(view.context, anim))
}
protected open fun hideTabSwitcher() {
/**
* @param shouldCloseZimBook A flag to indicate whether the ZIM book should be closed.
* - Default is `true`, which ensures normal behavior for most scenarios.
* - If `false`, the ZIM book is not closed. This is useful in cases where the user restores tabs,
* as closing the ZIM book would require reloading the ZIM file, which can be a resource-intensive operation.
*/
protected open fun hideTabSwitcher(shouldCloseZimBook: Boolean = true) {
actionBar?.apply {
setDisplayShowTitleEnabled(true)
}
@ -1388,7 +1394,17 @@ abstract class CoreReaderFragment :
.setAction(R.string.undo) { undoButton ->
undoButton.isEnabled = false
restoreDeletedTab(index)
}.show()
}.addCallback(object : Snackbar.Callback() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
// If the undo button is not clicked and no tabs are left, exit the book and
// clean up resources.
if (event != DISMISS_EVENT_ACTION && webViewList.isEmpty()) {
closeZimBook()
}
}
})
.show()
}
openHomeScreen()
}
@ -1399,18 +1415,22 @@ abstract class CoreReaderFragment :
mainMenu?.showBookSpecificMenuItems()
}
protected fun exitBook() {
protected fun exitBook(shouldCloseZimBook: Boolean = true) {
showNoBookOpenViews()
bottomToolbar?.visibility = View.GONE
actionBar?.title = getString(R.string.reader)
contentFrame?.visibility = View.GONE
hideProgressBar()
mainMenu?.hideBookSpecificMenuItems()
closeZimBook()
if (shouldCloseZimBook) {
closeZimBook()
}
}
fun closeZimBook() {
zimReaderContainer?.setZimReaderSource(null)
lifecycleScope.launch {
zimReaderContainer?.setZimReaderSource(null)
}
}
protected fun showProgressBarWithProgress(progress: Int) {
@ -1443,7 +1463,6 @@ abstract class CoreReaderFragment :
LinearLayout.LayoutParams.MATCH_PARENT
)
}
zimReaderContainer?.setZimReaderSource(tempZimSourceForUndo)
webViewList.add(index, it)
tabsAdapter?.notifyDataSetChanged()
snackBarRoot?.let { root ->
@ -1800,7 +1819,7 @@ abstract class CoreReaderFragment :
protected fun setUpBookmarks(zimFileReader: ZimFileReader) {
safeDispose()
bookmarkingDisposable = Flowable.combineLatest(
libkiwixBookmarks?.bookmarkUrlsForCurrentBook(zimFileReader),
libkiwixBookmarks?.bookmarkUrlsForCurrentBook(zimFileReader.id),
webUrlsProcessor,
List<String?>::contains
)
@ -1876,7 +1895,16 @@ abstract class CoreReaderFragment :
setIsCloseAllTabButtonClickable(true)
restoreDeletedTabs()
}
}.show()
}.addCallback(object : Snackbar.Callback() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
// If the undo button is not clicked and no tabs are left, exit the book and
// clean up resources.
if (event != DISMISS_EVENT_ACTION && webViewList.isEmpty()) {
closeZimBook()
}
}
}).show()
}
}
@ -1886,7 +1914,6 @@ abstract class CoreReaderFragment :
private fun restoreDeletedTabs() {
if (tempWebViewListForUndo.isNotEmpty()) {
zimReaderContainer?.setZimReaderSource(tempZimSourceForUndo)
webViewList.addAll(tempWebViewListForUndo)
tabsAdapter?.notifyDataSetChanged()
snackBarRoot?.let { root ->

View File

@ -18,7 +18,9 @@
package org.kiwix.kiwixmobile.core.reader
import android.webkit.WebResourceResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Factory
import java.net.HttpURLConnection
import javax.inject.Inject
@ -32,11 +34,11 @@ class ZimReaderContainer @Inject constructor(private val zimFileReaderFactory: F
field = value
}
fun setZimReaderSource(zimReaderSource: ZimReaderSource?) {
suspend fun setZimReaderSource(zimReaderSource: ZimReaderSource?) {
if (zimReaderSource == zimFileReader?.zimReaderSource) {
return
}
zimFileReader = runBlocking {
zimFileReader = withContext(Dispatchers.IO) {
if (zimReaderSource?.exists() == true && zimReaderSource.canOpenInLibkiwix())
zimFileReaderFactory.create(zimReaderSource)
else null

View File

@ -42,6 +42,7 @@ import org.kiwix.sharedFunctions.bookOnDisk
import org.kiwix.sharedFunctions.resetSchedulers
import org.kiwix.sharedFunctions.setScheduler
import java.io.File
import java.util.concurrent.TimeUnit
class StorageObserverTest {
@ -106,6 +107,7 @@ class StorageObserverTest {
.also {
downloads.offer(listOf(downloadModel))
files.offer(listOf(file))
it.awaitDone(2, TimeUnit.SECONDS)
}
private fun withFiltering() {