55import android .os .AsyncTask ;
66import android .os .Handler ;
77import android .os .Looper ;
8- import android .provider .Settings ;
98import android .view .View ;
109
10+ import androidx .annotation .OptIn ;
11+
1112import com .facebook .react .ReactApplication ;
13+ import com .facebook .react .ReactDelegate ;
14+ import com .facebook .react .ReactHost ;
1215import com .facebook .react .ReactInstanceManager ;
16+ import com .facebook .react .ReactActivity ;
1317import com .facebook .react .ReactRootView ;
1418import com .facebook .react .bridge .Arguments ;
1519import com .facebook .react .bridge .JSBundleLoader ;
2024import com .facebook .react .bridge .ReactMethod ;
2125import com .facebook .react .bridge .ReadableMap ;
2226import com .facebook .react .bridge .WritableMap ;
27+ import com .facebook .react .common .annotations .UnstableReactNativeAPI ;
2328import com .facebook .react .modules .core .ChoreographerCompat ;
2429import com .facebook .react .modules .core .DeviceEventManagerModule ;
2530import com .facebook .react .modules .core .ReactChoreographer ;
31+ import com .facebook .react .runtime .ReactHostDelegate ;
2632
2733import org .json .JSONArray ;
2834import org .json .JSONException ;
2935import org .json .JSONObject ;
3036
3137import java .io .IOException ;
3238import java .lang .reflect .Field ;
39+ import java .lang .reflect .Method ;
3340import java .util .ArrayList ;
3441import java .util .Date ;
3542import java .util .HashMap ;
36- import java .util .List ;
3743import java .util .Map ;
3844import java .util .UUID ;
3945
@@ -120,15 +126,38 @@ private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBu
120126 latestJSBundleLoader = JSBundleLoader .createFileLoader (latestJSBundleFile );
121127 }
122128
123- Field bundleLoaderField = instanceManager .getClass ().getDeclaredField ("mBundleLoader" );
124- bundleLoaderField .setAccessible (true );
125- bundleLoaderField .set (instanceManager , latestJSBundleLoader );
129+ ReactHost reactHost = resolveReactHost ();
130+ if (reactHost == null ) {
131+ // Bridge, Old Architecture and RN < 0.74 (we support Bridgeless >= 0.74)
132+ setJSBundleLoaderBridge (instanceManager , latestJSBundleLoader );
133+ return ;
134+ }
135+
136+ // Bridgeless (RN >= 0.74)
137+ setJSBundleLoaderBridgeless (reactHost , latestJSBundleLoader );
126138 } catch (Exception e ) {
127139 CodePushUtils .log ("Unable to set JSBundle - CodePush may not support this version of React Native" );
128140 throw new IllegalAccessException ("Could not setJSBundle" );
129141 }
130142 }
131143
144+ private void setJSBundleLoaderBridge (ReactInstanceManager instanceManager , JSBundleLoader latestJSBundleLoader ) throws NoSuchFieldException , IllegalAccessException {
145+ Field bundleLoaderField = instanceManager .getClass ().getDeclaredField ("mBundleLoader" );
146+ bundleLoaderField .setAccessible (true );
147+ bundleLoaderField .set (instanceManager , latestJSBundleLoader );
148+ }
149+
150+ @ OptIn (markerClass = UnstableReactNativeAPI .class )
151+ private void setJSBundleLoaderBridgeless (ReactHost reactHost , JSBundleLoader latestJSBundleLoader ) throws NoSuchFieldException , IllegalAccessException {
152+ Field mReactHostDelegateField = reactHost .getClass ().getDeclaredField ("mReactHostDelegate" );
153+ mReactHostDelegateField .setAccessible (true );
154+ ReactHostDelegate reactHostDelegate = (ReactHostDelegate ) mReactHostDelegateField .get (reactHost );
155+ assert reactHostDelegate != null ;
156+ Field jsBundleLoaderField = reactHostDelegate .getClass ().getDeclaredField ("jsBundleLoader" );
157+ jsBundleLoaderField .setAccessible (true );
158+ jsBundleLoaderField .set (reactHostDelegate , latestJSBundleLoader );
159+ }
160+
132161 private void loadBundle () {
133162 clearLifecycleEventListener ();
134163 try {
@@ -156,12 +185,22 @@ private void loadBundle() {
156185 @ Override
157186 public void run () {
158187 try {
159- // We don't need to resetReactRootViews anymore
160- // due the issue https://github.com/facebook/react-native/issues/14533
161- // has been fixed in RN 0.46.0
162- //resetReactRootViews(instanceManager);
188+ // reload method introduced in RN 0.74 (https://github.com/reactwg/react-native-new-architecture/discussions/174)
189+ // so, we need to check if reload method exists and call it
190+ try {
191+ ReactDelegate reactDelegate = resolveReactDelegate ();
192+ if (reactDelegate == null ) {
193+ throw new NoSuchMethodException ("ReactDelegate doesn't have reload method in RN < 0.74" );
194+ }
195+
196+ resetReactRootViews (reactDelegate );
163197
164- instanceManager .recreateReactContextInBackground ();
198+ Method reloadMethod = reactDelegate .getClass ().getMethod ("reload" );
199+ reloadMethod .invoke (reactDelegate );
200+ } catch (NoSuchMethodException e ) {
201+ // RN < 0.74 calls ReactInstanceManager.recreateReactContextInBackground() directly
202+ instanceManager .recreateReactContextInBackground ();
203+ }
165204 mCodePush .initializeUpdateAfterRestart ();
166205 } catch (Exception e ) {
167206 // The recreation method threw an unknown exception
@@ -179,18 +218,19 @@ public void run() {
179218 }
180219 }
181220
182- // This workaround has been implemented in order to fix https://github.com/facebook/react-native/issues/14533
183- // resetReactRootViews allows to call recreateReactContextInBackground without any exceptions
184- // This fix also relates to https://github.com/microsoft/react-native-code-push/issues/878
185- private void resetReactRootViews (ReactInstanceManager instanceManager ) throws NoSuchFieldException , IllegalAccessException {
186- Field mAttachedRootViewsField = instanceManager .getClass ().getDeclaredField ("mAttachedRootViews" );
187- mAttachedRootViewsField .setAccessible (true );
188- List <ReactRootView > mAttachedRootViews = (List <ReactRootView >)mAttachedRootViewsField .get (instanceManager );
189- for (ReactRootView reactRootView : mAttachedRootViews ) {
190- reactRootView .removeAllViews ();
191- reactRootView .setId (View .NO_ID );
221+ // Fix freezing that occurs when reloading the app (RN >= 0.77.1 Old Architecture)
222+ // - "Trying to add a root view with an explicit id (11) already set.
223+ // React Native uses the id field to track react tags and will overwrite this field.
224+ // If that is fine, explicitly overwrite the id field to View.NO_ID before calling addRootView."
225+ private void resetReactRootViews (ReactDelegate reactDelegate ) {
226+ ReactActivity currentActivity = (ReactActivity ) getCurrentActivity ();
227+ if (currentActivity != null ) {
228+ ReactRootView reactRootView = reactDelegate .getReactRootView ();
229+ if (reactRootView != null ) {
230+ reactRootView .removeAllViews ();
231+ reactRootView .setId (View .NO_ID );
232+ }
192233 }
193- mAttachedRootViewsField .set (instanceManager , mAttachedRootViews );
194234 }
195235
196236 private void clearLifecycleEventListener () {
@@ -201,6 +241,36 @@ private void clearLifecycleEventListener() {
201241 }
202242 }
203243
244+ private ReactDelegate resolveReactDelegate () {
245+ ReactActivity currentActivity = (ReactActivity ) getCurrentActivity ();
246+ if (currentActivity == null ) {
247+ return null ;
248+ }
249+
250+ try {
251+ Method getReactDelegateMethod = currentActivity .getClass ().getMethod ("getReactDelegate" );
252+ return (ReactDelegate ) getReactDelegateMethod .invoke (currentActivity );
253+ } catch (Exception e ) {
254+ // RN < 0.74 doesn't have getReactDelegate method
255+ return null ;
256+ }
257+ }
258+
259+ private ReactHost resolveReactHost () {
260+ ReactDelegate reactDelegate = resolveReactDelegate ();
261+ if (reactDelegate == null ) {
262+ return null ;
263+ }
264+
265+ try {
266+ Field reactHostField = reactDelegate .getClass ().getDeclaredField ("mReactHost" );
267+ reactHostField .setAccessible (true );
268+ return (ReactHost ) reactHostField .get (reactDelegate );
269+ } catch (Exception e ) {
270+ return null ;
271+ }
272+ }
273+
204274 // Use reflection to find the ReactInstanceManager. See #556 for a proposal for a less brittle way to approach this.
205275 private ReactInstanceManager resolveInstanceManager () throws NoSuchFieldException , IllegalAccessException {
206276 ReactInstanceManager instanceManager = CodePush .getReactInstanceManager ();
@@ -492,7 +562,7 @@ protected Void doInBackground(Void... params) {
492562 return null ;
493563 }
494564 }
495-
565+
496566 promise .resolve ("" );
497567 } catch (CodePushUnknownException e ) {
498568 CodePushUtils .log (e );
0 commit comments