mirror of
https://github.com/zhigang1992/react-native-code-push.git
synced 2026-06-10 23:59:42 +08:00
Merge pull request #491 from igrayson/patch-1
[Android] Support React instances with no Activity
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -132,6 +132,7 @@ gen/
|
||||
.gradle/
|
||||
build/
|
||||
*/build/
|
||||
android/app/gradle*
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
@@ -149,4 +150,4 @@ proguard/
|
||||
captures/
|
||||
|
||||
# Remove after this framework is published on NPM
|
||||
code-push-plugin-testing-framework/node_modules
|
||||
code-push-plugin-testing-framework/node_modules
|
||||
|
||||
51
README.md
51
README.md
@@ -343,6 +343,57 @@ public class MainActivity extends ReactActivity {
|
||||
}
|
||||
```
|
||||
|
||||
#### Background React Instances ####
|
||||
|
||||
**This section is only necessary if you're *explicitly* launching a React Native instance without an `Activity` (for example, from within a native push notification receiver). For these situations, CodePush must be told how to find your React Native instance.**
|
||||
|
||||
In order to update/restart your React Native instance, CodePush must be configured with a `ReactInstanceHolder` before attempting to restart an instance in the background. This is usually done in your `Application` implementation.
|
||||
|
||||
**For React Native >= v0.29**
|
||||
|
||||
Update the `MainApplication.java` file to use CodePush via the following changes:
|
||||
|
||||
```java
|
||||
...
|
||||
// 1. Declare your ReactNativeHost to extend ReactInstanceHolder. ReactInstanceHolder is a subset of ReactNativeHost, so no additional implementation is needed.
|
||||
import com.microsoft.codepush.react.ReactInstanceHolder;
|
||||
|
||||
public class MyReactNativeHost extends ReactNativeHost implements ReactInstanceHolder {
|
||||
// ... usual overrides
|
||||
}
|
||||
|
||||
// 2. Provide your ReactNativeHost to CodePush.
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
private final MyReactNativeHost mReactNativeHost = new MyReactNativeHost(this);
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
CodePush.setReactInstanceHolder(mReactNativeHost);
|
||||
super.onCreate();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**For React Native v0.19 - v0.28**
|
||||
|
||||
Before v0.29, React Native did not provide a `ReactNativeHost` abstraction. If you're launching a background instance, you'll likely have built your own, which should now implement `ReactInstanceHolder`. Once that's done...
|
||||
|
||||
```java
|
||||
// 1. Provide your ReactInstanceHolder to CodePush.
|
||||
|
||||
public class MainApplication extends Application {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
// ... initialize your instance holder
|
||||
CodePush.setReactInstanceHolder(myInstanceHolder);
|
||||
super.onCreate();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In order to effectively make use of the `Staging` and `Production` deployments that were created along with your CodePush app, refer to the [multi-deployment testing](#multi-deployment-testing) docs below before actually moving your app's usage of CodePush into production.
|
||||
|
||||
## Windows Setup
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -36,7 +37,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;
|
||||
@@ -78,16 +79,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() {
|
||||
@@ -100,41 +98,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
|
||||
@@ -155,28 +126,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 {
|
||||
@@ -505,4 +509,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