feat: reloadApplication for JS bundle restart without restarting app process by NathanWalker · Pull Request #1963 · NativeScript/android · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions test-app/app/src/main/java/com/tns/NativeScriptRuntime.java
64 changes: 63 additions & 1 deletion test-app/app/src/main/java/com/tns/RuntimeHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -237,6 +245,45 @@ public static Runtime initRuntime(Context context) {
}
}

// The overload is kept for API parity with ios, but not needed with android.
public static synchronized boolean reloadApplication(String baseDir) {
return reloadApplication();
}

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;

Expand Down Expand Up @@ -265,6 +312,12 @@ private static void waitForLiveSync(Context context) {
}

private static void registerTimezoneChangedListener(Context context, final Runtime runtime) {
// Register/unregister against the application context so the same Context
// instance is used across initial launch and reload. Using the passed-in
// context (which may be an Activity on first launch but applicationContext
// on reload) would make unregisterReceiver fail and leak the old receiver.
final Context receiverContext = applicationContext != null ? applicationContext : context;

IntentFilter timezoneFilter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);

BroadcastReceiver timezoneReceiver = new BroadcastReceiver() {
Expand Down Expand Up @@ -295,7 +348,16 @@ public void onReceive(Context context, Intent intent) {
}
};

context.registerReceiver(timezoneReceiver, timezoneFilter);
if (timezoneChangedReceiver != null) {
try {
receiverContext.unregisterReceiver(timezoneChangedReceiver);
} catch (IllegalArgumentException e) {
// Already unregistered.
}
}

timezoneChangedReceiver = timezoneReceiver;
receiverContext.registerReceiver(timezoneChangedReceiver, timezoneFilter);
}

public static void initLiveSync(Application app) {
Expand Down
30 changes: 29 additions & 1 deletion test-app/runtime/src/main/cpp/com_tns_Runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "Runtime.h"
#include "NativeScriptException.h"
#include "CallbackHandlers.h"
#include "WorkerWrapper.h"
#include <sstream>
#include <android/log.h>

Expand Down Expand Up @@ -352,6 +353,33 @@ extern "C" JNIEXPORT jint Java_com_tns_Runtime_getCurrentRuntimeIdLegacy(JNIEnv*
return getCurrentRuntimeIdCritical_impl();
}

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();

// Terminate this runtime's child workers before disposing the isolate. Their
// Worker object persistents live in this isolate, so they must be released
// first - mirrors WorkerWrapper::BackgroundLooper's nested-worker teardown.
WorkerWrapper::TerminateChildren(isolate);

{
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_ResetDateTimeConfigurationCache(JNIEnv* _env, jobject obj, jint runtimeId) {
auto runtime = TryGetRuntime(runtimeId);
if (runtime == nullptr) {
Expand All @@ -360,4 +388,4 @@ extern "C" JNIEXPORT void Java_com_tns_Runtime_ResetDateTimeConfigurationCache(J

auto isolate = runtime->GetIsolate();
isolate->DateTimeConfigurationChangeNotification(Isolate::TimeZoneDetection::kRedetect);
}
}
22 changes: 22 additions & 0 deletions test-app/runtime/src/main/java/com/tns/Runtime.java