Horis 2 лет назад
Родитель
Сommit
bc49727189

+ 1 - 0
app/src/main/java/io/legado/app/constant/PreferKey.kt

@@ -144,6 +144,7 @@ object PreferKey {
     const val volumeKeyPage = "volumeKeyPage"
     const val volumeKeyPage = "volumeKeyPage"
     const val volumeKeyPageOnPlay = "volumeKeyPageOnPlay"
     const val volumeKeyPageOnPlay = "volumeKeyPageOnPlay"
     const val mouseWheelPage = "mouseWheelPage"
     const val mouseWheelPage = "mouseWheelPage"
+    const val recordHeapDump = "recordHeapDump"
 
 
     const val cPrimary = "colorPrimary"
     const val cPrimary = "colorPrimary"
     const val cAccent = "colorAccent"
     const val cAccent = "colorAccent"

+ 30 - 2
app/src/main/java/io/legado/app/help/CrashHandler.kt

@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
 import android.content.Context
 import android.content.Context
 import android.net.Uri
 import android.net.Uri
 import android.os.Build
 import android.os.Build
+import android.os.Debug
 import android.os.Looper
 import android.os.Looper
 import android.webkit.WebSettings
 import android.webkit.WebSettings
 import io.legado.app.constant.AppConst
 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.AppConfig
 import io.legado.app.help.config.LocalConfig
 import io.legado.app.help.config.LocalConfig
 import io.legado.app.model.ReadAloud
 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 splitties.init.appCtx
 import java.io.PrintWriter
 import java.io.PrintWriter
 import java.io.StringWriter
 import java.io.StringWriter
 import java.text.SimpleDateFormat
 import java.text.SimpleDateFormat
-import java.util.*
+import java.util.Date
 import java.util.concurrent.TimeUnit
 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
         LocalConfig.appCrash = true
         //保存日志文件
         //保存日志文件
         saveCrashInfo2File(ex)
         saveCrashInfo2File(ex)
+        if (ex is OutOfMemoryError && AppConfig.recordHeapDump) {
+            doHeapDump()
+        }
         context.longToastOnUiLegacy(ex.stackTraceStr)
         context.longToastOnUiLegacy(ex.stackTraceStr)
         Thread.sleep(3000)
         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)
+        }
+
     }
     }
 
 
 }
 }

+ 2 - 0
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 recordLog get() = appCtx.getPrefBoolean(PreferKey.recordLog)
 
 
+    val recordHeapDump get() = appCtx.getPrefBoolean(PreferKey.recordHeapDump, false)
+
     val loadCoverOnlyWifi get() = appCtx.getPrefBoolean(PreferKey.loadCoverOnlyWifi, false)
     val loadCoverOnlyWifi get() = appCtx.getPrefBoolean(PreferKey.loadCoverOnlyWifi, false)
 
 
     val showAddToShelfAlert get() = appCtx.getPrefBoolean(PreferKey.showAddToShelfAlert, true)
     val showAddToShelfAlert get() = appCtx.getPrefBoolean(PreferKey.showAddToShelfAlert, true)

+ 42 - 0
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.AppConst.appInfo
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppLog
 import io.legado.app.help.AppUpdate
 import io.legado.app.help.AppUpdate
+import io.legado.app.help.CrashHandler
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.ui.widget.dialog.TextDialog
 import io.legado.app.ui.widget.dialog.TextDialog
@@ -61,6 +62,7 @@ class AboutFragment : PreferenceFragmentCompat() {
             "gzGzh" -> requireContext().sendToClip(getString(R.string.legado_gzh))
             "gzGzh" -> requireContext().sendToClip(getString(R.string.legado_gzh))
             "crashLog" -> showDialogFragment<CrashLogsDialog>()
             "crashLog" -> showDialogFragment<CrashLogsDialog>()
             "saveLog" -> saveLog()
             "saveLog" -> saveLog()
+            "saveHeapDump" -> saveHeapDump()
         }
         }
         return super.onPreferenceTreeClick(preference)
         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("已保存至备份目录")
             toastOnUi("已保存至备份目录")
         }.onError {
         }.onError {
             AppLog.put("保存日志出错\n${it.localizedMessage}", it, true)
             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)
+        }
+    }
+
 }
 }

+ 38 - 2
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.annotation.SuppressLint
 import android.graphics.Bitmap
 import android.graphics.Bitmap
+import android.net.Uri
 import android.net.http.SslError
 import android.net.http.SslError
 import android.os.Bundle
 import android.os.Bundle
 import android.view.Menu
 import android.view.Menu
 import android.view.MenuItem
 import android.view.MenuItem
 import android.view.View
 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 androidx.fragment.app.activityViewModels
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.base.BaseFragment
 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.databinding.FragmentWebViewLoginBinding
 import io.legado.app.help.http.CookieStore
 import io.legado.app.help.http.CookieStore
 import io.legado.app.lib.theme.accentColor
 import io.legado.app.lib.theme.accentColor
-import io.legado.app.utils.gone
 import io.legado.app.utils.NetworkUtils
 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.snackbar
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 
 
