소스 검색

Unmount screens before handling bundle reload

When ever bundle was reloaded, all currently registered screens
where recreated since they were still attached to the react context.
This commit adds, in a very hacky way, means to respond to bundle
reload events.
Guy Carmeli 8 년 전
부모
커밋
34f09ff4dd

+ 13
- 0
android/app/src/main/java/com/reactnativenavigation/activities/BaseReactActivity.java 파일 보기

@@ -1,6 +1,9 @@
1 1
 package com.reactnativenavigation.activities;
2 2
 
3
+import android.content.BroadcastReceiver;
4
+import android.content.Context;
3 5
 import android.content.Intent;
6
+import android.content.IntentFilter;
4 7
 import android.content.res.Configuration;
5 8
 import android.os.Build;
6 9
 import android.os.Bundle;
@@ -26,6 +29,7 @@ import com.facebook.react.bridge.Arguments;
26 29
 import com.facebook.react.bridge.ReadableMap;
27 30
 import com.facebook.react.bridge.WritableMap;
28 31
 import com.facebook.react.common.ReactConstants;
32
+import com.facebook.react.devsupport.DevServerHelper;
29 33
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
30 34
 import com.facebook.react.shell.MainReactPackage;
31 35
 import com.reactnativenavigation.BuildConfig;
@@ -367,6 +371,15 @@ public abstract class BaseReactActivity extends AppCompatActivity implements Def
367 371
         return super.onKeyUp(keyCode, event);
368 372
     }
369 373
 
374
+    /**
375
+     * Called after bundle was reloaded. This is a good chance to clean up previously connected react views.
376
+     */
377
+    public void onJSBundleReloaded() {
378
+        removeAllReactViews();
379
+    }
380
+
381
+    protected abstract void removeAllReactViews();
382
+
370 383
     @Override
371 384
     public void onBackPressed() {
372 385
         if (getScreenStackSize() > 1) {

+ 7
- 0
android/app/src/main/java/com/reactnativenavigation/activities/BottomTabActivity.java 파일 보기

@@ -267,4 +267,11 @@ public class BottomTabActivity extends BaseReactActivity implements AHBottomNavi
267 267
         }
268 268
         this.onTabSelected(0, false);
269 269
     }
270
+
271
+    @Override
272
+    protected void removeAllReactViews() {
273
+        for (ScreenStack screenStack : mScreenStacks) {
274
+            screenStack.removeAllReactViews();
275
+        }
276
+    }
270 277
 }

+ 4
- 0
android/app/src/main/java/com/reactnativenavigation/activities/RootActivity.java 파일 보기

@@ -45,4 +45,8 @@ public class RootActivity extends BaseReactActivity {
45 45
         return 0;
46 46
     }
47 47
 
48
+    @Override
49
+    public void removeAllReactViews() {
50
+
51
+    }
48 52
 }

+ 5
- 0
android/app/src/main/java/com/reactnativenavigation/activities/SingleScreenActivity.java 파일 보기

@@ -103,4 +103,9 @@ public class SingleScreenActivity extends BaseReactActivity {
103 103
     public int getScreenStackSize() {
104 104
         return mScreenStack.getStackSize();
105 105
     }
106
+
107
+    @Override
108
+    protected void removeAllReactViews() {
109
+        mScreenStack.removeAllReactViews();
110
+    }
106 111
 }

+ 5
- 1
android/app/src/main/java/com/reactnativenavigation/activities/TabActivity.java 파일 보기

@@ -1,6 +1,5 @@
1 1
 package com.reactnativenavigation.activities;
2 2
 
3
-import android.support.design.widget.CoordinatorLayout;
4 3
 import android.support.v4.view.ViewPager;
5 4
 import android.view.Menu;
6 5
 import android.view.ViewGroup;
@@ -69,6 +68,11 @@ public class TabActivity extends BaseReactActivity {
69 68
         return ret;
70 69
     }
71 70
 
71
+    @Override
72
+    public void removeAllReactViews() {
73
+
74
+    }
75
+
72 76
     @Override
