1 package expo.modules.devlauncher.react 2 3 import android.util.Log 4 import com.facebook.react.ReactInstanceManager 5 import com.facebook.react.common.ShakeDetector 6 import com.facebook.react.devsupport.DevServerHelper 7 import com.facebook.react.devsupport.DevSupportManagerBase 8 import com.facebook.react.devsupport.DisabledDevSupportManager 9 import com.facebook.react.devsupport.interfaces.DevSupportManager 10 import com.facebook.react.packagerconnection.JSPackagerClient 11 import expo.modules.devlauncher.helpers.getProtectedFieldValue 12 import expo.modules.devlauncher.helpers.setProtectedDeclaredField 13 import expo.modules.devlauncher.koin.DevLauncherKoinComponent 14 import expo.modules.devlauncher.launcher.DevLauncherControllerInterface 15 import expo.modules.devlauncher.rncompatibility.DevLauncherDevSupportManager 16 import kotlinx.coroutines.delay 17 import kotlinx.coroutines.launch 18 import org.koin.core.component.inject 19 20 class DevLauncherDevSupportManagerSwapper : DevLauncherKoinComponent { 21 private val controller: DevLauncherControllerInterface by inject() 22 swapDevSupportManagerImplnull23 fun swapDevSupportManagerImpl( 24 reactInstanceManager: ReactInstanceManager 25 ) { 26 val currentDevSupportManager = reactInstanceManager.devSupportManager 27 if (currentDevSupportManager is DevLauncherDevSupportManager) { 28 // DevSupportManager was swapped by the DevLauncherReactNativeHostHandler 29 return 30 } 31 32 if (currentDevSupportManager is DisabledDevSupportManager) { 33 Log.i("DevLauncher", "DevSupportManager is disabled. So we don't want to override it.") 34 return 35 } 36 try { 37 val devManagerClass = DevSupportManagerBase::class.java 38 val newDevSupportManager = DevLauncherDevSupportManager( 39 applicationContext = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "mApplicationContext"), 40 reactInstanceManagerHelper = devManagerClass.getProtectedFieldValue(currentDevSupportManager, DevLauncherDevSupportManager.getDevHelperInternalFieldName()), 41 packagerPathForJSBundleName = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "mJSAppBundleName"), 42 enableOnCreate = true, 43 redBoxHandler = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "mRedBoxHandler"), 44 devBundleDownloadListener = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "mBundleDownloadListener"), 45 minNumShakes = 1, 46 customPackagerCommandHandlers = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "mCustomPackagerCommandHandlers") 47 ) 48 49 ReactInstanceManager::class.java.setProtectedDeclaredField(reactInstanceManager, "mDevSupportManager", newDevSupportManager) 50 51 /** 52 * We need to invalidate the old packager connection. 53 * However, this connection is established in the background 54 * and we don't know when it will be available (see [DevServerHelper.openPackagerConnection]). 55 * So we just wait for connection and then we kill it. 56 */ 57 controller.coroutineScope.launch { 58 try { 59 while (true) { 60 // Invalidate shake detector - not doing that leads to memory leaks 61 tryToStopShakeDetector(currentDevSupportManager) 62 63 val devServerHelper: DevServerHelper = devManagerClass.getProtectedFieldValue( 64 currentDevSupportManager, 65 "mDevServerHelper" 66 ) 67 68 try { 69 val packagerConnectionLock: Boolean = DevServerHelper::class.java.getProtectedFieldValue( 70 devServerHelper, 71 "mPackagerConnectionLock" 72 ) 73 74 if (!packagerConnectionLock) { 75 devServerHelper.closePackagerConnection() 76 return@launch 77 } 78 } catch (_: NoSuchFieldException) { 79 // mPackagerConnectionLock was removed from the React Native in v0.63.4 80 val packagerClient: JSPackagerClient? = DevServerHelper::class.java.getProtectedFieldValue( 81 devServerHelper, 82 "mPackagerClient" 83 ) 84 85 if (packagerClient != null) { 86 devServerHelper.closePackagerConnection() 87 return@launch 88 } 89 } 90 91 delay(50) 92 } 93 } catch (e: Exception) { 94 Log.w("DevLauncher", "Couldn't close the packager connection: ${e.message}", e) 95 } 96 } 97 } catch (e: Exception) { 98 Log.i("DevLauncher", "Couldn't inject `DevLauncherDevSupportManager`.", e) 99 } 100 } 101 tryToStopShakeDetectornull102 private fun tryToStopShakeDetector(currentDevSupportManager: DevSupportManager) { 103 try { 104 val shakeDetector: ShakeDetector = 105 DevSupportManagerBase::class.java.getProtectedFieldValue( 106 currentDevSupportManager, 107 "mShakeDetector", 108 ) 109 shakeDetector.stop() 110 } catch (e: Exception) { 111 Log.w("DevLauncher", "Couldn't stop shake detector.", e) 112 } 113 } 114 } 115