@@ -89,6 +98,33 @@ class WebViewLoginFragment : BaseFragment(R.layout.fragment_web_view_login) {
                 super.onPageFinished(view, url)
                 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")
             @SuppressLint("WebViewClientOnReceivedSslError")
             override fun onReceivedSslError(
             override fun onReceivedSslError(
                 view: WebView?,
                 view: WebView?,

+ 3 - 0
app/src/main/res/values-es-rES/strings.xml

@@ -1141,4 +1141,7 @@
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="save_log">保存日志</string>
     <string name="save_log">保存日志</string>
+    <string name="save_heap_dump">保存堆转储</string>
+    <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
+    <string name="record_heap_dump_t">记录堆转储</string>
 </resources>
 </resources>

+ 3 - 0
app/src/main/res/values-ja-rJP/strings.xml

@@ -1144,4 +1144,7 @@
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="save_log">保存日志</string>
     <string name="save_log">保存日志</string>
+    <string name="save_heap_dump">保存堆转储</string>
+    <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
+    <string name="record_heap_dump_t">记录堆转储</string>
 </resources>
 </resources>

+ 3 - 0
app/src/main/res/values-pt-rBR/strings.xml

@@ -1144,4 +1144,7 @@
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="save_log">保存日志</string>
     <string name="save_log">保存日志</string>
+    <string name="save_heap_dump">保存堆转储</string>
+    <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
+    <string name="record_heap_dump_t">记录堆转储</string>
 </resources>
 </resources>

+ 3 - 0
app/src/main/res/values-vi/strings.xml

@@ -1140,4 +1140,7 @@ Còn </string>
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="save_log">保存日志</string>
     <string name="save_log">保存日志</string>
+    <string name="save_heap_dump">保存堆转储</string>
+    <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
+    <string name="record_heap_dump_t">记录堆转储</string>
 </resources>
 </resources>

+ 3 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -1141,4 +1141,7 @@
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="save_log">保存日志</string>
     <string name="save_log">保存日志</string>
+    <string name="save_heap_dump">保存堆转储</string>
+    <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
+    <string name="record_heap_dump_t">记录堆转储</string>
 </resources>
 </resources>

+ 3 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -1143,4 +1143,7 @@
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="save_log">保存日志</string>
     <string name="save_log">保存日志</string>
+    <string name="save_heap_dump">保存堆转储</string>
+    <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
+    <string name="record_heap_dump_t">记录堆转储</string>
 </resources>
 </resources>

+ 3 - 0
app/src/main/res/values-zh/strings.xml

@@ -1143,4 +1143,7 @@
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="clear_webview_data_success">清除成功,3秒后自动重启应用</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="save_log">保存日志</string>
     <string name="save_log">保存日志</string>
+    <string name="save_heap_dump">保存堆转储</string>
+    <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
+    <string name="record_heap_dump_t">记录堆转储</string>
 </resources>
 </resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -1144,4 +1144,7 @@
     <string name="clear_webview_data_success">Cleared successfully, automatically restarts the application after 3 seconds</string>
     <string name="clear_webview_data_success">Cleared successfully, automatically restarts the application after 3 seconds</string>
     <string name="key_page_on_long_press">Press and hold the key to turn the page</string>
     <string name="key_page_on_long_press">Press and hold the key to turn the page</string>
     <string name="save_log">保存日志</string>
     <string name="save_log">保存日志</string>
+    <string name="save_heap_dump">保存堆转储</string>
+    <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
+    <string name="record_heap_dump_t">记录堆转储</string>
 </resources>
 </resources>

+ 5 - 0
app/src/main/res/xml/about.xml

@@ -39,6 +39,11 @@
             android:title="@string/save_log"
             android:title="@string/save_log"
             app:iconSpaceReserved="false" />
             app:iconSpaceReserved="false" />
 
 
+        <io.legado.app.lib.prefs.Preference
+            android:key="saveHeapDump"
+            android:title="@string/save_heap_dump"
+            app:iconSpaceReserved="false" />
+
         <io.legado.app.lib.prefs.Preference
         <io.legado.app.lib.prefs.Preference
             android:key="privacyPolicy"
             android:key="privacyPolicy"
             android:title="@string/privacy_policy"
             android:title="@string/privacy_policy"

+ 7 - 0
app/src/main/res/xml/pref_config_other.xml

@@ -188,6 +188,13 @@
             android:title="@string/record_log"
             android:title="@string/record_log"
             app:iconSpaceReserved="false" />
             app:iconSpaceReserved="false" />
 
 
+        <io.legado.app.lib.prefs.SwitchPreference
+            android:defaultValue="false"
+            android:key="recordHeapDump"
+            android:summary="@string/record_heap_dump_s"
+            android:title="@string/record_heap_dump_t"
+            app:iconSpaceReserved="false" />
+
     </io.legado.app.lib.prefs.PreferenceCategory>
     </io.legado.app.lib.prefs.PreferenceCategory>
 
 
 
 

+ 5 - 0
modules/rhino1.7.3/src/main/java/com/script/CompiledScript.kt

@@ -3,6 +3,8 @@
  */
  */
 package com.script
 package com.script
 
 
+import org.mozilla.javascript.Scriptable
+
 abstract class CompiledScript {
 abstract class CompiledScript {
 
 
     abstract fun getEngine(): ScriptEngine
     abstract fun getEngine(): ScriptEngine
@@ -10,6 +12,9 @@ abstract class CompiledScript {
     @Throws(ScriptException::class)
     @Throws(ScriptException::class)
     abstract fun eval(context: ScriptContext): Any?
     abstract fun eval(context: ScriptContext): Any?
 
 
+    @Throws(ScriptException::class)
+    abstract fun eval(scope: Scriptable): Any?
+
     @Throws(ScriptException::class)
     @Throws(ScriptException::class)
     fun eval(bindings: Bindings?): Any? {
     fun eval(bindings: Bindings?): Any? {
         var ctxt = getEngine().context
         var ctxt = getEngine().context

+ 22 - 0
modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoCompiledScript.kt

@@ -69,4 +69,26 @@ internal class RhinoCompiledScript(
         return result
         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
+    }
+
 }
 }