I am using react native v0.69 to integrate with an existing native app. I was following integration guide and found a memory leak. I simplified my code base to be able to track it and find a solution but I can not see any way how to fix it.
I also notify this error message coming from react native immediately after I close this activity
unknown:ReactNative com.awesome.app E Tried to remove non-existent frame callback
this is LeaksCanary report:
====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
124939 bytes retained by leaking objects
Displaying only 1 leak trace out of 3 with the same signature
Signature: b5cfede934befdd5a3af7ef80b4bdc998d874757
┬───
│ GC Root: Global variable in native code
│
├─ com.facebook.react.bridge.JavaModuleWrapper instance
│ Leaking: UNKNOWN
│ Retaining 370 B in 11 objects
│ ↓ JavaModuleWrapper.mModuleHolder
│ ~~~~~~~~~~~~~
├─ com.facebook.react.bridge.ModuleHolder instance
│ Leaking: UNKNOWN
│ Retaining 1.0 kB in 13 objects
│ ↓ ModuleHolder.mModule
│ ~~~~~~~
├─ com.facebook.react.devsupport.LogBoxModule instance
│ Leaking: UNKNOWN
│ Retaining 980 B in 11 objects
│ mReactApplicationContext instance of com.facebook.react.bridge.ReactApplicationContext, wrapping android.app.
│ Application
│ ↓ LogBoxModule.mSurfaceDelegate
│ ~~~~~~~~~~~~~~~~
├─ com.facebook.react.devsupport.LogBoxDialogSurfaceDelegate instance
│ Leaking: UNKNOWN
│ Retaining 960 B in 10 objects
│ ↓ LogBoxDialogSurfaceDelegate.mReactRootView
│ ~~~~~~~~~~~~~~
├─ com.facebook.react.ReactRootView instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ Retaining 940 B in 9 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 0
│ mContext instance of com.awesome.activities.MainReactActivity with mDestroyed = true
│ ↓ View.mContext
╰→com.awesome.activities.MainReactActivity instance
Leaking: YES (ObjectWatcher was watching this because com.awesome.activities.MainReactActivity
received Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 41.6 kB in 785 objects
key = fc60d0e2-e08f-408d-8cda-5f29dec2e0ee
watchDurationMillis = 24805
retainedDurationMillis = 19800
mApplication instance of android.app.Application
mBase instance of android.app.ContextImpl
and this is MainReactActivity.kt
class MainReactActivity : FragmentActivity(), DefaultHardwareBackBtnHandler {
companion object {
const val OVERLAY_PERMISSION_REQ_CODE = 1
}
private val tag = "Awesome-MRActivity"
private lateinit var reactRootView: ReactRootView
private lateinit var reactInstanceManager: ReactInstanceManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
SoLoader.init(applicationContext, false)
reactInstanceManager = buildReactInstanceManager()
reactRootView = ReactRootView(this)
reactRootView.startReactApplication(
reactInstanceManager,
"PaymentSdk",
intent.extras
)
setContentView(reactRootView)
checkActivityOverlay()
}
private fun buildReactInstanceManager(): ReactInstanceManager =
ReactInstanceManager.builder()
.setApplication(application)
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackages(getModulePackages())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
.build()
private fun getJavaScriptExecutorFactory(): JavaScriptExecutorFactory? {
if (BuildConfig.DEBUG) {
return null
}
return HermesExecutorFactory()
}
private fun getModulePackages(): MutableList<ReactPackage> {
val packages: MutableList<ReactPackage> = PackageList(application).packages
return packages
}
private fun checkActivityOverlay() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && BuildConfig.DEBUG) {
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package: $packageName")
)
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
}
}
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (BuildConfig.DEBUG && requestCode == OVERLAY_PERMISSION_REQ_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// SYSTEM_ALERT_WINDOW permission not granted
// TODO LSA: do something when activity can not overlay
// TODO: NOTE: IT IS ONLY FOR DEV - double check it in production version
Log.i(tag, "onActivityResult: $OVERLAY_PERMISSION_REQ_CODE")
}
}
}
reactInstanceManager.onActivityResult(this, requestCode, resultCode, data)
}
override fun invokeDefaultOnBackPressed() {
super.onBackPressed()
}
override fun onPause() {
super.onPause()
reactInstanceManager.onHostPause(this)
}
override fun onResume() {
super.onResume()
reactInstanceManager.onHostResume(this, this)
}
override fun onDestroy() {
super.onDestroy()
reactInstanceManager.onHostDestroy(this)
reactRootView.unmountReactApplication()
}
override fun onBackPressed() {
reactInstanceManager.onBackPressed()
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_MENU) {
reactInstanceManager.showDevOptionsDialog()
return true
}
return super.onKeyUp(keyCode, event)
}
}
and I am opening it in another activity like this:
...
val mainReactActivityIntent = Intent(context, MainReactActivity::class.java)
context.startActivity(mainReactActivityIntent)
...
I am also able to track it in Profiler
in Android Studio this is detail: