mirror of
https://github.com/zhigang1992/react-native-code-push.git
synced 2026-05-19 19:39:54 +08:00
[Android] Support React instances with no Activity
This change aims to support applications which run React Native's catalyst instance in the background, sometimes without an activity. We're using this change today in an RN 0.27 application, which overrides `ReactActivity#createReactInstanceManager` to afford us control of the instance's lifecycle, so that we may run and launch without an `Activity` (i.e., in response to push notifications and other triggers). If we agree with this approach, I'll update this PR with documentation/etc.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.microsoft.codepush.react;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
@@ -44,6 +45,7 @@ public class CodePush implements ReactPackage {
|
||||
private Context mContext;
|
||||
private final boolean mIsDebugMode;
|
||||
|
||||
private static ReactInstanceHolder mReactInstanceHolder;
|
||||
private static CodePush mCurrentInstance;
|
||||
|
||||
public CodePush(String deploymentKey, Context context) {
|
||||
@@ -277,6 +279,17 @@ public class CodePush implements ReactPackage {
|
||||
mSettingsManager.removeFailedUpdates();
|
||||
}
|
||||
|
||||
public static void setReactInstanceHolder(ReactInstanceHolder reactInstanceHolder) {
|
||||
mReactInstanceHolder = reactInstanceHolder;
|
||||
}
|
||||
|
||||
static ReactInstanceManager getReactInstanceManager() {
|
||||
if (mReactInstanceHolder == null) {
|
||||
return null;
|
||||
}
|
||||
return mReactInstanceHolder.getReactInstanceManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
|
||||
CodePushNativeModule codePushModule = new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager);
|
||||
@@ -297,4 +310,4 @@ public class CodePush implements ReactPackage {
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
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;
|
||||
|
||||
@@ -35,7 +36,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
||||
private String mClientUniqueId = null;
|
||||
private LifecycleEventListener mLifecycleEventListener = null;
|
||||
private int mMinimumBackgroundDuration = 0;
|
||||
|
||||
|
||||
private CodePush mCodePush;
|
||||
private SettingsManager mSettingsManager;
|
||||
private CodePushTelemetryManager mTelemetryManager;
|
||||
@@ -77,16 +78,13 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
||||
return "CodePush";
|
||||
}
|
||||
|
||||
private boolean isReactApplication(Context context) {
|
||||
Class<?> reactApplicationClass = tryGetClass(REACT_APPLICATION_CLASS_NAME);
|
||||
if (reactApplicationClass != null && reactApplicationClass.isInstance(context)) {
|
||||
return true;
|
||||
private void loadBundleLegacy() {
|
||||
final Activity currentActivity = getCurrentActivity();
|
||||
if (currentActivity == null) {
|
||||
// The currentActivity can be null if it is backgrounded / destroyed, so we simply
|
||||
// no-op to prevent any null pointer exceptions.
|
||||
return;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void loadBundleLegacy(final Activity currentActivity) {
|
||||
mCodePush.invalidateCurrentInstance();
|
||||
|
||||
currentActivity.runOnUiThread(new Runnable() {
|
||||
@@ -99,41 +97,14 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
||||
|
||||
private void loadBundle() {
|
||||
mCodePush.clearDebugCacheIfNeeded();
|
||||
final Activity currentActivity = getCurrentActivity();
|
||||
|
||||
if (currentActivity == null) {
|
||||
// The currentActivity can be null if it is backgrounded / destroyed, so we simply
|
||||
// no-op to prevent any null pointer exceptions.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ReactInstanceManager instanceManager;
|
||||
// #1) Get the ReactInstanceManager instance, which is what includes the
|
||||
// logic to reload the current React context.
|
||||
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);
|
||||
final ReactInstanceManager instanceManager = resolveInstanceManager();
|
||||
if (instanceManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String latestJSBundleFile = mCodePush.getJSBundleFileInternal(mCodePush.getAssetsBundleFileName());
|
||||
|
||||
// #2) Update the locally stored JS bundle file path
|
||||
@@ -143,28 +114,61 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
||||
|
||||
// #3) Get the context creation method and fire it on the UI thread (which RN enforces)
|
||||
final Method recreateMethod = instanceManager.getClass().getMethod("recreateReactContextInBackground");
|
||||
|
||||
final ReactInstanceManager finalizedInstanceManager = instanceManager;
|
||||
currentActivity.runOnUiThread(new Runnable() {
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
recreateMethod.invoke(finalizedInstanceManager);
|
||||
recreateMethod.invoke(instanceManager);
|
||||
mCodePush.initializeUpdateAfterRestart();
|
||||
}
|
||||
catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
// The recreation method threw an unknown exception
|
||||
// so just simply fallback to restarting the Activity
|
||||
loadBundleLegacy(currentActivity);
|
||||
// so just simply fallback to restarting the Activity (if it exists)
|
||||
loadBundleLegacy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
// Our reflection logic failed somewhere
|
||||
// so fall back to restarting the Activity
|
||||
loadBundleLegacy(currentActivity);
|
||||
// so fall back to restarting the Activity (if it exists)
|
||||
loadBundleLegacy();
|
||||
}
|
||||
}
|
||||
|
||||
private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldException, IllegalAccessException {
|
||||
ReactInstanceManager instanceManager = CodePush.getReactInstanceManager();
|
||||
if (instanceManager != null) {
|
||||
return instanceManager;
|
||||
}
|
||||
|
||||
final Activity currentActivity = getCurrentActivity();
|
||||
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 {
|
||||
@@ -493,4 +497,4 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.microsoft.codepush.react;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
||||
/**
|
||||
* Provides access to a {@link ReactInstanceManager}.
|
||||
*
|
||||
* ReactNativeHost already implements this interface, if you make use of that react-native
|
||||
* component (just add `implements ReactInstanceHolder`).
|
||||
*/
|
||||
public interface ReactInstanceHolder {
|
||||
|
||||
/**
|
||||
* Get the current {@link ReactInstanceManager} instance. May return null.
|
||||
*/
|
||||
ReactInstanceManager getReactInstanceManager();
|
||||
}
|
||||
Reference in New Issue
Block a user