Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(android): Added Ti.App "close" event support #10637

Merged
merged 8 commits into from
Sep 10, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;

import org.appcelerator.kroll.KrollExceptionHandler.ExceptionMessage;
import org.appcelerator.kroll.common.Log;
Expand Down Expand Up @@ -54,13 +53,13 @@ public interface OnDisposingListener {

private WeakReference<KrollApplication> krollApplication;
private long threadId;
private CountDownLatch initLatch = new CountDownLatch(1);
private KrollEvaluator evaluator;
private KrollExceptionHandler primaryExceptionHandler;
private HashMap<String, KrollExceptionHandler> exceptionHandlers;

public enum State { INITIALIZED, RELEASED, RELAUNCHED, DISPOSED }
public enum State { INITIALIZED, RELEASED, DISPOSED }
private static State runtimeState = State.DISPOSED;
private static boolean isDisposing;

protected Handler handler;

Expand Down Expand Up @@ -161,44 +160,49 @@ public long getThreadId()
return threadId;
}

protected void doInit()
private void doInit()
{
// initializer for the specific runtime implementation (V8)
initRuntime();
// Do not continue if already initialized.
if (runtimeState == State.INITIALIZED) {
return;
}

// Notify the main thread that the runtime has been initialized
synchronized (runtimeState)
{
runtimeState = State.INITIALIZED;
// Make sure this method is being called on the runtime's thread.
if (!isRuntimeThread()) {
instance.handler.sendEmptyMessage(MSG_INIT);
return;
}
initLatch.countDown();

// If currently scheduled to be disposed, then do so now.
if (runtimeState == State.RELEASED) {
internalDispose();
if (runtimeState != State.DISPOSED) {
instance.handler.sendEmptyMessage(MSG_INIT);
return;
}
}

// Initialize the JavaScript runtime.
initRuntime();
runtimeState = State.INITIALIZED;
}

public void dispose()
{

Log.d(TAG, "Disposing runtime.", Log.DEBUG_MODE);

// Set state to released when since we have not fully disposed of it yet
// Flag the runtime as "released" to indicate that it's about to be disposed.
synchronized (runtimeState)
{
if (runtimeState == State.DISPOSED) {
return;
switch (runtimeState) {
case RELEASED:
case DISPOSED:
return;
}
runtimeState = State.RELEASED;
}

// Cancel all timers associated with the app
KrollApplication app = krollApplication.get();
if (app != null) {
app.cancelTimers();
}

if (isRuntimeThread()) {
internalDispose();
} else {
handler.sendEmptyMessage(MSG_DISPOSE);
}
// Dispose the runtime on the thread it was created on.
Log.d(TAG, "Disposing runtime.", Log.DEBUG_MODE);
internalDispose();
}

public void runModule(String source, String filename, KrollProxySupport activityProxy)
Expand Down Expand Up @@ -298,41 +302,6 @@ public boolean handleMessage(Message msg)
return false;
}

private static void waitForInit()
{
try {
instance.initLatch.await();
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for runtime to initialize", e);
}
}

private static void syncInit()
{
waitForInit();

// When the process is re-entered, it is either in the RELEASED or DISPOSED state. If it is in the RELEASED
// state, that means we have not disposed of the runtime from the previous launch. In that case, we set the
// state to RELAUNCHED. If we are in the DISPOSED state, we need to re-initialize the runtime here.
synchronized (runtimeState)
{
if (runtimeState == State.DISPOSED) {
instance.initLatch = new CountDownLatch(1);

if (instance.isRuntimeThread()) {
instance.doInit();
} else {
instance.handler.sendEmptyMessage(MSG_INIT);
}

} else if (runtimeState == State.RELEASED) {
runtimeState = State.RELAUNCHED;
}
}

waitForInit();
}

/**
* This method is expected to be called for every Titanium activity that has been created.
* This will startup the JavaScript runtime when the activity count is 1 (the first activity created).
Expand All @@ -341,14 +310,12 @@ private static void syncInit()
*/
public static void incrementActivityRefCount()
{
waitForInit();

// Increment the activity count.
activityRefCount++;

// If this is the 1st activity, then start up the runtime, if not done already.
if ((activityRefCount == 1) && (instance != null)) {
syncInit();
instance.doInit();
}
}

Expand Down Expand Up @@ -400,26 +367,34 @@ public static int getServiceReceiverRefCount()

private void internalDispose()
{
// Make sure this method is called on the runtime's thread.
if (!isRuntimeThread()) {
handler.sendEmptyMessage(MSG_DISPOSE);
return;
}

// Do not continue if already disposed/disposing.
synchronized (runtimeState)
{
if (runtimeState == State.DISPOSED) {
if (runtimeState != State.RELEASED) {
return;
}
if (runtimeState == State.RELAUNCHED) {
// Abort the dispose if the application has been re-launched since we scheduled this dispose during the
// last exit. Then set it back to the initialized state.
runtimeState = State.INITIALIZED;
if (isDisposing) {
return;
}

runtimeState = State.DISPOSED;
isDisposing = true;
}

// Invoke all OnDisposingListener objects before disposing this runtime.
onDisposing(this);

// Dispose/terminate the runtime.
doDispose();
synchronized (runtimeState)
{
runtimeState = State.DISPOSED;
isDisposing = false;
}

// Request the application to dispose its native resources.
KrollApplication app = krollApplication.get();
Expand Down Expand Up @@ -560,7 +535,11 @@ private static void onDisposing(KrollRuntime runtime)
for (OnDisposingListener listener : clonedListeners) {
if (KrollRuntime.disposingListeners.contains(listener)) {
// Previous listener did not remove this listener. Invoke it.
listener.onDisposing(runtime);
try {
listener.onDisposing(runtime);
} catch (Exception ex) {
Log.e(TAG, "OnDisposingListener threw an exception.", ex);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public Object call(KrollObject krollObject, Object[] args)

public Object callSync(KrollObject krollObject, Object[] args)
{
if (!KrollRuntime.isInitialized()) {
if (KrollRuntime.isDisposed()) {
Log.w(TAG, "Runtime disposed, cannot call function.");
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public Object getNativeObject()
@Override
public void setProperty(String name, Object value)
{
if (!KrollRuntime.isInitialized()) {
if (KrollRuntime.isDisposed()) {
Log.w(TAG, "Runtime disposed, cannot set property '" + name + "'");
return;
}
Expand All @@ -51,7 +51,7 @@ public void setProperty(String name, Object value)
public boolean fireEvent(KrollObject source, String type, Object data, boolean bubbles, boolean reportSuccess,
int code, String message)
{
if (!KrollRuntime.isInitialized()) {
if (KrollRuntime.isDisposed()) {
Log.w(TAG, "Runtime disposed, cannot fire event '" + type + "'");
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,23 @@ public void uncaughtException(Thread t, Throwable e)
deployData = new TiDeployData(this);

registerActivityLifecycleCallbacks(new TiApplicationLifecycle());

// Set up a listener to be invoked just before Titanium's JavaScript runtime gets terminated.
// Note: Runtime will be terminated once all Titanium activities have been destroyed.
KrollRuntime.addOnDisposingListener(new KrollRuntime.OnDisposingListener() {
@Override
public void onDisposing(KrollRuntime runtime)
{
// Fire a Ti.App "close" event. Must be fired synchronously before termination.
KrollModule appModule = getModuleByName("App");
if (appModule != null) {
appModule.fireSyncEvent(TiC.EVENT_CLOSE, null);
}

// Cancel all Titanium timers.
cancelTimers();
}
});
}

@Override
Expand Down
4 changes: 2 additions & 2 deletions apidoc/Titanium/App/App.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ events:
- name: close
summary: Fired by the system when the application is about to be terminated.
description: It is the last event that can be received before the application gets closed.
platforms: [iphone, ipad]
since: "1.5.0"
platforms: [android, iphone, ipad]
since: {android: "8.1.0", iphone: "1.5.0", ipad: "1.5.0"}
garymathews marked this conversation as resolved.
Show resolved Hide resolved

- name: memorywarning
summary: Fired when the app receives a warning from the operating system about low memory availability.
Expand Down