Skip to content

Commit

Permalink
feat(android): Added Ti.App "close" event support (#10637)
Browse files Browse the repository at this point in the history
* [TIMOB-26542] Android: Added Ti.App "close" event support

- Removed KrollRuntime state RELAUNCHED. No longer relevant.
- Removed KrollRuntime CountDownLatch. No longer needed since Kroll thread is not supported.

* docs(android): bump supported sdk version
  • Loading branch information
jquick-axway authored and garymathews committed Sep 10, 2019
1 parent d1cc406 commit 44a5968
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 79 deletions.
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.3.0", iphone: "1.5.0", ipad: "1.5.0"}

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

0 comments on commit 44a5968

Please sign in to comment.