Jeff Haynie
unread,Jul 22, 2010, 4:06:44 AM7/22/10Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to Appcelerator Titanium
As part of the 1.5 release, we're going to be adding first-class
support for Activities, Intents and Services.
Don and I talked through this (several times now) and here's what I
think we came up with as a strawman.
Feedback welcome. This is a long dump of what we talked about along
with some of my own filler.
Jeff
Android Activities, Intents and Background Services
======================================
The goal is to provide Android-specific native APIs that are first-
class citizens in Titanium.
New APIs
Ti.Android.createActivity() - returns Ti.Android.ActivityProxy
Ti.Android.createIntent() - returns Ti.Android.IntentProxy
Activity
Activity Proxy would be a wrapper around the android.app.Activity
class.
Underneath the cover, our activity would actually automatically map to
the heavy weight window.
Intent
Intent Proxy would be a wrapper around the android.os.Intent class.
Intent would define constants such as ACTION_DIAL. Constants should
be defined in the Ti.Android namespace.
Special properties in all JS contexts:
Ti.Android.currentActivity - the current Activity instance
Ti.Android.currentIntent - the current Intent instance
Ti.Android.currentBundle - the current Bundle instance passed in
onCreate
Use case #1 : external app invokes Appcelerator registered Intent
------------------------------------------------------------------------------------------------
First, we'd define our intent in our AndroidManifest.xml. Ideally,
we'd abstract this into tiapp.xml.
For example:
<intents>
<intent src="edit_activity.js">
<action name="android.intent.action.VIEW" />
<action name="android.intent.action.EDIT" />
<action name="android.intent.action.PICK" />
<category name="android.intent.category.DEFAULT" />
<data mimeType="vnd.android.cursor.dir/vnd.google.note" />
</intent>
</intents>
This would then be generated into:
<activity class=".EditActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
</intent-filter>
</activity>
Where EditActivity would be a generated Activity that would create a
JS Context and start edit_activity.js.
edit_activity.js would simply be like app.js except it would be
started once an intent matched.
To exit the activity, you'd need to call the following (explicitly):
Ti.Android.currentActivity.finish();
To set a result, it would be modeled after android:
Ti.Android.currentActivity.setResult(resultCode);
or with another intent:
Ti.Android.currentActivity.setResult(resultCode,myIntent);
To retrieve data from the incoming Intent:
// this would return "vnd.android.cursor.dir/vnd.google.note" from the
example above.
Ti.Android.currentIntent.getType()
To get an extra property:
var value = Ti.Android.currentIntent.getExtra("foo");
Any non-primitive data in an Intent (parceables) would be marshalled
into TiBlob objects.
Use case #2 : Appcelerator app wants to invoke external Activity with
Intent
---------------------------------------------------------------------------------------------------------------
In this use case, an app.js wants to invoke an external Activity with
an Intent.
// create an action dial intent which should dial 123
var intent = Ti.Android.createIntent({
action: Ti.Android.ACTION_DIAL,
data: "tel:123"
});
var activity = Ti.Android.createActivity();
// listen for activity lifecycle events
activity.addEventListener('start',function(e)
{
Ti.API.debug("our activity was started");
});
activity.addEventListener('destroy',function(e)
{
Ti.API.debug("our activity was destroy");
});
// register to receive the intent's result
activity.addEventListener('result',function(e)
{
// fired when an activity result is received
// properties:
//
// e.requestCode
// e.resultCode
// e.intent
});
// start an activity with our intent
activity.start(intent);
// you can also stop an activity
activity.stop();
Use case #3 : Appcelerator app wants to create internal Activity
---------------------------------------------------------------------------------------------
In this use case, we want to create an app activity - which will
create a new heavy-weight window on the stack.
var activity = Ti.Android.createActivity({url:"my_window.js"});
activity.start();
In this case, we pass in a 'url' property which would tell the
activity that we're creating a URL based activity to another JS
context. This would be similar to createWindow today with the
exception that this would create a true Activity with a true
heavyweight window and correct activity stack.
Use case #4 : Appcelerator app wants to start a Module SDK activity
-----------------------------------------------------------------------------------------------------
In this use case, we want to use the Module SDK to write a Activity in
Java and launch it instead of a JS activity.
To do that, we'd pass the special 'className' property that would
indicate that we need to load an activity from our package.
var activity =
Ti.Android.createActivity({className:"org.appcelerator.MyActivity"});
activity.start(intent);
This would allow developers to write their own Java-based Activities
(and Modules which are simply Activities and shareable) that can be re-
used.
Background Services
================
Background services would be generic between both Apple and Android
since they are similar in 1 of 2 use-cases.
Generic Use Case #1 : Recurring background service
-------------------------------------------------------------------------------
// first, we need to register a background service.
// this can be called as many times as you want to create multiple
services
// create a service proxy which points to the bg.js JS and runs every
// 15s
var serviceProxy = Ti.App.createBackgroundService({
url:"bg.js",
interval:15000
});
serviceProxy.addEventListener('start',function(e)
{
// service has started
});
serviceProxy.addEventListener('resume',function(e)
{
// this event is fired each time the service is resumed
});
serviceProxy.addEventListener('pause',function(e)
{
// this event is fired each time the service has completed and is
paused
});
serviceProxy.addEventListener('stop',function(e)
{
// service has stopped
})
// start the service
serviceProxy.start();
Inside the service JS context, the special property would be set.
Ti.App.currentService
to the serviceProxy. A service could stop itself with the stop
method.
The creator of the service could also call stop to stop the service.
Service Lifecycle
-------------------------
START - state when the service is registered and started (but not
necessary running)
RESUME - state when the service is running
PAUSE - state when the service has completed running
STOP - state when the service is stopped and no longer active
Android
-------------
On Android, this would create a Service and call
Context.startService(Intent).
The service Proxy would internally keep a handle etc so that the
service can be stopped.
iPhone
-----------
On iPhone, this would register for a background task when the
application goes into the background. The start event would be called
once the app is backgrounded, however, the resume method would only be
called once the background task is run.
Android-specific Use Case #2 : start background service
----------------------------------------------------------------------------------
In this use case, we want to start a service in Android. However,
unlike a recurring service, it will run once and until stopped.
var serviceProxy = Ti.Android.createService({
url:"bg.js",
});
The API and functionality will be the exact same as the generic
service with events, etc.
Migration from pre 1.5
=================
So, how do we deal with pre 1.5 for mixing heavy-weight, light-weight
windows.
First, we should generate a generic intent that maps to our app.js:
<intents>
<intent src="app.js">
<action name="android.intent.action.MAIN" />
<category name="android.intent.category.LAUNCHER" />
</intent>
</intents>
This would then allow us to automatically follow our basic
architecture for 1.5 but do it out-of-the-box. In our compiler, if we
have no intents (pre 1.5 created apps), we could simply assume the
same.
Second, we'd continue to treat windows created with createWindow as
light-weight. This would break any apps that are using our Android
hack for mixing windows/activities.
2 options:
1. we could have a flag in tiapp.xml (defaults to false) that might
allow us to emulate the old behavior (pre 1.5). this seems like a
bunch of difficulty in refactoring and testing.
2. we could create a simple JS shim that could be included that would
simply redefine createWindow and attempt to monkey patch through JS.
Example:
// psuedo code monkey patch
if (Ti.Platform.osname == 'android')
{
var oldWinCreate = Ti.UI.createWindow;
Ti.UI.createWindow = function(args)
{
//TODO: first check that this is an external, heavy weight
window ...
var activity = Ti.Android.createActivity({url:args.url});
activity.start();
return activity.window; // return activity's window proxy to heavy
weight
};
}