diff --git a/android/build.gradle b/android/build.gradle index 36a7cdb469ed88991180eec5cd878dc0a6178a27..c72606f8178c54e37a61f8c6cf852130cb9db945 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,6 +28,11 @@ android { repositories { mavenCentral() + jcenter() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$rootDir/../node_modules/react-native/android" + } } dependencies { diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..c3591a17aef0cccd4d6cf5259d9a1b06d3f273f7 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Sep 16 10:14:34 MDT 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/android/gradlew b/android/gradlew new file mode 100644 index 0000000000000000000000000000000000000000..9d82f78915133e1c35a6ea51252590fb38efac2f --- /dev/null +++ b/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000000000000000000000000000000000000..aec99730b4e8fcd90b57a0e8e01544fea7c31a89 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/src/main/java/com/reactlibrary/BaseReactPackage.java b/android/src/main/java/com/reactlibrary/BaseReactPackage.java new file mode 100644 index 0000000000000000000000000000000000000000..37c014ce71e3098f4833a29ab1e0c95e60f89e49 --- /dev/null +++ b/android/src/main/java/com/reactlibrary/BaseReactPackage.java @@ -0,0 +1,62 @@ +package com.reactlibrary; + +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.devsupport.JSCHeapCapture; +import com.facebook.react.modules.appstate.AppStateModule; +import com.facebook.react.modules.core.ExceptionsManagerModule; +import com.facebook.react.modules.core.Timing; +import com.facebook.react.modules.debug.SourceCodeModule; +import com.facebook.react.modules.intent.IntentModule; +import com.facebook.react.modules.location.LocationModule; +import com.facebook.react.modules.netinfo.NetInfoModule; +import com.facebook.react.modules.network.NetworkingModule; +import com.facebook.react.modules.storage.AsyncStorageModule; +import com.facebook.react.modules.systeminfo.AndroidInfoModule; +import com.facebook.react.modules.vibration.VibrationModule; +import com.facebook.react.modules.websocket.WebSocketModule; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class BaseReactPackage implements ReactPackage { + + private final ReactInstanceManager reactInstanceManager; + + public BaseReactPackage(ReactInstanceManager reactInstanceManager) { + this.reactInstanceManager = reactInstanceManager; + } + + @Override + public List createNativeModules(ReactApplicationContext catalystApplicationContext) { + return Arrays.asList( + // Core list + new AndroidInfoModule(), + new ExceptionsManagerModule(reactInstanceManager.getDevSupportManager()), + new AppStateModule(catalystApplicationContext), + new Timing(catalystApplicationContext, reactInstanceManager.getDevSupportManager()), + new UIManagerStubModule(catalystApplicationContext), + new SourceCodeModule(catalystApplicationContext), + new JSCHeapCapture(catalystApplicationContext), + + // Main list + new AsyncStorageModule(catalystApplicationContext), + new IntentModule(catalystApplicationContext), + new LocationModule(catalystApplicationContext), + new NetworkingModule(catalystApplicationContext), + new NetInfoModule(catalystApplicationContext), + new VibrationModule(catalystApplicationContext), + new WebSocketModule(catalystApplicationContext), + new ThreadSelfModule(catalystApplicationContext) + ); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return new ArrayList<>(0); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/reactlibrary/JSThread.java b/android/src/main/java/com/reactlibrary/JSThread.java new file mode 100644 index 0000000000000000000000000000000000000000..78f105d19316fbf742b1b024b380554667c3a818 --- /dev/null +++ b/android/src/main/java/com/reactlibrary/JSThread.java @@ -0,0 +1,72 @@ +package com.reactlibrary; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.modules.core.DeviceEventManagerModule; + +import java.util.Random; + +public class JSThread { + private int id; + + private String jsSlugname; + private ReactApplicationContext reactContext; + + public JSThread(String jsSlugname) { + this.id = Math.abs(new Random().nextInt()); + this.jsSlugname = jsSlugname; + } + + public int getThreadId() { + return this.id; + } + + public String getName() { + return jsSlugname; + } + + public void runFromContext(ReactApplicationContext context, ReactContextBuilder reactContextBuilder) throws Exception { + if (reactContext != null) { + return; + } + + reactContext = reactContextBuilder.build(); + + ThreadSelfModule threadSelfModule = reactContext.getNativeModule(ThreadSelfModule.class); + threadSelfModule.initialize(id, context); + } + + public void postMessage(String message) { + if (reactContext == null) { + return; + } + + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("ThreadMessage", message); + } + + public void onHostResume() { + if (reactContext == null) { + return; + } + + reactContext.onHostResume(null); + } + + public void onHostPause() { + if (reactContext == null) { + return; + } + + reactContext.onHostPause(); + } + + public void terminate() { + if (reactContext == null) { + return; + } + + reactContext.onHostPause(); + reactContext.destroy(); + reactContext = null; + } +} \ No newline at end of file diff --git a/android/src/main/java/com/reactlibrary/RNThreadModule.java b/android/src/main/java/com/reactlibrary/RNThreadModule.java index 4ee18fdfadda6f83cd7da1908d8edf1b4ad39fc0..58f74d9251bfbfb9cf7897036ff7b6c938642a6d 100644 --- a/android/src/main/java/com/reactlibrary/RNThreadModule.java +++ b/android/src/main/java/com/reactlibrary/RNThreadModule.java @@ -1,22 +1,216 @@ - package com.reactlibrary; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JSBundleLoader; +import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.Callback; +import com.facebook.react.devsupport.interfaces.DevSupportManager; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okio.Okio; +import okio.Sink; + + +public class RNThreadModule extends ReactContextBaseJavaModule implements LifecycleEventListener { -public class RNThreadModule extends ReactContextBaseJavaModule { + private String TAG = "ThreadManager"; + private HashMap threads; - private final ReactApplicationContext reactContext; + private ReactNativeHost reactNativeHost; - public RNThreadModule(ReactApplicationContext reactContext) { + private ReactPackage additionalThreadPackages[]; + + public RNThreadModule(final ReactApplicationContext reactContext, ReactNativeHost reactNativehost, ReactPackage additionalThreadPackages[]) { super(reactContext); - this.reactContext = reactContext; + threads = new HashMap<>(); + this.reactNativeHost = reactNativehost; + this.additionalThreadPackages = additionalThreadPackages; + reactContext.addLifecycleEventListener(this); } @Override public String getName() { - return "RNThread"; + return "ThreadManager"; + } + + @ReactMethod + public void startThread(final String jsFileName, final Promise promise) { + Log.d(TAG, "Starting web thread - " + jsFileName); + + String jsFileSlug = jsFileName.contains("/") ? jsFileName.replaceAll("/", "_") : jsFileName; + + JSBundleLoader bundleLoader = getDevSupportManager().getDevSupportEnabled() + ? createDevBundleLoader(jsFileName, jsFileSlug) + : createReleaseBundleLoader(jsFileName, jsFileSlug); + + try { + ArrayList threadPackages = new ArrayList(Arrays.asList(additionalThreadPackages)); + threadPackages.add(0, new BaseReactPackage(getReactInstanceManager())); + + ReactContextBuilder threadContextBuilder = new ReactContextBuilder(getReactApplicationContext()) + .setJSBundleLoader(bundleLoader) + .setDevSupportManager(getDevSupportManager()) + .setReactInstanceManager(getReactInstanceManager()) + .setReactPackages(threadPackages); + + JSThread thread = new JSThread(jsFileSlug); + thread.runFromContext( + getReactApplicationContext(), + threadContextBuilder + ); + threads.put(thread.getThreadId(), thread); + promise.resolve(thread.getThreadId()); + } catch (Exception e) { + promise.reject(e); + getDevSupportManager().handleException(e); + } + } + + @ReactMethod + public void stopThread(final int threadId) { + final JSThread thread = threads.get(threadId); + if (thread == null) { + Log.d(TAG, "Cannot stop thread - thread is null for id " + threadId); + return; + } + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + thread.terminate(); + threads.remove(threadId); + } + }); + } + + @ReactMethod + public void postThreadMessage(int threadId, String message) { + JSThread thread = threads.get(threadId); + if (thread == null) { + Log.d(TAG, "Cannot post message to thread - thread is null for id " + threadId); + return; + } + + thread.postMessage(message); + } + + @Override + public void onHostResume() { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + for (int threadId : threads.keySet()) { + threads.get(threadId).onHostResume(); + } + } + }); + } + + @Override + public void onHostPause() { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + for (int threadId : threads.keySet()) { + threads.get(threadId).onHostPause(); + } + } + }); + } + + @Override + public void onHostDestroy() { + Log.d(TAG, "onHostDestroy - Clean JS Threads"); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + for (int threadId : threads.keySet()) { + threads.get(threadId).terminate(); + } + } + }); + } + + @Override + public void onCatalystInstanceDestroy() { + super.onCatalystInstanceDestroy(); + onHostDestroy(); + } + + /* + * Helper methods + */ + + private JSBundleLoader createDevBundleLoader(String jsFileName, String jsFileSlug) { + String bundleUrl = bundleUrlForFile(jsFileName); + String bundleOut = getReactApplicationContext().getFilesDir().getAbsolutePath() + "/" + jsFileSlug; + + Log.d(TAG, "createDevBundleLoader - download web thread to - " + bundleOut); + downloadScriptToFileSync(bundleUrl, bundleOut); + + return JSBundleLoader.createCachedBundleFromNetworkLoader(bundleUrl, bundleOut); + } + + private JSBundleLoader createReleaseBundleLoader(String jsFileName, String jsFileSlug) { + Log.d(TAG, "createReleaseBundleLoader - reading file from assets"); + return JSBundleLoader.createFileLoader("assets://threads/" + jsFileSlug + ".bundle"); + } + + private ReactInstanceManager getReactInstanceManager() { + return reactNativeHost.getReactInstanceManager(); + } + + private DevSupportManager getDevSupportManager() { + return getReactInstanceManager().getDevSupportManager(); + } + + private String bundleUrlForFile(final String fileName) { + // http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=false&minify=false + String sourceUrl = getDevSupportManager().getSourceUrl().replace("http://", ""); + return "http://" + + sourceUrl.split("/")[0] + + "/" + + fileName + + ".bundle?platform=android&dev=true&hot=false&minify=false"; + } + + private void downloadScriptToFileSync(String bundleUrl, String bundleOut) { + OkHttpClient client = new OkHttpClient(); + final File out = new File(bundleOut); + + Request request = new Request.Builder() + .url(bundleUrl) + .build(); + + try { + Response response = client.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new RuntimeException("Error downloading thread script - " + response.toString()); + } + + Sink output = Okio.sink(out); + Okio.buffer(response.body().source()).readAll(output); + } catch (IOException e) { + throw new RuntimeException("Exception downloading thread script to file", e); + } } } \ No newline at end of file diff --git a/android/src/main/java/com/reactlibrary/RNThreadPackage.java b/android/src/main/java/com/reactlibrary/RNThreadPackage.java index b1b4ed231f2f0bc5d43345cb209612857b43d773..7af7d004414f00bcf07f2bb90ca295ffcbec58ca 100644 --- a/android/src/main/java/com/reactlibrary/RNThreadPackage.java +++ b/android/src/main/java/com/reactlibrary/RNThreadPackage.java @@ -1,23 +1,34 @@ - package com.reactlibrary; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - import com.facebook.react.ReactPackage; +import com.facebook.react.ReactNativeHost; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + public class RNThreadPackage implements ReactPackage { - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(new RNThreadModule(reactContext)); + + private ReactNativeHost reactNativeHost; + private ReactPackage additionalThreadPackages[]; + + public RNThreadPackage(ReactNativeHost reactNativeHost, ReactPackage... additionalThreadPackages) { + this.reactNativeHost = reactNativeHost; + this.additionalThreadPackages = additionalThreadPackages; } @Override public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); + return Collections.emptyList(); + } + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Arrays.asList( + new RNThreadModule(reactContext, reactNativeHost, additionalThreadPackages) + ); } -} +} \ No newline at end of file diff --git a/android/src/main/java/com/reactlibrary/ReactContextBuilder.java b/android/src/main/java/com/reactlibrary/ReactContextBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..c325cbabf5ff71f72d5bef258e39ad45191cbbdc --- /dev/null +++ b/android/src/main/java/com/reactlibrary/ReactContextBuilder.java @@ -0,0 +1,136 @@ +package com.reactlibrary; + +import android.content.Context; + +import com.facebook.react.NativeModuleRegistryBuilder; +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.CatalystInstanceImpl; +import com.facebook.react.bridge.JSBundleLoader; +import com.facebook.react.bridge.JSCJavaScriptExecutor; +import com.facebook.react.bridge.JavaScriptExecutor; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.NativeModuleCallExceptionHandler; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec; +import com.facebook.react.devsupport.interfaces.DevSupportManager; +import com.facebook.soloader.SoLoader; + +import java.util.ArrayList; +import java.util.concurrent.Callable; + +public class ReactContextBuilder { + + private Context parentContext; + private JSBundleLoader jsBundleLoader; + private DevSupportManager devSupportManager; + private ReactInstanceManager instanceManager; + private ArrayList reactPackages; + + public ReactContextBuilder(Context context) { + this.parentContext = context; + SoLoader.init(context, /* native exopackage */ false); + } + + public ReactContextBuilder setJSBundleLoader(JSBundleLoader jsBundleLoader) { + this.jsBundleLoader = jsBundleLoader; + return this; + } + + public ReactContextBuilder setDevSupportManager(DevSupportManager devSupportManager) { + this.devSupportManager = devSupportManager; + return this; + } + + public ReactContextBuilder setReactInstanceManager(ReactInstanceManager manager) { + this.instanceManager = manager; + return this; + } + + public ReactContextBuilder setReactPackages(ArrayList reactPackages) { + this.reactPackages = reactPackages; + return this; + } + + public ReactApplicationContext build() throws Exception { + JavaScriptExecutor jsExecutor = new JSCJavaScriptExecutor.Factory(new WritableNativeMap()).create(); + + // fresh new react context + final ReactApplicationContext reactContext = new ReactApplicationContext(parentContext); + if (devSupportManager != null) { + reactContext.setNativeModuleCallExceptionHandler(devSupportManager); + } + + // load native modules + NativeModuleRegistryBuilder nativeRegistryBuilder = new NativeModuleRegistryBuilder(reactContext, this.instanceManager, false); + addNativeModules(reactContext, nativeRegistryBuilder); + + CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() + .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault()) + .setJSExecutor(jsExecutor) + .setRegistry(nativeRegistryBuilder.build()) + .setJSBundleLoader(jsBundleLoader) + .setNativeModuleCallExceptionHandler(devSupportManager != null + ? devSupportManager + : createNativeModuleExceptionHandler() + ); + + + final CatalystInstance catalystInstance; + catalystInstance = catalystInstanceBuilder.build(); + + catalystInstance.getReactQueueConfiguration().getJSQueueThread().callOnQueue( + new Callable() { + @Override + public Object call() throws Exception { + try { + reactContext.initializeWithInstance(catalystInstance); + catalystInstance.runJSBundle(); + } catch (Exception e) { + e.printStackTrace(); + devSupportManager.handleException(e); + } + + return null; + } + } + ).get(); + + catalystInstance.getReactQueueConfiguration().getUIQueueThread().callOnQueue(new Callable() { + @Override + public Object call() throws Exception { + try { + catalystInstance.initialize(); + reactContext.onHostResume(null); + } catch (Exception e) { + e.printStackTrace(); + devSupportManager.handleException(e); + } + + return null; + } + }).get(); + + return reactContext; + } + + private NativeModuleCallExceptionHandler createNativeModuleExceptionHandler() { + return new NativeModuleCallExceptionHandler() { + @Override + public void handleException(Exception e) { + throw new RuntimeException(e); + } + }; + } + + private void addNativeModules(ReactApplicationContext reactContext, NativeModuleRegistryBuilder nativeRegistryBuilder) { + for (int i = 0; i < reactPackages.size(); i++) { + ReactPackage reactPackage = reactPackages.get(i); + for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) { + nativeRegistryBuilder.addNativeModule(nativeModule); + } + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/reactlibrary/ThreadSelfModule.java b/android/src/main/java/com/reactlibrary/ThreadSelfModule.java new file mode 100644 index 0000000000000000000000000000000000000000..54e8798bf997565e246c8b7fc6c07ea1f2734fa8 --- /dev/null +++ b/android/src/main/java/com/reactlibrary/ThreadSelfModule.java @@ -0,0 +1,34 @@ +package com.reactlibrary; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.modules.core.DeviceEventManagerModule; + +public class ThreadSelfModule extends ReactContextBaseJavaModule { + + private int threadId; + private ReactApplicationContext parentContext; + + public ThreadSelfModule(ReactApplicationContext context) { + super(context); + } + + public void initialize(int threadId, ReactApplicationContext parentContext) { + this.parentContext = parentContext; + this.threadId = threadId; + } + + @Override + public String getName() { + return "ThreadSelfManager"; + } + + @ReactMethod + public void postMessage(String data) { + if (parentContext == null) { return; } + + parentContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("Thread" + String.valueOf(threadId), data); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/reactlibrary/UIManagerStubModule.java b/android/src/main/java/com/reactlibrary/UIManagerStubModule.java new file mode 100644 index 0000000000000000000000000000000000000000..570aa747a05192646a678275a2c6015cca07d8f2 --- /dev/null +++ b/android/src/main/java/com/reactlibrary/UIManagerStubModule.java @@ -0,0 +1,16 @@ +package com.reactlibrary; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; + +public class UIManagerStubModule extends ReactContextBaseJavaModule { + + public UIManagerStubModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public String getName() { + return "UIManager"; + } +} \ No newline at end of file diff --git a/example/ThreadExample/android/app/build.gradle b/example/ThreadExample/android/app/build.gradle index d034bd9d3e96c6736139674c3534c04962f501b9..e9e15ed7b620e56dfbac24b0fd063c016e7e1eea 100644 --- a/example/ThreadExample/android/app/build.gradle +++ b/example/ThreadExample/android/app/build.gradle @@ -133,6 +133,7 @@ android { } dependencies { + compile project(':react-native-thread') compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" compile "com.facebook.react:react-native:+" // From node_modules diff --git a/example/ThreadExample/android/app/src/main/java/com/threadexample/MainApplication.java b/example/ThreadExample/android/app/src/main/java/com/threadexample/MainApplication.java index 079be7b60c5401cd15e6f90629ba834da0097611..d9c2873f8721d1601bc036f5b005912577079e99 100644 --- a/example/ThreadExample/android/app/src/main/java/com/threadexample/MainApplication.java +++ b/example/ThreadExample/android/app/src/main/java/com/threadexample/MainApplication.java @@ -3,6 +3,7 @@ package com.threadexample; import android.app.Application; import com.facebook.react.ReactApplication; +import com.reactlibrary.RNThreadPackage; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; @@ -22,7 +23,8 @@ public class MainApplication extends Application implements ReactApplication { @Override protected List getPackages() { return Arrays.asList( - new MainReactPackage() + new MainReactPackage(), + new RNThreadPackage(mReactNativeHost) ); } }; diff --git a/example/ThreadExample/android/settings.gradle b/example/ThreadExample/android/settings.gradle index 2fa0740c1e7361b73877e560534ea5ac422f9f22..6c1c0b6cc8cad1cbea935ff8c9d6bcf49acf1673 100644 --- a/example/ThreadExample/android/settings.gradle +++ b/example/ThreadExample/android/settings.gradle @@ -1,3 +1,5 @@ rootProject.name = 'ThreadExample' +include ':react-native-thread' +project(':react-native-thread').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-thread/android') include ':app' diff --git a/example/ThreadExample/index.android.js b/example/ThreadExample/index.android.js index 71c6b35e7a74626634ad437b46bc068e0265d02a..48990e1951eb5405b0593fea09021f59cf5becfb 100644 --- a/example/ThreadExample/index.android.js +++ b/example/ThreadExample/index.android.js @@ -1,31 +1,35 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - * @flow - */ - import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, - View + View, + TouchableOpacity, } from 'react-native'; -export default class ThreadExample extends Component { +import { Thread } from 'react-native-thread'; + +class ThreadExample extends Component { + componentDidMount() { + this.worker= new Thread('worker.js'); + + this.worker.onmessage = (message) => { + console.log("Got message from worker", message); + } + } + render() { + return ( - - Welcome to React Native! - - - To get started, edit index.android.js - - - Double tap R on your keyboard to reload,{'\n'} - Shake or press menu button for dev menu - + { + console.log('SENDING MESSAGE TO WORKER'); + this.worker.postMessage("Hello from main thread"); + }}> + + Send message + + ); } @@ -51,3 +55,4 @@ const styles = StyleSheet.create({ }); AppRegistry.registerComponent('ThreadExample', () => ThreadExample); + diff --git a/package.json b/package.json index 6c940f54e07ebcb6afbeaa3401daa3357f87a0cb..6028d2547878631964257f40893a259d06ca839f 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,3 @@ - { "name": "react-native-thread", "version": "1.0.0", @@ -13,12 +12,13 @@ "author": "", "license": "", "files": [ + "android/", "ios/", "js/", "index.js" ], "peerDependencies": { - "react-native": "^0.41.2", + "react-native": ">=0.41.2", "react-native-windows": "0.41.0-rc.1" } }