mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Migrate to Kotlin - DevSupportManagerBase.kt (#50559)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50559 This diff migrates the following file to Kotlin - DevSupportManagerBase.kt as part of our ongoing effort of migrating the codebase to Kotlin This file was 1000LOC+ so its migration is quite involved. I had to look into nullability of various parameters. Most notably the `Array<StackFrame?>` -> `Array<StackFrame>`. This also is the last file left in the devsupport package that needs to be migrated to Kotlin. Changelog: [Android] [Breaking] - DevSupportManagerBase is now converted to Kotlin. If you're subclassing this class, you will have to adjust some of the parameters as types have changed during the migration. Reviewed By: javache, mdvacca Differential Revision: D72637098 fbshipit-source-id: b87d5f17e30d69fe9c09ec9b49f96818df34ea3b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
dd0cd07405
commit
9da485b54c
@@ -1996,35 +1996,35 @@ public abstract interface class com/facebook/react/devsupport/DevServerHelper$Pa
|
||||
}
|
||||
|
||||
public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/facebook/react/devsupport/interfaces/DevSupportManager {
|
||||
protected final field mReactInstanceDevHelper Lcom/facebook/react/devsupport/ReactInstanceDevHelper;
|
||||
public static final field Companion Lcom/facebook/react/devsupport/DevSupportManagerBase$Companion;
|
||||
public fun <init> (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;)V
|
||||
public fun addCustomDevOption (Ljava/lang/String;Lcom/facebook/react/devsupport/interfaces/DevOptionHandler;)V
|
||||
public fun createRootView (Ljava/lang/String;)Landroid/view/View;
|
||||
public fun createSurfaceDelegate (Ljava/lang/String;)Lcom/facebook/react/common/SurfaceDelegate;
|
||||
public fun destroyRootView (Landroid/view/View;)V
|
||||
public fun downloadBundleResourceFromUrlSync (Ljava/lang/String;Ljava/io/File;)Ljava/io/File;
|
||||
public fun fetchSplitBundleAndCreateBundleLoader (Ljava/lang/String;Lcom/facebook/react/devsupport/DevSupportManagerBase$CallbackWithBundleLoader;)V
|
||||
protected fun getApplicationContext ()Landroid/content/Context;
|
||||
public final fun fetchSplitBundleAndCreateBundleLoader (Ljava/lang/String;Lcom/facebook/react/devsupport/DevSupportManagerBase$CallbackWithBundleLoader;)V
|
||||
protected final fun getApplicationContext ()Landroid/content/Context;
|
||||
public fun getCurrentActivity ()Landroid/app/Activity;
|
||||
public fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
|
||||
public fun getDevLoadingViewManager ()Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;
|
||||
public fun getDevServerHelper ()Lcom/facebook/react/devsupport/DevServerHelper;
|
||||
public fun getDevSettings ()Lcom/facebook/react/modules/debug/interfaces/DeveloperSettings;
|
||||
public fun getDevSupportEnabled ()Z
|
||||
public final fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
|
||||
public final fun getDevLoadingViewManager ()Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;
|
||||
public final fun getDevServerHelper ()Lcom/facebook/react/devsupport/DevServerHelper;
|
||||
public final fun getDevSettings ()Lcom/facebook/react/modules/debug/interfaces/DeveloperSettings;
|
||||
public final fun getDevSupportEnabled ()Z
|
||||
public fun getDownloadedJSBundleFile ()Ljava/lang/String;
|
||||
public fun getJSAppBundleName ()Ljava/lang/String;
|
||||
public fun getLastErrorCookie ()I
|
||||
public fun getLastErrorStack ()[Lcom/facebook/react/devsupport/interfaces/StackFrame;
|
||||
public fun getLastErrorTitle ()Ljava/lang/String;
|
||||
public fun getLastErrorType ()Lcom/facebook/react/devsupport/interfaces/ErrorType;
|
||||
public fun getReactInstanceDevHelper ()Lcom/facebook/react/devsupport/ReactInstanceDevHelper;
|
||||
public final fun getJSAppBundleName ()Ljava/lang/String;
|
||||
public final fun getLastErrorCookie ()I
|
||||
public final fun getLastErrorStack ()[Lcom/facebook/react/devsupport/interfaces/StackFrame;
|
||||
public final fun getLastErrorTitle ()Ljava/lang/String;
|
||||
public final fun getLastErrorType ()Lcom/facebook/react/devsupport/interfaces/ErrorType;
|
||||
public final fun getReactInstanceDevHelper ()Lcom/facebook/react/devsupport/ReactInstanceDevHelper;
|
||||
public fun getRedBoxHandler ()Lcom/facebook/react/devsupport/interfaces/RedBoxHandler;
|
||||
public fun getSourceMapUrl ()Ljava/lang/String;
|
||||
public fun getSourceUrl ()Ljava/lang/String;
|
||||
protected abstract fun getUniqueTag ()Ljava/lang/String;
|
||||
public fun handleException (Ljava/lang/Exception;)V
|
||||
public fun hasUpToDateJSBundleInCache ()Z
|
||||
protected fun hideDevLoadingView ()V
|
||||
protected final fun hideDevLoadingView ()V
|
||||
public fun hidePausedInDebuggerOverlay ()V
|
||||
public fun hideRedboxDialog ()V
|
||||
public fun isPackagerRunning (Lcom/facebook/react/devsupport/interfaces/PackagerStatusCallback;)V
|
||||
@@ -2036,11 +2036,16 @@ public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/
|
||||
public fun reloadJSFromServer (Ljava/lang/String;Lcom/facebook/react/devsupport/interfaces/BundleLoadCallback;)V
|
||||
public fun reloadSettings ()V
|
||||
public fun setAdditionalOptionForPackager (Ljava/lang/String;Ljava/lang/String;)V
|
||||
public fun setDevSupportEnabled (Z)V
|
||||
public final fun setDevLoadingViewManager (Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;)V
|
||||
public final fun setDevSupportEnabled (Z)V
|
||||
public fun setFpsDebugEnabled (Z)V
|
||||
public fun setHotModuleReplacementEnabled (Z)V
|
||||
public final fun setLastErrorCookie (I)V
|
||||
public final fun setLastErrorStack ([Lcom/facebook/react/devsupport/interfaces/StackFrame;)V
|
||||
public final fun setLastErrorTitle (Ljava/lang/String;)V
|
||||
public final fun setLastErrorType (Lcom/facebook/react/devsupport/interfaces/ErrorType;)V
|
||||
public fun setPackagerLocationCustomizer (Lcom/facebook/react/devsupport/interfaces/DevSupportManager$PackagerLocationCustomizer;)V
|
||||
protected fun showDevLoadingViewForRemoteJSEnabled ()V
|
||||
protected final fun showDevLoadingViewForRemoteJSEnabled ()V
|
||||
public fun showDevOptionsDialog ()V
|
||||
public fun showNewJSError (Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;I)V
|
||||
public fun showNewJavaError (Ljava/lang/String;Ljava/lang/Throwable;)V
|
||||
@@ -2055,6 +2060,9 @@ public abstract interface class com/facebook/react/devsupport/DevSupportManagerB
|
||||
public abstract fun onSuccess (Lcom/facebook/react/bridge/JSBundleLoader;)V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/devsupport/DevSupportManagerBase$Companion {
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/devsupport/DevSupportManagerFactory {
|
||||
public abstract fun create (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;)Lcom/facebook/react/devsupport/interfaces/DevSupportManager;
|
||||
public abstract fun create (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;Z)Lcom/facebook/react/devsupport/interfaces/DevSupportManager;
|
||||
|
||||
+3
-2
@@ -56,7 +56,7 @@ public class BridgeDevSupportManager(
|
||||
redBoxHandler: RedBoxHandler?,
|
||||
devBundleDownloadListener: DevBundleDownloadListener?,
|
||||
minNumShakes: Int,
|
||||
customPackagerCommandHandlers: Map<String?, RequestHandler?>?,
|
||||
customPackagerCommandHandlers: Map<String, RequestHandler>?,
|
||||
surfaceDelegateFactory: SurfaceDelegateFactory?,
|
||||
devLoadingViewManager: DevLoadingViewManager?,
|
||||
pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?
|
||||
@@ -74,7 +74,8 @@ public class BridgeDevSupportManager(
|
||||
devLoadingViewManager,
|
||||
pausedInDebuggerOverlayManager) {
|
||||
|
||||
override fun getUniqueTag(): String = "Bridge"
|
||||
override val uniqueTag: String
|
||||
get() = "Bridge"
|
||||
|
||||
override fun loadSplitBundleFromServer(bundlePath: String, callback: DevSplitBundleCallback) {
|
||||
fetchSplitBundleAndCreateBundleLoader(
|
||||
|
||||
+5
-4
@@ -71,7 +71,8 @@ internal class BridgelessDevSupportManager(
|
||||
devLoadingViewManager = null,
|
||||
pausedInDebuggerOverlayManager = null)
|
||||
|
||||
override fun getUniqueTag(): String = "Bridgeless"
|
||||
override val uniqueTag: String
|
||||
get() = "Bridgeless"
|
||||
|
||||
override fun loadSplitBundleFromServer(bundlePath: String, callback: DevSplitBundleCallback) {
|
||||
fetchSplitBundleAndCreateBundleLoader(
|
||||
@@ -79,9 +80,9 @@ internal class BridgelessDevSupportManager(
|
||||
object : CallbackWithBundleLoader {
|
||||
override fun onSuccess(bundleLoader: JSBundleLoader) {
|
||||
try {
|
||||
mReactInstanceDevHelper.loadBundle(bundleLoader).waitForCompletion()
|
||||
reactInstanceDevHelper.loadBundle(bundleLoader).waitForCompletion()
|
||||
val bundleURL = devServerHelper.getDevServerSplitBundleURL(bundlePath)
|
||||
val reactContext = mReactInstanceDevHelper.currentReactContext
|
||||
val reactContext = reactInstanceDevHelper.currentReactContext
|
||||
reactContext?.getJSModule(HMRClient::class.java)?.registerBundle(bundleURL)
|
||||
callback.onSuccess()
|
||||
} catch (e: InterruptedException) {
|
||||
@@ -99,6 +100,6 @@ internal class BridgelessDevSupportManager(
|
||||
UiThreadUtil.assertOnUiThread()
|
||||
// dismiss redbox if exists
|
||||
hideRedboxDialog()
|
||||
mReactInstanceDevHelper.reload("BridgelessDevSupportManager.handleReloadJS()")
|
||||
reactInstanceDevHelper.reload("BridgelessDevSupportManager.handleReloadJS()")
|
||||
}
|
||||
}
|
||||
|
||||
-1144
File diff suppressed because it is too large
Load Diff
+913
@@ -0,0 +1,913 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.devsupport
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.ActivityManager
|
||||
import android.app.AlertDialog
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Typeface
|
||||
import android.hardware.SensorManager
|
||||
import android.os.Build
|
||||
import android.util.Pair
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ListAdapter
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.core.util.Supplier
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.R
|
||||
import com.facebook.react.bridge.DefaultJSExceptionHandler
|
||||
import com.facebook.react.bridge.JSBundleLoader
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.bridge.ReactMarker
|
||||
import com.facebook.react.bridge.ReactMarkerConstants
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.UiThreadUtil
|
||||
import com.facebook.react.common.DebugServerException
|
||||
import com.facebook.react.common.JavascriptException
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import com.facebook.react.common.ShakeDetector
|
||||
import com.facebook.react.common.SurfaceDelegate
|
||||
import com.facebook.react.common.SurfaceDelegateFactory
|
||||
import com.facebook.react.devsupport.DebugOverlayController.Companion.requestPermission
|
||||
import com.facebook.react.devsupport.DevServerHelper.PackagerCommandListener
|
||||
import com.facebook.react.devsupport.InspectorFlags.getFuseboxEnabled
|
||||
import com.facebook.react.devsupport.StackTraceHelper.convertJavaStackTrace
|
||||
import com.facebook.react.devsupport.StackTraceHelper.convertJsStackTrace
|
||||
import com.facebook.react.devsupport.interfaces.BundleLoadCallback
|
||||
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener
|
||||
import com.facebook.react.devsupport.interfaces.DevLoadingViewManager
|
||||
import com.facebook.react.devsupport.interfaces.DevOptionHandler
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager.PackagerLocationCustomizer
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager.PausedInDebuggerOverlayCommandListener
|
||||
import com.facebook.react.devsupport.interfaces.ErrorCustomizer
|
||||
import com.facebook.react.devsupport.interfaces.ErrorType
|
||||
import com.facebook.react.devsupport.interfaces.PackagerStatusCallback
|
||||
import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager
|
||||
import com.facebook.react.devsupport.interfaces.RedBoxHandler
|
||||
import com.facebook.react.devsupport.interfaces.StackFrame
|
||||
import com.facebook.react.modules.core.RCTNativeAppEventEmitter
|
||||
import com.facebook.react.modules.debug.interfaces.DeveloperSettings
|
||||
import com.facebook.react.packagerconnection.RequestHandler
|
||||
import java.io.File
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import java.util.Locale
|
||||
|
||||
public abstract class DevSupportManagerBase(
|
||||
protected val applicationContext: Context,
|
||||
public val reactInstanceDevHelper: ReactInstanceDevHelper,
|
||||
@get:JvmName("getJSAppBundleName") public val jsAppBundleName: String?,
|
||||
enableOnCreate: Boolean,
|
||||
public override val redBoxHandler: RedBoxHandler?,
|
||||
private val devBundleDownloadListener: DevBundleDownloadListener?,
|
||||
minNumShakes: Int,
|
||||
private val customPackagerCommandHandlers: Map<String, RequestHandler>?,
|
||||
private val surfaceDelegateFactory: SurfaceDelegateFactory?,
|
||||
public var devLoadingViewManager: DevLoadingViewManager?,
|
||||
private var pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?
|
||||
) : DevSupportManager {
|
||||
public interface CallbackWithBundleLoader {
|
||||
public fun onSuccess(bundleLoader: JSBundleLoader)
|
||||
|
||||
public fun onError(url: String, cause: Throwable)
|
||||
}
|
||||
|
||||
protected abstract val uniqueTag: String
|
||||
|
||||
public final override var currentReactContext: ReactContext? = null
|
||||
private set
|
||||
|
||||
public final override val devSettings: DeveloperSettings =
|
||||
DevInternalSettings(
|
||||
applicationContext,
|
||||
object : DevInternalSettings.Listener {
|
||||
override fun onInternalSettingsChanged() {
|
||||
this@DevSupportManagerBase.reloadSettings()
|
||||
}
|
||||
})
|
||||
|
||||
override val currentActivity: Activity?
|
||||
get() = reactInstanceDevHelper.currentActivity
|
||||
|
||||
/**
|
||||
* [com.facebook.react.ReactInstanceManager] is responsible for enabling/disabling dev support
|
||||
* when a React view is attached/detached or when application state changes (e.g. the application
|
||||
* is backgrounded).
|
||||
*/
|
||||
final override var devSupportEnabled: Boolean
|
||||
get() = isDevSupportEnabled
|
||||
set(isDevSupportEnabled) {
|
||||
this.isDevSupportEnabled = isDevSupportEnabled
|
||||
reloadSettings()
|
||||
}
|
||||
|
||||
override val sourceMapUrl: String
|
||||
get() = jsAppBundleName?.let { devServerHelper.getSourceMapUrl(it) } ?: ""
|
||||
|
||||
override val sourceUrl: String
|
||||
get() = jsAppBundleName?.let { devServerHelper.getSourceUrl(it) } ?: ""
|
||||
|
||||
override val downloadedJSBundleFile: String
|
||||
get() = jsBundleDownloadedFile.absolutePath
|
||||
|
||||
public val devServerHelper: DevServerHelper =
|
||||
DevServerHelper(devSettings, applicationContext, devSettings.packagerConnectionSettings)
|
||||
|
||||
public final override var lastErrorTitle: String? = null
|
||||
public final override var lastErrorStack: Array<StackFrame>? = null
|
||||
public final override var lastErrorType: ErrorType? = null
|
||||
public final override var lastErrorCookie: Int = 0
|
||||
|
||||
// Prepare shake gesture detector (will be started/stopped from #reload)
|
||||
private val shakeDetector: ShakeDetector =
|
||||
ShakeDetector({ this.showDevOptionsDialog() }, minNumShakes)
|
||||
|
||||
// Prepare reload APP broadcast receiver (will be registered/unregistered from #reload)
|
||||
private val reloadAppBroadcastReceiver: BroadcastReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action
|
||||
if (getReloadAppAction(context) == action) {
|
||||
handleReloadJS()
|
||||
}
|
||||
}
|
||||
}
|
||||
private val customDevOptions = LinkedHashMap<String, DevOptionHandler>()
|
||||
private val jsBundleDownloadedFile: File
|
||||
private val jsSplitBundlesDir: File
|
||||
private val defaultJSExceptionHandler: DefaultJSExceptionHandler = DefaultJSExceptionHandler()
|
||||
private var redBoxSurfaceDelegate: SurfaceDelegate? = null
|
||||
private var devOptionsDialog: AlertDialog? = null
|
||||
private var debugOverlayController: DebugOverlayController? = null
|
||||
private var devLoadingViewVisible = false
|
||||
private var pendingJSSplitBundleRequests = 0
|
||||
private var isReceiverRegistered = false
|
||||
private var isShakeDetectorStarted = false
|
||||
private var isDevSupportEnabled = false
|
||||
private var isPackagerConnected = false
|
||||
private val errorCustomizers: MutableList<ErrorCustomizer> = mutableListOf()
|
||||
private var packagerLocationCustomizer: PackagerLocationCustomizer? = null
|
||||
private val jSExecutorDescription: String?
|
||||
get() =
|
||||
try {
|
||||
reactInstanceDevHelper.javaScriptExecutorFactory.toString()
|
||||
} catch (e: IllegalStateException) {
|
||||
null
|
||||
}
|
||||
|
||||
init {
|
||||
// We store JS bundle loaded from dev server in a single destination in app's data dir.
|
||||
// In case when someone schedule 2 subsequent reloads it may happen that JS thread will
|
||||
// start reading first reload output while the second reload starts writing to the same
|
||||
// file. As this should only be the case in dev mode we leave it as it is.
|
||||
// TODO(6418010): Fix readers-writers problem in debug reload from HTTP server
|
||||
val subclassTag = uniqueTag
|
||||
val bundleFile = subclassTag + "ReactNativeDevBundle.js"
|
||||
jsBundleDownloadedFile = File(applicationContext.filesDir, bundleFile)
|
||||
val splitBundlesDir = subclassTag.lowercase() + "_dev_js_split_bundles"
|
||||
jsSplitBundlesDir = applicationContext.getDir(splitBundlesDir, Context.MODE_PRIVATE)
|
||||
devSupportEnabled = enableOnCreate
|
||||
if (devLoadingViewManager == null) {
|
||||
devLoadingViewManager = DefaultDevLoadingViewImplementation(reactInstanceDevHelper)
|
||||
}
|
||||
if (pausedInDebuggerOverlayManager == null) {
|
||||
pausedInDebuggerOverlayManager =
|
||||
PausedInDebuggerOverlayDialogManager(
|
||||
Supplier {
|
||||
val context = reactInstanceDevHelper.currentActivity
|
||||
if (context == null || context.isFinishing) {
|
||||
return@Supplier null
|
||||
}
|
||||
context
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleException(e: Exception) {
|
||||
if (isDevSupportEnabled) {
|
||||
logJSException(e)
|
||||
} else {
|
||||
defaultJSExceptionHandler.handleException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showNewJavaError(message: String?, e: Throwable) {
|
||||
FLog.e(ReactConstants.TAG, "Exception in native call", e)
|
||||
showNewError(message, convertJavaStackTrace(e), JAVA_ERROR_COOKIE, ErrorType.NATIVE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add option item to dev settings dialog displayed by this manager. In the case user select given
|
||||
* option from that dialog, the appropriate handler passed as {@param optionHandler} will be
|
||||
* called.
|
||||
*/
|
||||
override fun addCustomDevOption(optionName: String, optionHandler: DevOptionHandler) {
|
||||
customDevOptions[optionName] = optionHandler
|
||||
}
|
||||
|
||||
override fun showNewJSError(message: String?, details: ReadableArray?, errorCookie: Int) {
|
||||
showNewError(message, convertJsStackTrace(details), errorCookie, ErrorType.JS)
|
||||
}
|
||||
|
||||
override fun registerErrorCustomizer(errorCustomizer: ErrorCustomizer) {
|
||||
errorCustomizers.add(errorCustomizer)
|
||||
}
|
||||
|
||||
override fun processErrorCustomizers(
|
||||
errorInfo: Pair<String, Array<StackFrame>>
|
||||
): Pair<String, Array<StackFrame>> {
|
||||
var errorInfoLocal = errorInfo
|
||||
for (errorCustomizer in errorCustomizers) {
|
||||
errorInfoLocal = errorCustomizer.customizeErrorInfo(errorInfo)
|
||||
}
|
||||
return errorInfoLocal
|
||||
}
|
||||
|
||||
override fun hideRedboxDialog() {
|
||||
redBoxSurfaceDelegate?.hide()
|
||||
}
|
||||
|
||||
override fun createRootView(appKey: String): View? = reactInstanceDevHelper.createRootView(appKey)
|
||||
|
||||
override fun destroyRootView(rootView: View?) {
|
||||
rootView?.let { reactInstanceDevHelper.destroyRootView(it) }
|
||||
}
|
||||
|
||||
private fun logJSException(e: Exception) {
|
||||
val message = StringBuilder(e.message ?: "Exception in native call from JS")
|
||||
var cause = e.cause
|
||||
while (cause != null) {
|
||||
message.append("\n\n").append(cause.message)
|
||||
cause = cause.cause
|
||||
}
|
||||
if (e is JavascriptException) {
|
||||
FLog.e(ReactConstants.TAG, "Exception in native call from JS", e)
|
||||
showNewError(e.message, arrayOf(), JSEXCEPTION_ERROR_COOKIE, ErrorType.JS)
|
||||
} else {
|
||||
showNewJavaError(message.toString(), e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideDevOptionsDialog() {
|
||||
devOptionsDialog?.dismiss()
|
||||
devOptionsDialog = null
|
||||
}
|
||||
|
||||
private fun showNewError(
|
||||
message: String?,
|
||||
stack: Array<StackFrame>,
|
||||
errorCookie: Int,
|
||||
errorType: ErrorType
|
||||
) {
|
||||
UiThreadUtil.runOnUiThread {
|
||||
// Keep a copy of the latest error to be shown by the RedBoxSurface
|
||||
updateLastErrorInfo(message, stack, errorCookie, errorType)
|
||||
|
||||
if (redBoxSurfaceDelegate == null) {
|
||||
this.redBoxSurfaceDelegate =
|
||||
createSurfaceDelegate("RedBox")
|
||||
?: RedBoxDialogSurfaceDelegate(this@DevSupportManagerBase).apply {
|
||||
createContentView("RedBox")
|
||||
}
|
||||
}
|
||||
|
||||
if (redBoxSurfaceDelegate?.isShowing() == true) {
|
||||
// Sometimes errors cause multiple errors to be thrown in JS in quick succession. Only
|
||||
// show the first and most actionable one.
|
||||
return@runOnUiThread
|
||||
}
|
||||
redBoxSurfaceDelegate?.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showDevOptionsDialog() {
|
||||
if (devOptionsDialog != null || !isDevSupportEnabled || ActivityManager.isUserAMonkey()) {
|
||||
return
|
||||
}
|
||||
val options = LinkedHashMap<String, DevOptionHandler>()
|
||||
val disabledItemKeys: MutableSet<String?> = HashSet() /* register standard options */
|
||||
options[applicationContext.getString(R.string.catalyst_reload)] = DevOptionHandler {
|
||||
if (!devSettings.isJSDevModeEnabled && devSettings.isHotModuleReplacementEnabled) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
applicationContext.getString(R.string.catalyst_hot_reloading_auto_disable),
|
||||
Toast.LENGTH_LONG)
|
||||
.show()
|
||||
devSettings.isHotModuleReplacementEnabled = false
|
||||
}
|
||||
handleReloadJS()
|
||||
}
|
||||
|
||||
if (devSettings.isDeviceDebugEnabled) {
|
||||
// On-device JS debugging (CDP). Render action to open debugger frontend.
|
||||
val isConnected = isPackagerConnected
|
||||
val debuggerItemString =
|
||||
applicationContext.getString(
|
||||
if (isConnected) R.string.catalyst_debug_open
|
||||
else R.string.catalyst_debug_open_disabled)
|
||||
if (!isConnected) {
|
||||
disabledItemKeys.add(debuggerItemString)
|
||||
}
|
||||
options[debuggerItemString] = DevOptionHandler { this.openDebugger() }
|
||||
}
|
||||
|
||||
options[applicationContext.getString(R.string.catalyst_change_bundle_location)] =
|
||||
DevOptionHandler {
|
||||
val context = reactInstanceDevHelper.currentActivity
|
||||
if (context == null || context.isFinishing) {
|
||||
FLog.e(
|
||||
ReactConstants.TAG,
|
||||
"Unable to launch change bundle location because react activity is not available")
|
||||
return@DevOptionHandler
|
||||
}
|
||||
|
||||
val input = EditText(context)
|
||||
input.hint = "localhost:8081"
|
||||
|
||||
val bundleLocationDialog =
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(applicationContext.getString(R.string.catalyst_change_bundle_location))
|
||||
.setView(input)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
val host = input.text.toString()
|
||||
devSettings.packagerConnectionSettings.debugServerHost = host
|
||||
handleReloadJS()
|
||||
}
|
||||
.create()
|
||||
bundleLocationDialog.show()
|
||||
}
|
||||
|
||||
options[applicationContext.getString(R.string.catalyst_inspector_toggle)] = DevOptionHandler {
|
||||
devSettings.isElementInspectorEnabled = !devSettings.isElementInspectorEnabled
|
||||
reactInstanceDevHelper.toggleElementInspector()
|
||||
}
|
||||
|
||||
val hotReloadLabel =
|
||||
if (devSettings.isHotModuleReplacementEnabled) {
|
||||
applicationContext.getString(R.string.catalyst_hot_reloading_stop)
|
||||
} else {
|
||||
applicationContext.getString(R.string.catalyst_hot_reloading)
|
||||
}
|
||||
options[hotReloadLabel] = DevOptionHandler {
|
||||
val nextEnabled = !devSettings.isHotModuleReplacementEnabled
|
||||
devSettings.isHotModuleReplacementEnabled = nextEnabled
|
||||
val reactContext = currentReactContext
|
||||
if (reactContext != null) {
|
||||
if (nextEnabled) {
|
||||
reactContext.getJSModule(HMRClient::class.java)?.enable()
|
||||
} else {
|
||||
reactContext.getJSModule(HMRClient::class.java)?.disable()
|
||||
}
|
||||
}
|
||||
if (nextEnabled && !devSettings.isJSDevModeEnabled) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
applicationContext.getString(R.string.catalyst_hot_reloading_auto_enable),
|
||||
Toast.LENGTH_LONG)
|
||||
.show()
|
||||
devSettings.isJSDevModeEnabled = true
|
||||
handleReloadJS()
|
||||
}
|
||||
}
|
||||
|
||||
val fpsDebugLabel =
|
||||
if (devSettings.isFpsDebugEnabled)
|
||||
applicationContext.getString(R.string.catalyst_perf_monitor_stop)
|
||||
else applicationContext.getString(R.string.catalyst_perf_monitor)
|
||||
options[fpsDebugLabel] = DevOptionHandler {
|
||||
if (!devSettings.isFpsDebugEnabled) {
|
||||
// Request overlay permission if needed when "Show Perf Monitor" option is selected
|
||||
val context: Context? = reactInstanceDevHelper.currentActivity
|
||||
if (context == null) {
|
||||
FLog.e(ReactConstants.TAG, "Unable to get reference to react activity")
|
||||
} else {
|
||||
requestPermission(context)
|
||||
}
|
||||
}
|
||||
devSettings.isFpsDebugEnabled = !devSettings.isFpsDebugEnabled
|
||||
}
|
||||
options[applicationContext.getString(R.string.catalyst_settings)] = DevOptionHandler {
|
||||
val intent = Intent(applicationContext, DevSettingsActivity::class.java)
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
applicationContext.startActivity(intent)
|
||||
}
|
||||
|
||||
if (customDevOptions.isNotEmpty()) {
|
||||
options.putAll(customDevOptions)
|
||||
}
|
||||
val optionHandlers = options.values.toTypedArray<DevOptionHandler>()
|
||||
|
||||
val context = reactInstanceDevHelper.currentActivity
|
||||
if (context == null || context.isFinishing) {
|
||||
FLog.e(
|
||||
ReactConstants.TAG,
|
||||
"Unable to launch dev options menu because react activity " + "isn't available")
|
||||
return
|
||||
}
|
||||
|
||||
val header = LinearLayout(context)
|
||||
header.orientation = LinearLayout.VERTICAL
|
||||
|
||||
TextView(context).apply {
|
||||
text = context.getString(R.string.catalyst_dev_menu_header, uniqueTag)
|
||||
setPadding(0, 50, 0, 0)
|
||||
gravity = Gravity.CENTER
|
||||
textSize = 16f
|
||||
setTypeface(typeface, Typeface.BOLD)
|
||||
header.addView(this)
|
||||
}
|
||||
|
||||
val jsExecutorDescription = jSExecutorDescription
|
||||
|
||||
if (jsExecutorDescription != null) {
|
||||
TextView(context).apply {
|
||||
text = context.getString(R.string.catalyst_dev_menu_sub_header, jsExecutorDescription)
|
||||
setPadding(0, 20, 0, 0)
|
||||
gravity = Gravity.CENTER
|
||||
textSize = 14f
|
||||
header.addView(this)
|
||||
}
|
||||
}
|
||||
|
||||
val adapter: ListAdapter =
|
||||
object :
|
||||
ArrayAdapter<String?>(
|
||||
context, android.R.layout.simple_list_item_1, options.keys.toTypedArray<String>()) {
|
||||
override fun areAllItemsEnabled(): Boolean = false
|
||||
|
||||
override fun isEnabled(position: Int): Boolean =
|
||||
!disabledItemKeys.contains(getItem(position))
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View =
|
||||
super.getView(position, convertView, parent).apply { isEnabled = isEnabled(position) }
|
||||
}
|
||||
|
||||
devOptionsDialog =
|
||||
AlertDialog.Builder(context)
|
||||
.setCustomTitle(header)
|
||||
.setAdapter(adapter) { _: DialogInterface?, which: Int ->
|
||||
optionHandlers[which].onOptionSelected()
|
||||
devOptionsDialog = null
|
||||
}
|
||||
.setOnCancelListener { devOptionsDialog = null }
|
||||
.create()
|
||||
|
||||
devOptionsDialog?.show()
|
||||
|
||||
val reactContext = currentReactContext
|
||||
if (reactContext != null) {
|
||||
reactContext.getJSModule(RCTNativeAppEventEmitter::class.java).emit("RCTDevMenuShown", null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewReactContextCreated(reactContext: ReactContext) {
|
||||
resetCurrentContext(reactContext)
|
||||
}
|
||||
|
||||
override fun onReactInstanceDestroyed(reactContext: ReactContext) {
|
||||
if (reactContext === currentReactContext) {
|
||||
// only call reset context when the destroyed context matches the one that is currently set
|
||||
// for this manager
|
||||
resetCurrentContext(null)
|
||||
}
|
||||
|
||||
// If some JNI types (e.g. jni::HybridClass) are used in JSI (e.g. jsi::HostObject), they might
|
||||
// not be immediately deleted on an app refresh as both Java and JavaScript are
|
||||
// garbage-collected languages and the memory might float around for a while. For C++
|
||||
// developers, this will be hard to debug as destructors might be called at a later point, so in
|
||||
// this case we trigger a Java GC to maybe eagerly collect such objects when the app
|
||||
// reloads.
|
||||
System.gc()
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if [com.facebook.react.ReactInstanceManager] should use downloaded JS bundle
|
||||
* file instead of using JS file from assets. This may happen when app has not been updated
|
||||
* since the last time we fetched the bundle.
|
||||
*/
|
||||
override fun hasUpToDateJSBundleInCache(): Boolean {
|
||||
if (isDevSupportEnabled && jsBundleDownloadedFile.exists()) {
|
||||
try {
|
||||
val packageName = applicationContext.packageName
|
||||
val packageManager = applicationContext.packageManager
|
||||
if (packageManager != null) {
|
||||
val thisPackage = packageManager.getPackageInfo(packageName, 0)
|
||||
if (jsBundleDownloadedFile.lastModified() > thisPackage.lastUpdateTime) {
|
||||
// Base APK has not been updated since we downloaded JS, but if app is using exopackage
|
||||
// it may only be a single dex that has been updated. We check for exopackage dir update
|
||||
// time in that case.
|
||||
val exopackageDir =
|
||||
File(String.format(Locale.US, EXOPACKAGE_LOCATION_FORMAT, packageName))
|
||||
if (exopackageDir.exists()) {
|
||||
return jsBundleDownloadedFile.lastModified() > exopackageDir.lastModified()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
// Ignore this error and just fallback to loading JS from assets
|
||||
FLog.e(ReactConstants.TAG, "DevSupport is unable to get current app info")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun resetCurrentContext(reactContext: ReactContext?) {
|
||||
if (currentReactContext === reactContext) {
|
||||
// new context is the same as the old one - do nothing
|
||||
return
|
||||
}
|
||||
currentReactContext = reactContext
|
||||
|
||||
// Recreate debug overlay controller with new CatalystInstance object
|
||||
debugOverlayController?.setFpsDebugViewVisible(false)
|
||||
reactContext?.let { debugOverlayController = DebugOverlayController(it) }
|
||||
|
||||
if (reactContext != null) {
|
||||
try {
|
||||
val sourceUrl = URL(sourceUrl)
|
||||
var path = sourceUrl.path
|
||||
if (path != null) {
|
||||
path = path.substring(1) // strip initial slash in path
|
||||
}
|
||||
val host = sourceUrl.host
|
||||
val scheme = sourceUrl.protocol
|
||||
val port = if (sourceUrl.port != -1) sourceUrl.port else sourceUrl.defaultPort
|
||||
reactContext
|
||||
.getJSModule(HMRClient::class.java)
|
||||
.setup("android", path, host, port, devSettings.isHotModuleReplacementEnabled, scheme)
|
||||
} catch (e: MalformedURLException) {
|
||||
showNewJavaError(e.message, e)
|
||||
}
|
||||
}
|
||||
|
||||
reloadSettings()
|
||||
}
|
||||
|
||||
override fun reloadSettings() {
|
||||
if (UiThreadUtil.isOnUiThread()) {
|
||||
reload()
|
||||
} else {
|
||||
UiThreadUtil.runOnUiThread { this.reload() }
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun showDevLoadingViewForUrl(bundleUrl: String) {
|
||||
val parsedURL: URL
|
||||
|
||||
try {
|
||||
parsedURL = URL(bundleUrl)
|
||||
} catch (e: MalformedURLException) {
|
||||
FLog.e(ReactConstants.TAG, "Bundle url format is invalid. \n\n$e")
|
||||
return
|
||||
}
|
||||
|
||||
val port = if (parsedURL.port != -1) parsedURL.port else parsedURL.defaultPort
|
||||
devLoadingViewManager?.showMessage(
|
||||
applicationContext.getString(
|
||||
R.string.catalyst_loading_from_url, parsedURL.host + ":" + port))
|
||||
devLoadingViewVisible = true
|
||||
}
|
||||
|
||||
@UiThread
|
||||
protected fun showDevLoadingViewForRemoteJSEnabled() {
|
||||
devLoadingViewManager?.showMessage(
|
||||
applicationContext.getString(R.string.catalyst_debug_connecting))
|
||||
devLoadingViewVisible = true
|
||||
}
|
||||
|
||||
@UiThread
|
||||
protected fun hideDevLoadingView() {
|
||||
devLoadingViewManager?.hide()
|
||||
devLoadingViewVisible = false
|
||||
}
|
||||
|
||||
public fun fetchSplitBundleAndCreateBundleLoader(
|
||||
bundlePath: String,
|
||||
callback: CallbackWithBundleLoader
|
||||
) {
|
||||
val bundleUrl = devServerHelper.getDevServerSplitBundleURL(bundlePath)
|
||||
// The bundle path may contain the '/' character, which is not allowed in file names.
|
||||
val bundleFile = File(jsSplitBundlesDir, bundlePath.replace("/".toRegex(), "_") + ".jsbundle")
|
||||
UiThreadUtil.runOnUiThread {
|
||||
showSplitBundleDevLoadingView(bundleUrl)
|
||||
devServerHelper.downloadBundleFromURL(
|
||||
object : DevBundleDownloadListener {
|
||||
override fun onSuccess() {
|
||||
UiThreadUtil.runOnUiThread { hideSplitBundleDevLoadingView() }
|
||||
|
||||
val context: ReactContext? = this@DevSupportManagerBase.currentReactContext
|
||||
if (context == null || !context.hasActiveReactInstance()) {
|
||||
return
|
||||
}
|
||||
|
||||
val bundleLoader =
|
||||
JSBundleLoader.createCachedSplitBundleFromNetworkLoader(
|
||||
bundleUrl, bundleFile.absolutePath)
|
||||
callback.onSuccess(bundleLoader)
|
||||
}
|
||||
|
||||
override fun onProgress(status: String?, done: Int?, total: Int?) {
|
||||
devLoadingViewManager?.updateProgress(status, done, total)
|
||||
}
|
||||
|
||||
override fun onFailure(cause: Exception) {
|
||||
UiThreadUtil.runOnUiThread {
|
||||
this@DevSupportManagerBase.hideSplitBundleDevLoadingView()
|
||||
}
|
||||
callback.onError(bundleUrl, cause)
|
||||
}
|
||||
},
|
||||
bundleFile,
|
||||
bundleUrl,
|
||||
null)
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun showSplitBundleDevLoadingView(bundleUrl: String) {
|
||||
showDevLoadingViewForUrl(bundleUrl)
|
||||
pendingJSSplitBundleRequests++
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun hideSplitBundleDevLoadingView() {
|
||||
if (--pendingJSSplitBundleRequests == 0) {
|
||||
hideDevLoadingView()
|
||||
}
|
||||
}
|
||||
|
||||
override fun isPackagerRunning(callback: PackagerStatusCallback) {
|
||||
val checkPackagerRunning = Runnable { devServerHelper.isPackagerRunning(callback) }
|
||||
packagerLocationCustomizer?.run(checkPackagerRunning) ?: checkPackagerRunning.run()
|
||||
}
|
||||
|
||||
override fun downloadBundleResourceFromUrlSync(resourceURL: String, outputFile: File): File? =
|
||||
devServerHelper.downloadBundleResourceFromUrlSync(resourceURL, outputFile)
|
||||
|
||||
private fun updateLastErrorInfo(
|
||||
message: String?,
|
||||
stack: Array<StackFrame>,
|
||||
errorCookie: Int,
|
||||
errorType: ErrorType
|
||||
) {
|
||||
lastErrorTitle = message
|
||||
lastErrorStack = stack
|
||||
lastErrorCookie = errorCookie
|
||||
lastErrorType = errorType
|
||||
}
|
||||
|
||||
override fun reloadJSFromServer(bundleURL: String, callback: BundleLoadCallback) {
|
||||
ReactMarker.logMarker(ReactMarkerConstants.DOWNLOAD_START)
|
||||
|
||||
showDevLoadingViewForUrl(bundleURL)
|
||||
|
||||
val bundleInfo = BundleDownloader.BundleInfo()
|
||||
|
||||
devServerHelper.downloadBundleFromURL(
|
||||
object : DevBundleDownloadListener {
|
||||
override fun onSuccess() {
|
||||
hideDevLoadingView()
|
||||
devBundleDownloadListener?.onSuccess()
|
||||
ReactMarker.logMarker(ReactMarkerConstants.DOWNLOAD_END, bundleInfo.toJSONString())
|
||||
callback.onSuccess()
|
||||
}
|
||||
|
||||
override fun onProgress(status: String?, done: Int?, total: Int?) {
|
||||
devLoadingViewManager?.updateProgress(status, done, total)
|
||||
devBundleDownloadListener?.onProgress(status, done, total)
|
||||
}
|
||||
|
||||
override fun onFailure(cause: Exception) {
|
||||
hideDevLoadingView()
|
||||
devBundleDownloadListener?.onFailure(cause)
|
||||
FLog.e(ReactConstants.TAG, "Unable to download JS bundle", cause)
|
||||
reportBundleLoadingFailure(cause)
|
||||
callback.onError(cause)
|
||||
}
|
||||
},
|
||||
jsBundleDownloadedFile,
|
||||
bundleURL,
|
||||
bundleInfo)
|
||||
}
|
||||
|
||||
private fun reportBundleLoadingFailure(cause: Exception) {
|
||||
UiThreadUtil.runOnUiThread {
|
||||
if (cause is DebugServerException) {
|
||||
showNewJavaError(cause.message, cause)
|
||||
} else {
|
||||
showNewJavaError(applicationContext.getString(R.string.catalyst_reload_error), cause)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun startInspector() {
|
||||
if (isDevSupportEnabled) {
|
||||
devServerHelper.openInspectorConnection()
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopInspector() {
|
||||
devServerHelper.closeInspectorConnection()
|
||||
}
|
||||
|
||||
override fun setHotModuleReplacementEnabled(isHotModuleReplacementEnabled: Boolean) {
|
||||
if (!isDevSupportEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
UiThreadUtil.runOnUiThread {
|
||||
devSettings.isHotModuleReplacementEnabled = isHotModuleReplacementEnabled
|
||||
handleReloadJS()
|
||||
}
|
||||
}
|
||||
|
||||
override fun setFpsDebugEnabled(isFpsDebugEnabled: Boolean) {
|
||||
if (!isDevSupportEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
UiThreadUtil.runOnUiThread { devSettings.isFpsDebugEnabled = isFpsDebugEnabled }
|
||||
}
|
||||
|
||||
override fun toggleElementInspector() {
|
||||
if (!isDevSupportEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
UiThreadUtil.runOnUiThread {
|
||||
devSettings.isElementInspectorEnabled = !devSettings.isElementInspectorEnabled
|
||||
reactInstanceDevHelper.toggleElementInspector()
|
||||
}
|
||||
}
|
||||
|
||||
private fun reload() {
|
||||
UiThreadUtil.assertOnUiThread()
|
||||
|
||||
// reload settings, show/hide debug overlay if required & start/stop shake detector
|
||||
if (isDevSupportEnabled) {
|
||||
// update visibility of FPS debug overlay depending on the settings
|
||||
debugOverlayController?.setFpsDebugViewVisible(devSettings.isFpsDebugEnabled)
|
||||
|
||||
// start shake gesture detector
|
||||
if (!isShakeDetectorStarted) {
|
||||
val sensorManager =
|
||||
applicationContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
shakeDetector.start(sensorManager)
|
||||
isShakeDetectorStarted = true
|
||||
}
|
||||
|
||||
// register reload app broadcast receiver
|
||||
if (!isReceiverRegistered) {
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(getReloadAppAction(applicationContext))
|
||||
compatRegisterReceiver(applicationContext, reloadAppBroadcastReceiver, filter, true)
|
||||
isReceiverRegistered = true
|
||||
}
|
||||
|
||||
// show the dev loading if it should be
|
||||
if (devLoadingViewVisible) {
|
||||
devLoadingViewManager?.showMessage("Reloading...")
|
||||
}
|
||||
|
||||
devServerHelper.openPackagerConnection(
|
||||
javaClass.simpleName,
|
||||
object : PackagerCommandListener {
|
||||
override fun onPackagerConnected() {
|
||||
isPackagerConnected = true
|
||||
}
|
||||
|
||||
override fun onPackagerDisconnected() {
|
||||
isPackagerConnected = false
|
||||
}
|
||||
|
||||
override fun onPackagerReloadCommand() {
|
||||
if (!getFuseboxEnabled()) {
|
||||
// Disable debugger to resume the JsVM & avoid thread locks while reloading
|
||||
devServerHelper.disableDebugger()
|
||||
}
|
||||
UiThreadUtil.runOnUiThread { handleReloadJS() }
|
||||
}
|
||||
|
||||
override fun onPackagerDevMenuCommand() {
|
||||
UiThreadUtil.runOnUiThread { showDevOptionsDialog() }
|
||||
}
|
||||
|
||||
override fun customCommandHandlers(): Map<String, RequestHandler>? {
|
||||
return customPackagerCommandHandlers
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// hide FPS debug overlay
|
||||
debugOverlayController?.setFpsDebugViewVisible(false)
|
||||
|
||||
// stop shake gesture detector
|
||||
if (isShakeDetectorStarted) {
|
||||
shakeDetector.stop()
|
||||
isShakeDetectorStarted = false
|
||||
}
|
||||
|
||||
// unregister app reload broadcast receiver
|
||||
if (isReceiverRegistered) {
|
||||
applicationContext.unregisterReceiver(reloadAppBroadcastReceiver)
|
||||
isReceiverRegistered = false
|
||||
}
|
||||
|
||||
// hide redbox dialog
|
||||
hideRedboxDialog()
|
||||
|
||||
// hide dev options dialog
|
||||
hideDevOptionsDialog()
|
||||
|
||||
// hide loading view
|
||||
devLoadingViewManager?.hide()
|
||||
devServerHelper.closePackagerConnection()
|
||||
}
|
||||
}
|
||||
|
||||
override fun setPackagerLocationCustomizer(
|
||||
packagerLocationCustomizer: PackagerLocationCustomizer
|
||||
) {
|
||||
this.packagerLocationCustomizer = packagerLocationCustomizer
|
||||
}
|
||||
|
||||
override fun createSurfaceDelegate(moduleName: String): SurfaceDelegate? =
|
||||
surfaceDelegateFactory?.createSurfaceDelegate(moduleName)
|
||||
|
||||
/**
|
||||
* Starting with Android 14, apps and services that target Android 14 and use context-registered
|
||||
* receivers are required to specify a flag to indicate whether or not the receiver should be
|
||||
* exported to all other apps on the device: either RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED
|
||||
*
|
||||
* https://developer.android.com/about/versions/14/behavior-changes-14#runtime-receivers-exported
|
||||
*/
|
||||
private fun compatRegisterReceiver(
|
||||
context: Context,
|
||||
receiver: BroadcastReceiver,
|
||||
filter: IntentFilter,
|
||||
exported: Boolean
|
||||
) {
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
|
||||
context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
context.registerReceiver(
|
||||
receiver,
|
||||
filter,
|
||||
if (exported) Context.RECEIVER_EXPORTED else Context.RECEIVER_NOT_EXPORTED)
|
||||
} else {
|
||||
context.registerReceiver(receiver, filter)
|
||||
}
|
||||
}
|
||||
|
||||
override fun openDebugger() {
|
||||
devServerHelper.openDebugger(
|
||||
currentReactContext, applicationContext.getString(R.string.catalyst_open_debugger_error))
|
||||
}
|
||||
|
||||
override fun showPausedInDebuggerOverlay(
|
||||
message: String,
|
||||
listener: PausedInDebuggerOverlayCommandListener
|
||||
) {
|
||||
pausedInDebuggerOverlayManager?.showPausedInDebuggerOverlay(message, listener)
|
||||
}
|
||||
|
||||
override fun hidePausedInDebuggerOverlay() {
|
||||
pausedInDebuggerOverlayManager?.hidePausedInDebuggerOverlay()
|
||||
}
|
||||
|
||||
override fun setAdditionalOptionForPackager(name: String, value: String) {
|
||||
devSettings.packagerConnectionSettings.setAdditionalOptionForPackager(name, value)
|
||||
}
|
||||
|
||||
public companion object {
|
||||
private const val JAVA_ERROR_COOKIE = -1
|
||||
private const val JSEXCEPTION_ERROR_COOKIE = -1
|
||||
private const val RELOAD_APP_ACTION_SUFFIX = ".RELOAD_APP_ACTION"
|
||||
private const val EXOPACKAGE_LOCATION_FORMAT = "/data/local/tmp/exopackage/%s//secondary-dex"
|
||||
|
||||
/** Intent action for reloading the JS */
|
||||
private fun getReloadAppAction(context: Context): String =
|
||||
context.packageName + RELOAD_APP_ACTION_SUFFIX
|
||||
}
|
||||
}
|
||||
+50
-65
@@ -12,7 +12,6 @@ import com.facebook.react.bridge.ReadableType
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import com.facebook.react.devsupport.interfaces.StackFrame
|
||||
import java.io.File
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
@@ -43,13 +42,12 @@ public object StackTraceHelper {
|
||||
|
||||
/** Convert a JavaScript stack trace (see `parseErrorStack` JS module) to an array of [ ]s. */
|
||||
@JvmStatic
|
||||
public fun convertJsStackTrace(stack: ReadableArray?): Array<StackFrame?> {
|
||||
public fun convertJsStackTrace(stack: ReadableArray?): Array<StackFrame> {
|
||||
val size = stack?.size() ?: 0
|
||||
val result = arrayOfNulls<StackFrame>(size)
|
||||
if (stack == null) {
|
||||
return result
|
||||
return arrayOf()
|
||||
}
|
||||
for (i in 0..<size) {
|
||||
return Array(size) { i ->
|
||||
val type = stack.getType(i)
|
||||
if (type == ReadableType.Map) {
|
||||
val frame = requireNotNull(stack.getMap(i))
|
||||
@@ -65,34 +63,31 @@ public object StackTraceHelper {
|
||||
if (frame.hasKey(COLUMN_KEY) && !frame.isNull(COLUMN_KEY)) {
|
||||
columnNumber = frame.getInt(COLUMN_KEY)
|
||||
}
|
||||
result[i] =
|
||||
StackFrameImpl(
|
||||
file = file,
|
||||
method = method,
|
||||
line = lineNumber,
|
||||
column = columnNumber,
|
||||
isCollapsed = collapse)
|
||||
StackFrameImpl(
|
||||
file = file,
|
||||
method = method,
|
||||
line = lineNumber,
|
||||
column = columnNumber,
|
||||
isCollapsed = collapse)
|
||||
} else if (type == ReadableType.String) {
|
||||
val stackFrame = stack.getString(i)
|
||||
if (stackFrame != null) {
|
||||
result[i] = StackFrameImpl(file = null, method = stackFrame, line = -1, column = -1)
|
||||
}
|
||||
StackFrameImpl(
|
||||
file = null, method = checkNotNull(stack.getString(i)), line = -1, column = -1)
|
||||
} else {
|
||||
error("Cannot parse the stackframe for $stack")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a JavaScript stack trace (see `parseErrorStack` JS module) to an array of [StackFrame].
|
||||
*/
|
||||
public fun convertJsStackTrace(stack: JSONArray?): Array<StackFrame?> {
|
||||
val size = stack?.length() ?: 0
|
||||
val result = arrayOfNulls<StackFrame>(size)
|
||||
public fun convertJsStackTrace(stack: JSONArray?): Array<StackFrame> {
|
||||
if (stack == null) {
|
||||
return result
|
||||
return arrayOf()
|
||||
}
|
||||
val size = stack.length()
|
||||
try {
|
||||
for (i in 0..<size) {
|
||||
return Array(size) { i ->
|
||||
val frame = stack.getJSONObject(i)
|
||||
val method = frame.getString("methodName")
|
||||
val file = frame.getString("file")
|
||||
@@ -106,68 +101,58 @@ public object StackTraceHelper {
|
||||
}
|
||||
val collapse =
|
||||
frame.has("collapse") && !frame.isNull("collapse") && frame.getBoolean("collapse")
|
||||
result[i] =
|
||||
StackFrameImpl(
|
||||
file = file,
|
||||
method = method,
|
||||
line = lineNumber,
|
||||
column = columnNumber,
|
||||
isCollapsed = collapse)
|
||||
StackFrameImpl(
|
||||
file = file,
|
||||
method = method,
|
||||
line = lineNumber,
|
||||
column = columnNumber,
|
||||
isCollapsed = collapse)
|
||||
}
|
||||
} catch (exception: JSONException) {
|
||||
throw RuntimeException(exception)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Convert a JavaScript stack trace to an array of [StackFrame]s. */
|
||||
public fun convertJsStackTrace(stack: String): Array<StackFrame?> {
|
||||
public fun convertJsStackTrace(stack: String): Array<StackFrame> {
|
||||
val stackTrace = stack.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val result = arrayOfNulls<StackFrame>(stackTrace.size)
|
||||
for (i in stackTrace.indices) {
|
||||
return Array(stackTrace.size) { i ->
|
||||
val matcher1 = STACK_FRAME_PATTERN1.matcher(stackTrace[i])
|
||||
val matcher2 = STACK_FRAME_PATTERN2.matcher(stackTrace[i])
|
||||
val matcher: Matcher
|
||||
if (matcher2.find()) {
|
||||
matcher = matcher2
|
||||
} else if (matcher1.find()) {
|
||||
matcher = matcher1
|
||||
val matcher =
|
||||
when {
|
||||
matcher2.find() -> matcher2
|
||||
matcher1.find() -> matcher1
|
||||
else -> null
|
||||
}
|
||||
if (matcher != null) {
|
||||
val file = matcher.group(2)
|
||||
val method = matcher.group(1) ?: "(unknown)"
|
||||
val lineString = matcher.group(3)
|
||||
val columnString = matcher.group(4)
|
||||
StackFrameImpl(
|
||||
file = file,
|
||||
method = method,
|
||||
line = checkNotNull(lineString).toInt(),
|
||||
column = checkNotNull(columnString).toInt())
|
||||
} else {
|
||||
result[i] = StackFrameImpl(file = null, method = stackTrace[i], line = -1, column = -1)
|
||||
continue
|
||||
StackFrameImpl(file = null, method = stackTrace[i], line = -1, column = -1)
|
||||
}
|
||||
val file = matcher.group(2)
|
||||
val method = matcher.group(1) ?: "(unknown)"
|
||||
val lineString = matcher.group(3)
|
||||
val columnString = matcher.group(4)
|
||||
if (file == null || lineString == null || columnString == null) {
|
||||
continue
|
||||
}
|
||||
result[i] =
|
||||
StackFrameImpl(
|
||||
file = file,
|
||||
method = method,
|
||||
line = lineString.toInt(),
|
||||
column = columnString.toInt())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Convert a [Throwable] to an array of [StackFrame]s. */
|
||||
@JvmStatic
|
||||
public fun convertJavaStackTrace(exception: Throwable): Array<StackFrame?> {
|
||||
public fun convertJavaStackTrace(exception: Throwable): Array<StackFrame> {
|
||||
val stackTrace = exception.stackTrace
|
||||
val result = arrayOfNulls<StackFrame>(stackTrace.size)
|
||||
for (i in stackTrace.indices) {
|
||||
result[i] =
|
||||
StackFrameImpl(
|
||||
file = stackTrace[i].className,
|
||||
fileName = stackTrace[i].fileName,
|
||||
method = stackTrace[i].methodName,
|
||||
line = stackTrace[i].lineNumber,
|
||||
column = -1)
|
||||
return Array(stackTrace.size) { i ->
|
||||
StackFrameImpl(
|
||||
file = stackTrace[i].className,
|
||||
fileName = stackTrace[i].fileName,
|
||||
method = stackTrace[i].methodName,
|
||||
line = stackTrace[i].lineNumber,
|
||||
column = -1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Format a [StackFrame] to a String (method name is not included). */
|
||||
|
||||
Reference in New Issue
Block a user