[RN] Make all delegate / listener methods run in the main / UI thread

This commit is contained in:
Saúl Ibarra Corretgé 2018-05-17 13:38:59 +02:00 committed by Lyubo Marinov
parent ccbf3efa38
commit ef7fb1a7b0
6 changed files with 132 additions and 69 deletions

View File

@ -28,6 +28,7 @@ import org.jitsi.meet.sdk.invite.InviteController;
import org.jitsi.meet.sdk.invite.InviteControllerListener;
import com.calendarevents.CalendarEventsPackage;
import com.facebook.react.bridge.UiThreadUtil;
import java.util.ArrayList;
import java.util.HashMap;
@ -65,6 +66,8 @@ public class MainActivity extends JitsiMeetActivity {
if (BuildConfig.DEBUG && view != null) {
view.setListener(new JitsiMeetViewListener() {
private void on(String name, Map<String, Object> data) {
UiThreadUtil.assertOnUiThread();
// Log with the tag "ReactNative" in order to have the log
// visible in react-native log-android as well.
Log.d(
@ -112,6 +115,8 @@ public class MainActivity extends JitsiMeetActivity {
inviteController.setListener(new InviteControllerListener() {
public void beginAddPeople(
AddPeopleController addPeopleController) {
UiThreadUtil.assertOnUiThread();
onInviteControllerBeginAddPeople(
inviteController,
addPeopleController);
@ -129,6 +134,8 @@ public class MainActivity extends JitsiMeetActivity {
private void onAddPeopleControllerInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees) {
UiThreadUtil.assertOnUiThread();
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
// it is going to be memory-leaked in the associated InviteController
// and no subsequent InviteButton clicks/taps will be delivered.
@ -142,6 +149,8 @@ public class MainActivity extends JitsiMeetActivity {
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query) {
UiThreadUtil.assertOnUiThread();
int size = results.size();
if (size > 0) {
@ -182,6 +191,8 @@ public class MainActivity extends JitsiMeetActivity {
private void onInviteControllerBeginAddPeople(
InviteController inviteController,
AddPeopleController addPeopleController) {
UiThreadUtil.assertOnUiThread();
// Log with the tag "ReactNative" in order to have the log visible in
// react-native log-android as well.
Log.d(

View File

@ -21,9 +21,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import org.jitsi.meet.sdk.JitsiMeetView;
import org.jitsi.meet.sdk.JitsiMeetViewListener;
import com.facebook.react.bridge.UiThreadUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ -150,43 +148,53 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
* @param scope
*/
@ReactMethod
public void sendEvent(String name, ReadableMap data, String scope) {
// The JavaScript App needs to provide uniquely identifying information
// to the native ExternalAPI module so that the latter may match the
// former to the native JitsiMeetView which hosts it.
JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope);
public void sendEvent(final String name,
final ReadableMap data,
final String scope) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
// The JavaScript App needs to provide uniquely identifying
// information to the native ExternalAPI module so that the
// latter may match the former to the native JitsiMeetView which
// hosts it.
JitsiMeetView view
= JitsiMeetView.findViewByExternalAPIScope(scope);
if (view == null) {
return;
}
if (view == null) {
return;
}
maybeSetViewURL(name, data, view);
maybeSetViewURL(name, data, view);
JitsiMeetViewListener listener = view.getListener();
JitsiMeetViewListener listener = view.getListener();
if (listener == null) {
return;
}
if (listener == null) {
return;
}
Method method = JITSI_MEET_VIEW_LISTENER_METHODS.get(name);
Method method = JITSI_MEET_VIEW_LISTENER_METHODS.get(name);
if (method != null) {
try {
method.invoke(listener, toHashMap(data));
} catch (IllegalAccessException e) {
// FIXME There was a multicatch for IllegalAccessException and
// InvocationTargetException, but Android Studio complained
// with:
// "Multi-catch with these reflection exceptions requires
// API level 19 (current min is 16) because they get compiled to
// the common but new super type ReflectiveOperationException.
// As a workaround either create individual catch statements, or
// catch Exception."
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
if (method != null) {
try {
method.invoke(listener, toHashMap(data));
} catch (IllegalAccessException e) {
// FIXME There was a multicatch for
// IllegalAccessException and InvocationTargetException,
// but Android Studio complained with: "Multi-catch with
// these reflection exceptions requires API level 19
// (current min is 16) because they get compiled to the
// common but new super type
// ReflectiveOperationException. As a workaround either
// create individual catch statements, or
// catch Exception."
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
});
}
/**

View File

@ -22,6 +22,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.UiThreadUtil;
import org.jitsi.meet.sdk.JitsiMeetView;
@ -42,13 +43,18 @@ public class InviteModule extends ReactContextBaseJavaModule {
* {@code JitsiMeetView} whose {@code InviteButton} was clicked/tapped.
*/
@ReactMethod
public void beginAddPeople(String externalAPIScope) {
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
public void beginAddPeople(final String externalAPIScope) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController != null) {
inviteController.beginAddPeople(getReactApplicationContext());
}
if (inviteController != null) {
inviteController.beginAddPeople(getReactApplicationContext());
}
}
});
}
private InviteController findInviteControllerByExternalAPIScope(
@ -72,21 +78,26 @@ public class InviteModule extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void inviteSettled(
String externalAPIScope,
String addPeopleControllerScope,
ReadableArray failedInvitees) {
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
final String externalAPIScope,
final String addPeopleControllerScope,
final ReadableArray failedInvitees) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController == null) {
Log.w(
"InviteModule",
"Invite settled, but failed to find active controller to notify");
} else {
inviteController.inviteSettled(
addPeopleControllerScope,
failedInvitees);
}
if (inviteController == null) {
Log.w(
"InviteModule",
"Invite settled, but failed to find active controller to notify");
} else {
inviteController.inviteSettled(
addPeopleControllerScope,
failedInvitees);
}
}
});
}
/**
@ -98,22 +109,27 @@ public class InviteModule extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void receivedResults(
String externalAPIScope,
String addPeopleControllerScope,
String query,
ReadableArray results) {
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
final String externalAPIScope,
final String addPeopleControllerScope,
final String query,
final ReadableArray results) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController == null) {
Log.w(
"InviteModule",
"Received results, but failed to find active controller to send results back");
} else {
inviteController.receivedResultsForQuery(
addPeopleControllerScope,
query,
results);
}
if (inviteController == null) {
Log.w(
"InviteModule",
"Received results, but failed to find active controller to send results back");
} else {
inviteController.receivedResultsForQuery(
addPeopleControllerScope,
query,
results);
}
}
});
}
}

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
#import <assert.h>
#import "ViewController.h"
/**
@ -64,6 +66,9 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) {
NSLog(
@"[%s:%d] JitsiMeetViewDelegate %@ %@",
__FILE__, __LINE__, name, data);
assert([NSThread isMainThread]
&& "Delegate method called in a non-main thread");
}
- (void)conferenceFailed:(NSDictionary *)data {
@ -97,6 +102,9 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) {
@"[%s:%d] JMInviteControllerDelegate %s",
__FILE__, __LINE__, __FUNCTION__);
assert([NSThread isMainThread]
&& "Delegate method called in a non-main thread");
NSString *query = ADD_PEOPLE_CONTROLLER_QUERY;
JitsiMeetView *view = (JitsiMeetView *) self.view;
JMInviteController *inviteController = view.inviteController;
@ -119,6 +127,9 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) {
- (void)addPeopleController:(JMAddPeopleController * _Nonnull)controller
didReceiveResults:(NSArray<NSDictionary *> * _Nonnull)results
forQuery:(NSString * _Nonnull)query {
assert([NSThread isMainThread]
&& "Delegate method called in a non-main thread");
NSUInteger count = results.count;
if (count) {
@ -151,6 +162,9 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) {
- (void) inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees
fromSearchController:(JMAddPeopleController * _Nonnull)addPeopleController {
assert([NSThread isMainThread]
&& "Delegate method called in a non-main thread");
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
// is going to be memory-leaked in the associated JMInviteController and no
// subsequent InviteButton clicks/taps will be delivered. Technically,

View File

@ -25,6 +25,13 @@
RCT_EXPORT_MODULE();
/**
* Make sure all methods in this module are called in the main (i.e. UI) thread.
*/
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
/**
* Dispatches an event that occurred on JavaScript to the view's delegate.
*

View File

@ -40,6 +40,13 @@ RCT_EXPORT_MODULE();
];
}
/**
* Make sure all methods in this module are called in the main (i.e. UI) thread.
*/
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
/**
* Initiates the process to add people. This involves calling a delegate method
* in the JMInviteControllerDelegate so the native host application can start