#Ti.AppLifecycle
##foreGround/backGround An android app is a container of activities and other components. These activities are the visible screens, with other words the UI for the user. They lifes in lifecycles.
If we have a reference to an activity then we all events are available, in Titanium too.
###Problem In some cases we need an info about state on app level. The only way is a dirty way to inspect which activity in foreground. If the activity is changed then we can fire an event.
###Idea
In module we start a cronjob. The cronjob looks in process table and detects if the toppest task is changed. The cronJob.scheduleAtFixedRate
method is related to setInterval() of javascript. The code inside the brackets is related to to function in javascript. We start the code at onAppCreate
method. And we don't forget to cancel the cronjob at onAppDestroy
method.
cronJob.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
boolean isInFront = false;
TaskTestResult result = isInForeground();
isInFront = result.getIsForground();
if (isInFront != wasInForeGround
|| lastPackageName != result.getPackageName()) {
Log.d(LCAT,
"new top package name: >>>>>>>>> "
+ result.getPackageName());
String key = (isInFront == true) ? "resumed" : "paused";
if (FIRE_EVENT_ENABLED == true) {
KrollDict dict = new KrollDict();
dict.put("packageName", result.getPackageName());
mApp.fireAppEvent(key, dict);
}
Log.d(LCAT, key);
wasInForeGround = isInFront;
}
}
}, 0, testInterval);
The method isInForeground
make a new instance of a class ForegroundCheckTask
.
static public TaskTestResult isInForeground() {
try {
TaskTestResult result = new ForegroundCheckTask().execute(
TiApplication.getInstance().getApplicationContext()).get();
return result;
} catch (InterruptedException e) {
e.printStackTrace();
return null;
} catch (ExecutionException e) {
e.printStackTrace();
return null;
}
}
###Test interval The interval of cronjob is critical: if to short, the bad for CPU and if too long then
- delay of event
- we cannot fire event because reference is null In tiapp.xml we can use a property for setting:
<property name="LIFECYCLE_TESTINTERVAL" type="int">200</property>
For read this properties we need a reference to it:
private static TiProperties appProperties = TiApplication.getInstance().getAppProperties();
and we can read it by
private static int testInterval = appProperties.getInt("LIFECYCLE_TESTINTERVAL", 1000);
The class needs two result values:
- a boolean for info if foreground or not
- a string with top packageName
In PHP or SWIFT we can use tuples for results of functions – in Java we need a custome class:
public final class TaskTestResult {
private final boolean isForeground;
private final String packageName;
public TaskTestResult(boolean isForeground, String packageName) {
this.isForeground = isForeground;
this.packageName = packageName;
}
public boolean getIsForeground() {
return isForeground;
}
public String getPackageName() {
return packageName;
}
}
With other words: we build a variable with special struct and we define getters. The setting happens in constructor (in our case).
Now we can inspect the core:
class ForegroundCheckTask extends AsyncTask<Context, Void, TaskTestResult> {
@Override
protected TaskTestResult doInBackground(Context... params) {
final Context context = params[0];
return isAppOnForeground(context);
}
@SuppressWarnings("deprecation")
private TaskTestResult isAppOnForeground(Context context) {
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
/* retreiving packagename of toppest task */
String foregroundTaskPackageName = "";
if (Build.VERSION.SDK_INT > 20) {
foregroundTaskPackageName = am.getRunningAppProcesses().get(0).processName;
} else {
foregroundTaskPackageName = am.getRunningTasks(1).get(0).topActivity
.getPackageName();
}
PackageManager pm = context.getPackageManager();
PackageInfo foregroundAppPackageInfo = null;
try {
foregroundAppPackageInfo = pm.getPackageInfo(
foregroundTaskPackageName, 0);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
String foregroundTaskAppId = foregroundAppPackageInfo.applicationInfo.packageName;
return new TaskTestResult(context.getPackageName().equals(
foregroundTaskAppId), foregroundTaskAppId);
}
}
Depending on Android API level (hell) we need two different api methods:
if (Build.VERSION.SDK_INT > 20) {
foregroundTaskPackageName = am.getRunningAppProcesses().get(0).processName;
} else {
foregroundTaskPackageName = am.getRunningTasks(1).get(0).topActivity
.getPackageName();
}
##Screen On/Off
Detecting of screen is simpler because android provide a standard way. Similar to Titaniums global evnt firering aka Ti.App.addEventListener()
we have on operating system level the BroadcastReceivers. These can subscribe to system events. the payload is realized with an intent.
We need an intent with a fitting filter:
final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
Then we can call our custome class wwith this intentFilter:
mReceiver = new ScreenReceiver();
context.registerReceiver(mReceiver, intentFilter);
Here the content of ScreenReceiver
:
package de.appwerft.applifecycle;
public class ScreenReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
de.appwerft.applifecycle.ApplifecycleModule.onScreenChanged(false);
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
de.appwerft.applifecycle.ApplifecycleModule.onScreenChanged(true);
}
}
}
"Our" receiver is a child of 'BroadcastReceiver' and overides only one method 'onReceive'. The method has to parameters:
- the (unused) context
- the intent, the intent contains payload from event
Because we have no scope to app we call two functions in module.
In module we resolve:
public static void onScreenChanged(Boolean screenstate) {
mApp.fireAppEvent((screenstate == true) ? "screenon" : "screenoff", new KrollDict());
}
and send back to Ti.App. In javascript layer we can:
Ti.App.addEventListener('screenon',function(){
Ti.UI.createNotification({message:"Welcome back!"}).show();
})