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 .ReactHost ;
1214import com .facebook .react .ReactInstanceManager ;
1315import com .facebook .react .ReactRootView ;
1416import com .facebook .react .bridge .Arguments ;
17+ import com .facebook .react .bridge .BaseJavaModule ;
1518import com .facebook .react .bridge .JSBundleLoader ;
1619import com .facebook .react .bridge .LifecycleEventListener ;
1720import com .facebook .react .bridge .Promise ;
1821import com .facebook .react .bridge .ReactApplicationContext ;
19- import com .facebook .react .bridge .ReactContextBaseJavaModule ;
2022import com .facebook .react .bridge .ReactMethod ;
2123import com .facebook .react .bridge .ReadableMap ;
2224import com .facebook .react .bridge .WritableMap ;
25+ import com .facebook .react .common .annotations .UnstableReactNativeAPI ;
26+ import com .facebook .react .devsupport .interfaces .DevSupportManager ;
2327import com .facebook .react .modules .core .ChoreographerCompat ;
2428import com .facebook .react .modules .core .DeviceEventManagerModule ;
2529import com .facebook .react .modules .core .ReactChoreographer ;
30+ import com .facebook .react .modules .debug .interfaces .DeveloperSettings ;
31+ import com .facebook .react .runtime .ReactHostDelegate ;
32+ import com .facebook .react .runtime .ReactHostImpl ;
2633
2734import org .json .JSONArray ;
2835import org .json .JSONException ;
2936import org .json .JSONObject ;
3037
3138import java .io .IOException ;
3239import java .lang .reflect .Field ;
40+ import java .lang .reflect .Method ;
3341import java .util .ArrayList ;
3442import java .util .Date ;
3543import java .util .HashMap ;
3644import java .util .List ;
3745import java .util .Map ;
3846import java .util .UUID ;
3947
40- public class CodePushNativeModule extends ReactContextBaseJavaModule {
48+ @ OptIn (markerClass = UnstableReactNativeAPI .class )
49+ public class CodePushNativeModule extends BaseJavaModule {
4150 private String mBinaryContentsHash = null ;
4251 private String mClientUniqueId = null ;
4352 private LifecycleEventListener mLifecycleEventListener = null ;
@@ -93,7 +102,7 @@ public String getName() {
93102 }
94103
95104 private void loadBundleLegacy () {
96- final Activity currentActivity = getCurrentActivity ();
105+ final Activity currentActivity = getReactApplicationContext (). getCurrentActivity ();
97106 if (currentActivity == null ) {
98107 // The currentActivity can be null if it is backgrounded / destroyed, so we simply
99108 // no-op to prevent any null pointer exceptions.
@@ -124,59 +133,155 @@ private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBu
124133 bundleLoaderField .setAccessible (true );
125134 bundleLoaderField .set (instanceManager , latestJSBundleLoader );
126135 } catch (Exception e ) {
127- CodePushUtils .log ("Unable to set JSBundle - CodePush may not support this version of React Native" );
136+ CodePushUtils .log ("Unable to set JSBundle of ReactInstanceManager - CodePush may not support this version of React Native" );
128137 throw new IllegalAccessException ("Could not setJSBundle" );
129138 }
130139 }
131140
132- private void loadBundle () {
133- clearLifecycleEventListener ();
141+ // Use reflection to find and set the appropriate fields on ReactHostDelegate. See #556 for a proposal for a less brittle way
142+ // to approach this.
143+ private void setJSBundle (ReactHostDelegate reactHostDelegate , String latestJSBundleFile ) throws IllegalAccessException {
134144 try {
135- mCodePush .clearDebugCacheIfNeeded (resolveInstanceManager ());
136- } catch (Exception e ) {
137- // If we got error in out reflection we should clear debug cache anyway.
138- mCodePush .clearDebugCacheIfNeeded (null );
145+ JSBundleLoader latestJSBundleLoader ;
146+ if (latestJSBundleFile .toLowerCase ().startsWith ("assets://" )) {
147+ latestJSBundleLoader = JSBundleLoader .createAssetLoader (getReactApplicationContext (), latestJSBundleFile , false );
148+ } else {
149+ latestJSBundleLoader = JSBundleLoader .createFileLoader (latestJSBundleFile );
150+ }
151+
152+ Field bundleLoaderField = reactHostDelegate .getClass ().getDeclaredField ("jsBundleLoader" );
153+ bundleLoaderField .setAccessible (true );
154+ bundleLoaderField .set (reactHostDelegate , latestJSBundleLoader );
155+ } catch (Exception e ) {
156+ CodePushUtils .log ("Unable to set JSBundle of ReactHostDelegate - CodePush may not support this version of React Native" );
157+ throw new IllegalAccessException ("Could not setJSBundle" );
139158 }
159+ }
140160
141- try {
142- // #1) Get the ReactInstanceManager instance, which is what includes the
143- // logic to reload the current React context.
144- final ReactInstanceManager instanceManager = resolveInstanceManager ();
145- if (instanceManager == null ) {
146- return ;
161+ private void loadBundle () {
162+ clearLifecycleEventListener ();
163+
164+ // ReactNative core components are changed on new architecture.
165+ if (BuildConfig .IS_NEW_ARCHITECTURE_ENABLED ) {
166+ try {
167+ DevSupportManager devSupportManager = null ;
168+ ReactHost reactHost = resolveReactHost ();
169+ if (reactHost != null ) {
170+ devSupportManager = reactHost .getDevSupportManager ();
171+ }
172+ boolean isLiveReloadEnabled = isLiveReloadEnabled (devSupportManager );
173+
174+ mCodePush .clearDebugCacheIfNeeded (isLiveReloadEnabled );
175+ } catch (Exception e ) {
176+ // If we got error in out reflection we should clear debug cache anyway.
177+ mCodePush .clearDebugCacheIfNeeded (false );
178+ }
179+
180+ try {
181+ // #1) Get the ReactHost instance, which is what includes the
182+ // logic to reload the current React context.
183+ final ReactHost reactHost = resolveReactHost ();
184+ if (reactHost == null ) {
185+ return ;
186+ }
187+
188+ String latestJSBundleFile = mCodePush .getJSBundleFileInternal (mCodePush .getAssetsBundleFileName ());
189+
190+ // #2) Update the locally stored JS bundle file path
191+ setJSBundle (getReactHostDelegate ((ReactHostImpl ) reactHost ), latestJSBundleFile );
192+
193+ // #3) Get the context creation method
194+ try {
195+ reactHost .reload ("CodePush triggers reload" );
196+ mCodePush .initializeUpdateAfterRestart ();
197+ } catch (Exception e ) {
198+ // The recreation method threw an unknown exception
199+ // so just simply fallback to restarting the Activity (if it exists)
200+ loadBundleLegacy ();
201+ }
202+
203+ } catch (Exception e ) {
204+ // Our reflection logic failed somewhere
205+ // so fall back to restarting the Activity (if it exists)
206+ CodePushUtils .log ("Failed to load the bundle, falling back to restarting the Activity (if it exists). " + e .getMessage ());
207+ loadBundleLegacy ();
208+ }
209+
210+ } else {
211+
212+ try {
213+ DevSupportManager devSupportManager = null ;
214+ ReactInstanceManager reactInstanceManager = resolveInstanceManager ();
215+ if (reactInstanceManager != null ) {
216+ devSupportManager = reactInstanceManager .getDevSupportManager ();
217+ }
218+ boolean isLiveReloadEnabled = isLiveReloadEnabled (devSupportManager );
219+
220+ mCodePush .clearDebugCacheIfNeeded (isLiveReloadEnabled );
221+ } catch (Exception e ) {
222+ // If we got error in out reflection we should clear debug cache anyway.
223+ mCodePush .clearDebugCacheIfNeeded (false );
147224 }
148225
149- String latestJSBundleFile = mCodePush .getJSBundleFileInternal (mCodePush .getAssetsBundleFileName ());
226+ try {
227+ // #1) Get the ReactInstanceManager instance, which is what includes the
228+ // logic to reload the current React context.
229+ final ReactInstanceManager instanceManager = resolveInstanceManager ();
230+ if (instanceManager == null ) {
231+ return ;
232+ }
233+
234+ String latestJSBundleFile = mCodePush .getJSBundleFileInternal (mCodePush .getAssetsBundleFileName ());
235+
236+ // #2) Update the locally stored JS bundle file path
237+ setJSBundle (instanceManager , latestJSBundleFile );
238+
239+ // #3) Get the context creation method and fire it on the UI thread (which RN enforces)
240+ new Handler (Looper .getMainLooper ()).post (new Runnable () {
241+ @ Override
242+ public void run () {
243+ try {
244+ // We don't need to resetReactRootViews anymore
245+ // due the issue https://github.com/facebook/react-native/issues/14533
246+ // has been fixed in RN 0.46.0
247+ //resetReactRootViews(instanceManager);
248+
249+ instanceManager .recreateReactContextInBackground ();
250+ mCodePush .initializeUpdateAfterRestart ();
251+ } catch (Exception e ) {
252+ // The recreation method threw an unknown exception
253+ // so just simply fallback to restarting the Activity (if it exists)
254+ loadBundleLegacy ();
255+ }
256+ }
257+ });
258+
259+ } catch (Exception e ) {
260+ // Our reflection logic failed somewhere
261+ // so fall back to restarting the Activity (if it exists)
262+ CodePushUtils .log ("Failed to load the bundle, falling back to restarting the Activity (if it exists). " + e .getMessage ());
263+ loadBundleLegacy ();
264+ }
150265
151- // #2) Update the locally stored JS bundle file path
152- setJSBundle ( instanceManager , latestJSBundleFile );
266+ }
267+ }
153268
154- // #3) Get the context creation method and fire it on the UI thread (which RN enforces)
155- new Handler (Looper .getMainLooper ()).post (new Runnable () {
156- @ Override
157- public void run () {
269+ private boolean isLiveReloadEnabled (DevSupportManager devSupportManager ) {
270+ if (devSupportManager != null ) {
271+ DeveloperSettings devSettings = devSupportManager .getDevSettings ();
272+ Method [] methods = devSettings .getClass ().getMethods ();
273+ for (Method m : methods ) {
274+ if (m .getName ().equals ("isReloadOnJSChangeEnabled" )) {
158275 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);
163-
164- instanceManager .recreateReactContextInBackground ();
165- mCodePush .initializeUpdateAfterRestart ();
166- } catch (Exception e ) {
167- // The recreation method threw an unknown exception
168- // so just simply fallback to restarting the Activity (if it exists)
169- loadBundleLegacy ();
276+ return (boolean ) m .invoke (devSettings );
277+ } catch (Exception x ) {
278+ return false ;
170279 }
171280 }
172- });
173-
174- } catch (Exception e ) {
175- // Our reflection logic failed somewhere
176- // so fall back to restarting the Activity (if it exists)
177- CodePushUtils .log ("Failed to load the bundle, falling back to restarting the Activity (if it exists). " + e .getMessage ());
178- loadBundleLegacy ();
281+ }
179282 }
283+
284+ return false ;
180285 }
181286
182287 // This workaround has been implemented in order to fix https://github.com/facebook/react-native/issues/14533
@@ -208,7 +313,7 @@ private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldExceptio
208313 return instanceManager ;
209314 }
210315
211- final Activity currentActivity = getCurrentActivity ();
316+ final Activity currentActivity = getReactApplicationContext (). getCurrentActivity ();
212317 if (currentActivity == null ) {
213318 return null ;
214319 }
@@ -219,6 +324,21 @@ private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldExceptio
219324 return instanceManager ;
220325 }
221326
327+ private ReactHost resolveReactHost () throws NoSuchFieldException , IllegalAccessException {
328+ ReactHost reactHost = CodePush .getReactHost ();
329+ if (reactHost != null ) {
330+ return reactHost ;
331+ }
332+
333+ final Activity currentActivity = getReactApplicationContext ().getCurrentActivity ();
334+ if (currentActivity == null ) {
335+ return null ;
336+ }
337+
338+ ReactApplication reactApplication = (ReactApplication ) currentActivity .getApplication ();
339+ return reactApplication .getReactHost ();
340+ }
341+
222342 private void restartAppInternal (boolean onlyIfUpdateIsPending ) {
223343 if (this ._restartInProgress ) {
224344 CodePushUtils .log ("Restart request queued until the current restart is completed" );
@@ -492,7 +612,7 @@ protected Void doInBackground(Void... params) {
492612 return null ;
493613 }
494614 }
495-
615+
496616 promise .resolve ("" );
497617 } catch (CodePushUnknownException e ) {
498618 CodePushUtils .log (e );
@@ -711,4 +831,18 @@ public void addListener(String eventName) {
711831 public void removeListeners (Integer count ) {
712832 // Remove upstream listeners, stop unnecessary background tasks
713833 }
834+
835+ public ReactHostDelegate getReactHostDelegate (ReactHostImpl reactHostImpl ) {
836+ try {
837+ Class <?> clazz = reactHostImpl .getClass ();
838+ Field field = clazz .getDeclaredField ("mReactHostDelegate" );
839+ field .setAccessible (true );
840+
841+ // Get the value of the field for the provided instance
842+ return (ReactHostDelegate ) field .get (reactHostImpl );
843+ } catch (NoSuchFieldException | IllegalAccessException e ) {
844+ e .printStackTrace ();
845+ return null ;
846+ }
847+ }
714848}
0 commit comments