73 77
     public void push(Screen screen) {
74 78
         super.push(screen);

+ 71
- 0
android/app/src/main/java/com/reactnativenavigation/core/RctManager.java 파일 보기

@@ -1,15 +1,21 @@
1 1
 package com.reactnativenavigation.core;
2 2
 
3 3
 import android.app.Application;
4
+import android.util.Log;
4 5
 
5 6
 import com.facebook.react.LifecycleState;
6 7
 import com.facebook.react.ReactInstanceManager;
7 8
 import com.facebook.react.ReactPackage;
9
+import com.facebook.react.bridge.JavaJSExecutor;
8 10
 import com.facebook.react.bridge.ReactContext;
9 11
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
10 12
 import com.facebook.react.bridge.WritableMap;
13
+import com.facebook.react.devsupport.DevSupportManager;
14
+import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler;
11 15
 import com.reactnativenavigation.activities.BaseReactActivity;
12 16
 import com.reactnativenavigation.core.objects.Screen;
17
+import com.reactnativenavigation.utils.ContextProvider;
18
+import com.reactnativenavigation.utils.ReflectionUtils;
13 19
 
14 20
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
15 21
 
@@ -17,6 +23,7 @@ import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDevice
17 23
  * Created by guyc on 22/02/16.
18 24
  */
19 25
 public class RctManager {
26
+    private static final String TAG = "RctManager";
20 27
     private static final String KEY_EVENT_ID = "id";
21 28
     private static RctManager sInstance;
22 29
 
@@ -68,9 +75,34 @@ public class RctManager {
68 75
         }
69 76
 
70 77
         mReactManager = builder.build();
78
+        setupDevSupportHandler(mReactManager);
79
+
71 80
         return mReactManager;
72 81
     }
73 82
 
83
+    /**
84
+     * Inject our CustomDevCommandsHandler to {@code reactInstanceManager} so we can detect when the bundle was
85
+     * parsed and is about to load.
86
+     * @param reactInstanceManager
87
+     */
88
+    private void setupDevSupportHandler(ReactInstanceManager reactInstanceManager) {
89
+        final ReactInstanceDevCommandsHandler devInterface = (ReactInstanceDevCommandsHandler)
90
+                ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevInterface");
91
+        if (devInterface == null) {
92
+            Log.e(TAG, "Could not get field: mDevInterface");
93
+            return;
94
+        }
95
+
96
+        // Create customDevCommandsHandler
97
+        CustomDevCommandsHandler customDevCommandsHandler = new CustomDevCommandsHandler(devInterface);
98
+        ReflectionUtils.setField(reactInstanceManager, "mDevInterface", customDevCommandsHandler);
99
+
100
+        // Set customDevCommandsHandler in devSupportManager. Fun =).
101
+        DevSupportManager devSupportManager = (DevSupportManager)
102
+                ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevSupportManager");
103
+        ReflectionUtils.setField(devSupportManager, "mReactInstanceCommandsHandler", customDevCommandsHandler);
104
+    }
105
+
74 106
     public <T extends ReactContextBaseJavaModule> T getNativeModule(Class<T> nativeModuleClass) {
75 107
         if (mReactManager == null || mReactManager.getCurrentReactContext() == null) {
76 108
             return null;
@@ -113,5 +145,44 @@ public class RctManager {
113 145
         mReactManager = null;
114 146
         sInstance = null;
115 147
     }
148
+
149
+    /**
150
+     * Proxy for {@link ReactInstanceDevCommandsHandler} used by {@link DevSupportManager} for requesting React
151
+     * instance recreation. Used to notify {@link BaseReactActivity} that the bundle has been reloaded.
152
+     */
153
+    private static class CustomDevCommandsHandler implements ReactInstanceDevCommandsHandler {
154
+        private ReactInstanceDevCommandsHandler mCommandsHandler;
155
+
156
+        public CustomDevCommandsHandler(ReactInstanceDevCommandsHandler commandsHandler) {
157
+            mCommandsHandler = commandsHandler;
158
+        }
159
+
160
+        @Override
161
+        public void onReloadWithJSDebugger(JavaJSExecutor.Factory proxyExecutorFactory) {
162
+            onJSBundleReloaded();
163
+            mCommandsHandler.onReloadWithJSDebugger(proxyExecutorFactory);
164
+        }
165
+
166
+        @Override
167
+        public void onJSBundleLoadedFromServer() {
168
+            onJSBundleReloaded();
169
+            mCommandsHandler.onJSBundleLoadedFromServer();
170
+        }
171
+
172
+        /**
173
+         * Detach previously added ReactRootViews before handling bundle.
174
+         */
175
+        private void onJSBundleReloaded() {
176
+            BaseReactActivity context = ContextProvider.getActivityContext();
177
+            if (context != null) {
178
+                context.onJSBundleReloaded();
179
+            }
180
+        }
181
+
182
+        @Override
183
+        public void toggleElementInspector() {
184
+            mCommandsHandler.toggleElementInspector();
185
+        }
186
+    }
116 187
 }
117 188
 

+ 0
- 2
android/app/src/main/java/com/reactnativenavigation/utils/IconUtils.java 파일 보기

@@ -1,13 +1,11 @@
1 1
 package com.reactnativenavigation.utils;
2 2
 
3 3
 import android.content.Context;
4
-import android.content.res.Resources;
5 4
 import android.graphics.Bitmap;
6 5
 import android.graphics.BitmapFactory;
7 6
 import android.graphics.drawable.BitmapDrawable;
8 7
 import android.graphics.drawable.Drawable;
9 8
 import android.net.Uri;
10
-import android.util.DisplayMetrics;
11 9
 
12 10
 import com.reactnativenavigation.BuildConfig;
13 11
 

+ 25
- 4
android/app/src/main/java/com/reactnativenavigation/utils/ReflectionUtils.java 파일 보기

@@ -1,24 +1,45 @@
1 1
 package com.reactnativenavigation.utils;
2 2
 
3 3
 import java.lang.reflect.Field;
4
+import java.lang.reflect.Method;
4 5
 
5 6
 /**
6 7
  * Created by guyc on 14/04/16.
7 8
  */
8 9
 public class ReflectionUtils {
9 10
 
10
-    public static boolean setBooleanField(Object obj, String name, Boolean value) {
11
+    public static boolean setField(Object obj, String name, Object value) {
11 12
         Field field;
12 13
         try {
13 14
             field = obj.getClass().getDeclaredField(name);
14 15
             field.setAccessible(true);
15 16
             field.set(obj, value);
16 17
             return true;
17
-        } catch (NoSuchFieldException e) {
18
-            e.printStackTrace();
19
-        } catch (IllegalAccessException e) {
18
+        } catch (Exception e) {
20 19
             e.printStackTrace();
21 20
         }
22 21
         return false;
23 22
     }
23
+
24
+    public static Object getDeclaredField(Object obj, String fieldName) {
25
+        try {
26
+            Field f = obj.getClass().getDeclaredField(fieldName);
27
+            f.setAccessible(true);
28
+            return f.get(obj); //IllegalAccessException
29
+        } catch (Exception e) {
30
+            e.printStackTrace();
31
+        }
32
+        return null;
33
+    }
34
+
35
+    public static Object invoke(Object object, String methodName) {
36
+        try {
37
+            Method method = object.getClass().getDeclaredMethod(methodName);
38
+            method.setAccessible(true);
39
+            return method.invoke(object);
40
+        } catch (Exception e) {
41
+            e.printStackTrace();
42
+        }
43
+        return null;
44
+    }
24 45
 }

+ 7
- 3
android/app/src/main/java/com/reactnativenavigation/views/RctView.java 파일 보기

@@ -67,7 +67,7 @@ public class RctView extends FrameLayout {
67 67
      */
68 68
     public void onTemporallyRemovedFromScreen() {
69 69
         // Hack in order to prevent the react view from getting unmounted
70
-        ReflectionUtils.setBooleanField(mReactRootView, "mAttachScheduled", true);
70
+        ReflectionUtils.setField(mReactRootView, "mAttachScheduled", true);
71 71
     }
72 72
 
73 73
     /**
@@ -75,7 +75,7 @@ public class RctView extends FrameLayout {
75 75
      * executed and componentWillUnmount is called
76 76
      */
77 77
     public void onRemoveFromScreen() {
78
-        ReflectionUtils.setBooleanField(mReactRootView, "mAttachScheduled", false);
78
+        ReflectionUtils.setField(mReactRootView, "mAttachScheduled", false);
79 79
     }
80 80
 
81 81
     /**
@@ -83,7 +83,11 @@ public class RctView extends FrameLayout {
83 83
      * executed and componentWillUnmount is called
84 84
      */
85 85
     public void onReAddToScreen() {
86
-        ReflectionUtils.setBooleanField(mReactRootView, "mAttachScheduled", false);
86
+        ReflectionUtils.setField(mReactRootView, "mAttachScheduled", false);
87
+    }
88
+
89
+    public void detachFromScreen() {
90
+        ReflectionUtils.invoke(mReactRootView, "onDetachedFromWindow");
87 91
     }
88 92
 }
89 93
 

+ 11
- 0
android/app/src/main/java/com/reactnativenavigation/views/ScreenStack.java 파일 보기

@@ -165,4 +165,15 @@ public class ScreenStack extends FrameLayout {
165 165
 
166 166
         parent.addView(this, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
167 167
     }
168
+
169
+    public void removeAllReactViews() {
170
+        while (!mStack.empty()) {
171
+            RctView view = mStack.pop().view;
172
+            // Ensure view will be properly detached and unmounted
173
+            view.onRemoveFromScreen();
174
+            // Unmount the view
175
+            view.detachFromScreen();
176
+        }
177
+        removeAllViews();
178
+    }
168 179
 }