diff --git a/test-app/app/src/main/java/com/tns/NativeScriptRuntime.java b/test-app/app/src/main/java/com/tns/NativeScriptRuntime.java new file mode 100644 index 000000000..e8eefefe2 --- /dev/null +++ b/test-app/app/src/main/java/com/tns/NativeScriptRuntime.java @@ -0,0 +1,14 @@ +package com.tns; + +public final class NativeScriptRuntime { + private NativeScriptRuntime() { + } + + public static boolean reloadApplication() { + return RuntimeHelper.reloadApplication(); + } + + public static boolean reloadApplication(String baseDir) { + return RuntimeHelper.reloadApplication(); + } +} diff --git a/test-app/app/src/main/java/com/tns/RuntimeHelper.java b/test-app/app/src/main/java/com/tns/RuntimeHelper.java index fa1542966..c6ead3f6b 100644 --- a/test-app/app/src/main/java/com/tns/RuntimeHelper.java +++ b/test-app/app/src/main/java/com/tns/RuntimeHelper.java @@ -8,6 +8,8 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.preference.PreferenceManager; import android.util.Log; @@ -24,6 +26,9 @@ private RuntimeHelper() { } private static AndroidJsV8Inspector v8Inspector; + private static Context applicationContext; + private static BroadcastReceiver timezoneChangedReceiver; + private static boolean reloadScheduled; // hasErrorIntent tells you if there was an event (with an uncaught // exception) raised from ErrorReport @@ -59,6 +64,9 @@ private static boolean hasErrorIntent(Context context) { } public static Runtime initRuntime(Context context) { + Context appContext = context.getApplicationContext(); + applicationContext = appContext != null ? appContext : context; + if (Runtime.isInitialized()) { return Runtime.getCurrentRuntime(); } @@ -237,6 +245,40 @@ public static Runtime initRuntime(Context context) { } } + public static synchronized boolean reloadApplication() { + final Context context = applicationContext; + if (context == null || reloadScheduled) { + return false; + } + + reloadScheduled = true; + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + try { + Runtime.destroyMainRuntime(); + + Runtime runtime = initRuntime(context); + if (runtime == null) { + throw new IllegalStateException("NativeScript runtime reload failed to initialize a new runtime."); + } + + runtime.run(); + } catch (Throwable e) { + Log.e(logTag, "NativeScript runtime reload failed.", e); + throw new RuntimeException("NativeScript runtime reload failed.", e); + } finally { + synchronized (RuntimeHelper.class) { + reloadScheduled = false; + } + } + } + }); + + return true; + } + private static void waitForLiveSync(Context context) { boolean needToWait = false; @@ -295,7 +337,16 @@ public void onReceive(Context context, Intent intent) { } }; - context.registerReceiver(timezoneReceiver, timezoneFilter); + if (timezoneChangedReceiver != null) { + try { + context.unregisterReceiver(timezoneChangedReceiver); + } catch (IllegalArgumentException e) { + // Already unregistered. + } + } + + timezoneChangedReceiver = timezoneReceiver; + context.registerReceiver(timezoneChangedReceiver, timezoneFilter); } public static void initLiveSync(Application app) { diff --git a/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp b/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp index a05d869fb..515d3d121 100644 --- a/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp +++ b/test-app/runtime/src/main/cpp/com_tns_Runtime.cpp @@ -421,6 +421,7 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_TerminateWorkerCallback(JNIEnv* e auto runtime = TryGetRuntime(runtimeId); if (runtime == nullptr) { // TODO: Pete: Log message informing the developer of the failure + return; } auto isolate = runtime->GetIsolate(); @@ -440,6 +441,28 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_TerminateWorkerCallback(JNIEnv* e delete runtime; } +extern "C" JNIEXPORT void Java_com_tns_Runtime_TerminateRuntimeCallback(JNIEnv* env, jobject obj, jint runtimeId) { + auto runtime = TryGetRuntime(runtimeId); + if (runtime == nullptr) { + // TODO: Pete: Log message informing the developer of the failure + return; + } + + auto isolate = runtime->GetIsolate(); + + { + v8::Locker locker(isolate); + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handleScope(isolate); + + runtime->DestroyRuntime(); + } + + isolate->Dispose(); + + delete runtime; +} + extern "C" JNIEXPORT void Java_com_tns_Runtime_ClearWorkerPersistent(JNIEnv* env, jobject obj, jint runtimeId, jint workerId) { // Worker Thread runtime auto runtime = TryGetRuntime(runtimeId); @@ -487,4 +510,4 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_ResetDateTimeConfigurationCache(J auto isolate = runtime->GetIsolate(); isolate->DateTimeConfigurationChangeNotification(Isolate::TimeZoneDetection::kRedetect); -} \ No newline at end of file +} diff --git a/test-app/runtime/src/main/java/com/tns/Runtime.java b/test-app/runtime/src/main/java/com/tns/Runtime.java index b610ddb0c..c315dcb7b 100644 --- a/test-app/runtime/src/main/java/com/tns/Runtime.java +++ b/test-app/runtime/src/main/java/com/tns/Runtime.java @@ -118,6 +118,8 @@ public static void SetManualInstrumentationMode(String mode) { private static native void TerminateWorkerCallback(int runtimeId); + private static native void TerminateRuntimeCallback(int runtimeId); + private static native void ClearWorkerPersistent(int runtimeId, int workerId); private static native void CallWorkerObjectOnErrorHandleMain(int runtimeId, int workerId, String message, String stackTrace, String filename, int lineno, String threadName) throws NativeScriptException; @@ -486,6 +488,40 @@ public static boolean isInitialized() { return (runtime != null) ? runtime.isInitializedImpl() : false; } + static void destroyMainRuntime() { + Runtime runtime = Runtime.getCurrentRuntime(); + if (runtime == null) { + return; + } + + if (runtime.workerId != 0) { + throw new NativeScriptException("Only the main NativeScript runtime can be destroyed with destroyMainRuntime()."); + } + + runtime.isTerminating = true; + runtime.terminateWorkers(); + GcListener.unsubscribe(runtime); + runtimeCache.remove(runtime.runtimeId); + pendingWorkerMessages.clear(); + currentRuntime.remove(); + + TerminateRuntimeCallback(runtime.runtimeId); + } + + private void terminateWorkers() { + for (Handler workerHandler : new ArrayList<>(workerIdToHandler.values())) { + if (workerHandler == null) { + continue; + } + + Message msg = Message.obtain(); + msg.arg1 = MessageType.TerminateThread; + workerHandler.sendMessageAtFrontOfQueue(msg); + } + + workerIdToHandler.clear(); + } + public int getWorkerId() { return workerId; }