Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
package com.microsoft.codepush.react;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class CodePush implements ReactPackage {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.microsoft.codepush.react;

import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.view.Choreographer;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
Expand All @@ -18,8 +16,8 @@
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ReactChoreographer;

import org.json.JSONArray;
Expand All @@ -44,9 +42,6 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
private CodePushTelemetryManager mTelemetryManager;
private CodePushUpdateManager mUpdateManager;

private static final String REACT_APPLICATION_CLASS_NAME = "com.facebook.react.ReactApplication";
private static final String REACT_NATIVE_HOST_CLASS_NAME = "com.facebook.react.ReactNativeHost";

public CodePushNativeModule(ReactApplicationContext reactContext, CodePush codePush, CodePushUpdateManager codePushUpdateManager, CodePushTelemetryManager codePushTelemetryManager, SettingsManager settingsManager) {
super(reactContext);

Expand Down Expand Up @@ -100,7 +95,7 @@ public void run() {

// Use reflection to find and set the appropriate fields on ReactInstanceManager. See #556 for a proposal for a less brittle way
// to approach this.
private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBundleFile) throws NoSuchFieldException, IllegalAccessException {
private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBundleFile) throws IllegalAccessException {
try {
Field bundleLoaderField = instanceManager.getClass().getDeclaredField("mBundleLoader");
Class<?> jsBundleLoaderClass = Class.forName("com.facebook.react.cxxbridge.JSBundleLoader");
Expand All @@ -109,7 +104,7 @@ private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBu
? "createAssetLoader" : "createFileLoader";

Method[] methods = jsBundleLoaderClass.getDeclaredMethods();
for (Method method : methods) {
for (Method method : methods) {
if (method.getName().equals(createFileLoaderMethodName)) {
createFileLoaderMethod = method;
break;
Expand All @@ -127,7 +122,7 @@ private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBu
// RN >= v0.34
latestJSBundleLoader = createFileLoaderMethod.invoke(jsBundleLoaderClass, latestJSBundleFile);
} else if (numParameters == 2) {
// RN >= v0.31 && RN < v0.34 or AssetLoader instance
// AssetLoader instance
latestJSBundleLoader = createFileLoaderMethod.invoke(jsBundleLoaderClass, getReactApplicationContext(), latestJSBundleFile);
} else {
throw new NoSuchMethodException("Could not find a recognized 'createFileLoader' method");
Expand All @@ -136,14 +131,13 @@ private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBu
bundleLoaderField.setAccessible(true);
bundleLoaderField.set(instanceManager, latestJSBundleLoader);
} catch (Exception e) {
// RN < v0.31
Field jsBundleField = instanceManager.getClass().getDeclaredField("mJSBundleFile");
jsBundleField.setAccessible(true);
jsBundleField.set(instanceManager, latestJSBundleFile);
CodePushUtils.log("Unable to set JSBundle - CodePush may not support this version of React Native");
throw new IllegalAccessException("Could not setJSBundle");
}
}

private void loadBundle() {
clearLifecycleEventListener();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any issue with clearing this before we attempt to load the bundle? We don't need the callback after this point.

We must call this before loadBundleLegacy() or it will go into an infinite restart loop.

mCodePush.clearDebugCacheIfNeeded();
try {
// #1) Get the ReactInstanceManager instance, which is what includes the
Expand All @@ -159,12 +153,11 @@ private void loadBundle() {
setJSBundle(instanceManager, latestJSBundleFile);

// #3) Get the context creation method and fire it on the UI thread (which RN enforces)
final Method recreateMethod = instanceManager.getClass().getMethod("recreateReactContextInBackground");
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
try {
recreateMethod.invoke(instanceManager);
instanceManager.recreateReactContextInBackground();
mCodePush.initializeUpdateAfterRestart();
} catch (Exception e) {
// The recreation method threw an unknown exception
Expand All @@ -181,6 +174,14 @@ public void run() {
}
}

private void clearLifecycleEventListener() {
// Remove LifecycleEventListener to prevent infinite restart loop
if (mLifecycleEventListener != null) {
getReactApplicationContext().removeLifecycleEventListener(mLifecycleEventListener);
mLifecycleEventListener = null;
}
}

// Use reflection to find the ReactInstanceManager. See #556 for a proposal for a less brittle way to approach this.
private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldException, IllegalAccessException {
ReactInstanceManager instanceManager = CodePush.getReactInstanceManager();
Expand All @@ -192,37 +193,11 @@ private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldExceptio
if (currentActivity == null) {
return null;
}
try {
// In RN >=0.29, the "mReactInstanceManager" field yields a null value, so we try
// to get the instance manager via the ReactNativeHost, which only exists in 0.29.
Method getApplicationMethod = ReactActivity.class.getMethod("getApplication");
Object reactApplication = getApplicationMethod.invoke(currentActivity);
Class<?> reactApplicationClass = tryGetClass(REACT_APPLICATION_CLASS_NAME);
Method getReactNativeHostMethod = reactApplicationClass.getMethod("getReactNativeHost");
Object reactNativeHost = getReactNativeHostMethod.invoke(reactApplication);
Class<?> reactNativeHostClass = tryGetClass(REACT_NATIVE_HOST_CLASS_NAME);
Method getReactInstanceManagerMethod = reactNativeHostClass.getMethod("getReactInstanceManager");
instanceManager = (ReactInstanceManager)getReactInstanceManagerMethod.invoke(reactNativeHost);
} catch (Exception e) {
// The React Native version might be older than 0.29, or the activity does not
// extend ReactActivity, so we try to get the instance manager via the
// "mReactInstanceManager" field.
Class instanceManagerHolderClass = currentActivity instanceof ReactActivity
? ReactActivity.class
: currentActivity.getClass();
Field instanceManagerField = instanceManagerHolderClass.getDeclaredField("mReactInstanceManager");
instanceManagerField.setAccessible(true);
instanceManager = (ReactInstanceManager)instanceManagerField.get(currentActivity);
}
return instanceManager;
}

private Class tryGetClass(String className) {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
return null;
}
ReactApplication reactApplication = (ReactApplication) currentActivity.getApplication();
instanceManager = reactApplication.getReactNativeHost().getReactInstanceManager();

return instanceManager;
}

@ReactMethod
Expand Down