diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index c213788db..d8995cec7 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -144,6 +144,7 @@ object PreferKey { const val volumeKeyPage = "volumeKeyPage" const val volumeKeyPageOnPlay = "volumeKeyPageOnPlay" const val mouseWheelPage = "mouseWheelPage" + const val recordHeapDump = "recordHeapDump" const val cPrimary = "colorPrimary" const val cAccent = "colorAccent" diff --git a/app/src/main/java/io/legado/app/help/CrashHandler.kt b/app/src/main/java/io/legado/app/help/CrashHandler.kt index c20390582..2f9565231 100644 --- a/app/src/main/java/io/legado/app/help/CrashHandler.kt +++ b/app/src/main/java/io/legado/app/help/CrashHandler.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.Context import android.net.Uri import android.os.Build +import android.os.Debug import android.os.Looper import android.webkit.WebSettings import io.legado.app.constant.AppConst @@ -12,13 +13,24 @@ import io.legado.app.exception.NoStackTraceException import io.legado.app.help.config.AppConfig import io.legado.app.help.config.LocalConfig import io.legado.app.model.ReadAloud -import io.legado.app.utils.* +import io.legado.app.utils.FileDoc +import io.legado.app.utils.FileUtils +import io.legado.app.utils.createFileIfNotExist +import io.legado.app.utils.createFolderReplace +import io.legado.app.utils.externalCache +import io.legado.app.utils.getFile +import io.legado.app.utils.longToastOnUiLegacy +import io.legado.app.utils.stackTraceStr +import io.legado.app.utils.writeText import splitties.init.appCtx import java.io.PrintWriter import java.io.StringWriter import java.text.SimpleDateFormat -import java.util.* +import java.util.Date import java.util.concurrent.TimeUnit +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.set /** * 异常管理类 @@ -69,6 +81,9 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler { LocalConfig.appCrash = true //保存日志文件 saveCrashInfo2File(ex) + if (ex is OutOfMemoryError && AppConfig.recordHeapDump) { + doHeapDump() + } context.longToastOnUiLegacy(ex.stackTraceStr) Thread.sleep(3000) } @@ -148,6 +163,19 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler { } } + /** + * 进行堆转储 + */ + fun doHeapDump() { + val heapDir = appCtx + .externalCache + .getFile("heapDump") + heapDir.createFolderReplace() + val heapFile = heapDir.getFile("heap-dump-${System.currentTimeMillis()}.hprof") + val heapDumpName = heapFile.absolutePath + Debug.dumpHprofData(heapDumpName) + } + } } diff --git a/app/src/main/java/io/legado/app/help/config/AppConfig.kt b/app/src/main/java/io/legado/app/help/config/AppConfig.kt index e776ef866..65fc5f53f 100644 --- a/app/src/main/java/io/legado/app/help/config/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/AppConfig.kt @@ -449,6 +449,8 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { val recordLog get() = appCtx.getPrefBoolean(PreferKey.recordLog) + val recordHeapDump get() = appCtx.getPrefBoolean(PreferKey.recordHeapDump, false) + val loadCoverOnlyWifi get() = appCtx.getPrefBoolean(PreferKey.loadCoverOnlyWifi, false) val showAddToShelfAlert get() = appCtx.getPrefBoolean(PreferKey.showAddToShelfAlert, true) diff --git a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt index e99e49401..f2eec9523 100644 --- a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt +++ b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt @@ -12,6 +12,7 @@ import io.legado.app.R import io.legado.app.constant.AppConst.appInfo import io.legado.app.constant.AppLog import io.legado.app.help.AppUpdate +import io.legado.app.help.CrashHandler import io.legado.app.help.config.AppConfig import io.legado.app.help.coroutine.Coroutine import io.legado.app.ui.widget.dialog.TextDialog @@ -61,6 +62,7 @@ class AboutFragment : PreferenceFragmentCompat() { "gzGzh" -> requireContext().sendToClip(getString(R.string.legado_gzh)) "crashLog" -> showDialogFragment() "saveLog" -> saveLog() + "saveHeapDump" -> saveHeapDump() } return super.onPreferenceTreeClick(preference) } @@ -137,10 +139,50 @@ class AboutFragment : PreferenceFragmentCompat() { } } } + val heapFile = FileDoc.fromFile(File(appCtx.externalCacheDir, "heapDump")).list() + ?.firstOrNull() + if (heapFile != null) { + doc.find("heapDump")?.delete() + val heapDumpDoc = doc.createFolderIfNotExist("heapDump") + heapFile.openInputStream().getOrNull()?.use { input -> + heapDumpDoc.createFileIfNotExist(heapFile.name).openOutputStream().getOrNull() + ?.use { + input.copyTo(it) + } + } + } toastOnUi("已保存至备份目录") }.onError { AppLog.put("保存日志出错\n${it.localizedMessage}", it, true) } } + private fun saveHeapDump() { + Coroutine.async { + val backupPath = AppConfig.backupPath ?: let { + toastOnUi("未设置备份目录") + return@async + } + toastOnUi("开始保存堆转储") + CrashHandler.doHeapDump() + val heapFile = FileDoc.fromFile(File(appCtx.externalCacheDir, "heapDump")).list() + ?.firstOrNull() ?: let { + toastOnUi("未找到堆转储文件") + return@async + } + val doc = FileDoc.fromUri(Uri.parse(backupPath), true) + doc.find("heapDump")?.delete() + val heapDumpDoc = doc.createFolderIfNotExist("heapDump") + heapFile.openInputStream().getOrNull()?.use { input -> + heapDumpDoc.createFileIfNotExist(heapFile.name).openOutputStream().getOrNull() + ?.use { + input.copyTo(it) + } + } + toastOnUi("已保存至备份目录") + }.onError { + AppLog.put("保存堆转储失败\n${it.localizedMessage}", it) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/login/WebViewLoginFragment.kt b/app/src/main/java/io/legado/app/ui/login/WebViewLoginFragment.kt index d36b4289a..da6b763fb 100644 --- a/app/src/main/java/io/legado/app/ui/login/WebViewLoginFragment.kt +++ b/app/src/main/java/io/legado/app/ui/login/WebViewLoginFragment.kt @@ -2,12 +2,19 @@ package io.legado.app.ui.login import android.annotation.SuppressLint import android.graphics.Bitmap +import android.net.Uri import android.net.http.SslError import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View -import android.webkit.* +import android.webkit.CookieManager +import android.webkit.SslErrorHandler +import android.webkit.WebChromeClient +import android.webkit.WebResourceRequest +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient import androidx.fragment.app.activityViewModels import io.legado.app.R import io.legado.app.base.BaseFragment @@ -16,8 +23,10 @@ import io.legado.app.data.entities.BaseSource import io.legado.app.databinding.FragmentWebViewLoginBinding import io.legado.app.help.http.CookieStore import io.legado.app.lib.theme.accentColor -import io.legado.app.utils.gone import io.legado.app.utils.NetworkUtils +import io.legado.app.utils.gone +import io.legado.app.utils.longSnackbar +import io.legado.app.utils.openUrl import io.legado.app.utils.snackbar import io.legado.app.utils.viewbindingdelegate.viewBinding @@ -89,6 +98,33 @@ class WebViewLoginFragment : BaseFragment(R.layout.fragment_web_view_login) { super.onPageFinished(view, url) } + override fun shouldOverrideUrlLoading( + view: WebView, + request: WebResourceRequest + ): Boolean { + return shouldOverrideUrlLoading(request.url) + } + + @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION", "KotlinRedundantDiagnosticSuppress") + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + return shouldOverrideUrlLoading(Uri.parse(url)) + } + + private fun shouldOverrideUrlLoading(url: Uri): Boolean { + when (url.scheme) { + "http", "https" -> { + return false + } + + else -> { + binding.root.longSnackbar(R.string.jump_to_another_app, R.string.confirm) { + context?.openUrl(url) + } + return true + } + } + } + @SuppressLint("WebViewClientOnReceivedSslError") override fun onReceivedSslError( view: WebView?, diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 17f6e5541..5ddaffa4f 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -1141,4 +1141,7 @@ 清除成功,3秒后自动重启应用 按键长按翻页 保存日志 + 保存堆转储 + 当应用发生OOM崩溃时保存堆转储 + 记录堆转储 diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index b14c6eb62..c4f551b3b 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -1144,4 +1144,7 @@ 清除成功,3秒后自动重启应用 按键长按翻页 保存日志 + 保存堆转储 + 当应用发生OOM崩溃时保存堆转储 + 记录堆转储 diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 43f5a9977..b332cd502 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1144,4 +1144,7 @@ 清除成功,3秒后自动重启应用 按键长按翻页 保存日志 + 保存堆转储 + 当应用发生OOM崩溃时保存堆转储 + 记录堆转储 diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 61f30b986..58b6b3183 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1140,4 +1140,7 @@ Còn 清除成功,3秒后自动重启应用 按键长按翻页 保存日志 + 保存堆转储 + 当应用发生OOM崩溃时保存堆转储 + 记录堆转储 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 790c313b9..6bc349ff5 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -1141,4 +1141,7 @@ 清除成功,3秒后自动重启应用 按键长按翻页 保存日志 + 保存堆转储 + 当应用发生OOM崩溃时保存堆转储 + 记录堆转储 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index e76d31613..a37b85635 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1143,4 +1143,7 @@ 清除成功,3秒后自动重启应用 按键长按翻页 保存日志 + 保存堆转储 + 当应用发生OOM崩溃时保存堆转储 + 记录堆转储 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 6d35fc548..21567afd6 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1143,4 +1143,7 @@ 清除成功,3秒后自动重启应用 按键长按翻页 保存日志 + 保存堆转储 + 当应用发生OOM崩溃时保存堆转储 + 记录堆转储 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3743f69dc..2e4193bda 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1144,4 +1144,7 @@ Cleared successfully, automatically restarts the application after 3 seconds Press and hold the key to turn the page 保存日志 + 保存堆转储 + 当应用发生OOM崩溃时保存堆转储 + 记录堆转储 diff --git a/app/src/main/res/xml/about.xml b/app/src/main/res/xml/about.xml index 63f543d33..513b80b31 100644 --- a/app/src/main/res/xml/about.xml +++ b/app/src/main/res/xml/about.xml @@ -39,6 +39,11 @@ android:title="@string/save_log" app:iconSpaceReserved="false" /> + + + + diff --git a/modules/rhino1.7.3/src/main/java/com/script/CompiledScript.kt b/modules/rhino1.7.3/src/main/java/com/script/CompiledScript.kt index 29a056d83..30c206621 100644 --- a/modules/rhino1.7.3/src/main/java/com/script/CompiledScript.kt +++ b/modules/rhino1.7.3/src/main/java/com/script/CompiledScript.kt @@ -3,6 +3,8 @@ */ package com.script +import org.mozilla.javascript.Scriptable + abstract class CompiledScript { abstract fun getEngine(): ScriptEngine @@ -10,6 +12,9 @@ abstract class CompiledScript { @Throws(ScriptException::class) abstract fun eval(context: ScriptContext): Any? + @Throws(ScriptException::class) + abstract fun eval(scope: Scriptable): Any? + @Throws(ScriptException::class) fun eval(bindings: Bindings?): Any? { var ctxt = getEngine().context diff --git a/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoCompiledScript.kt b/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoCompiledScript.kt index 386f91c5c..a6c8ae740 100644 --- a/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoCompiledScript.kt +++ b/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoCompiledScript.kt @@ -69,4 +69,26 @@ internal class RhinoCompiledScript( return result } + override fun eval(scope: Scriptable): Any? { + val cx = Context.enter() + val result: Any? + try { + val ret = script.exec(cx, scope) + result = engine.unwrapReturnValue(ret) + } catch (re: RhinoException) { + val line = if (re.lineNumber() == 0) -1 else re.lineNumber() + val msg: String = if (re is JavaScriptException) { + re.value.toString() + } else { + re.toString() + } + val se = ScriptException(msg, re.sourceName(), line) + se.initCause(re) + throw se + } finally { + Context.exit() + } + return result + } + } \ No newline at end of file