MVC in Android: Application or Service for asynchronous updates? - android

(I'm sorry for not being so clear in my first post)
Here is the situation: I have data that is to be refreshed from the Internet. Let's call it Model.
What I want to do: Basically it sounds like an MVC model, where the Model is also kept persistent in local (private) storage. The Model and its associated methods are application-wise. There are several Activity's that display and manipulate different aspects of it:
User
navigates across different Activity's
that display Model
from different perspectives. Currently I have a ListActivity for all elements, and an Activity for one element's details
Sometimes Model needs refreshing.
Surely this is done on a different thread. Refreshing can be triggered from several Activity's.
There are several (time consuming) common
tasks that can be triggered from different Activity's
My application loads and saves Model
to private storage when it starts
and stops
My problem: I'm not sure where to put Model and the related tasks in. Also, I don't know what mechanism to use to notify Activity's. Currently I come up with 2 approaches:
Use Service and send broadcasts. Saving to disk is performed in Service#onDestroyed(), so I want to minimize that by binding it to Activity's. At this point, I'm also not sure how to deliver the updated information: whether to provide a getter in Binder, or include that in the broadcast message.
Customize the Application object so that refreshing methods and getters are available globally. I then perform update from Activity's using AsyncTask. If there are other Activity's that are behind the current Activity, they will update in onResume() when the user navigates back.
Reasons I'm not using a class with static methods:
I need to save and store Model to disk.
Some of the methods need a Context
for displaying toasts, notifications, caching, etc.
Also, I don't put these functionalities in an Activity because there are several activities that manipulate the same piece of persistent data.
Below are pseudocode illustrating what I mean:
Using Service:
/** Service maintaining state and performing background tasks */
class MyService extends Service {
Model mModel;
Binder mBinder;
onCreate() {
super.onCreate();
mBinder = new Binder();
// load mModel from disk, or do default initialization
}
onDestroy() {
super.onDestroy();
// save mModel to disk
}
onBind() {
return mBinder;
}
class Binder {
refresh() {
new AsyncTask() {
doInBackground() {
// update mModel from Internet
}
onPostExecute() {
sendBroadcasts(new Intent("my.package.REFRESHED"));
}
}.execute();
}
getState() {
return mModel.getState();
}
}
}
/** Activity displaying result */
class MyActivity extends ListActivity {
MyService.Binder mBinder;
onCreate() {
super.onCreate();
// register mReceiver
// bind service
}
onDestroy() {
super.onDestroy();
// unbind service
// unregister mReceiver
}
/** Invokes time-consuming update */
refresh() {
// binding is asynchronous, and user may trigger refreshing too early
if (mBinder != null) {
mBinder.refresh();
}
}
BroadcastReceiver mReceiver = new BroadcastReceiver() {
onReceive(Intent intent) {
if ("my.package.REFRESHED".equals(intent.getAction())
&& mBinder != null) {
updateViews(mBinder.getState());
}
}
};
}
Make the functionality globally accessible in the custom Application object
/** Custom Application providing domain specific functionalities */
class MyApplication extends Application {
Model mModel;
onCreate() {
super.onCreate();
// load mModel from disk, or do default initialization
}
onTerminate() {
super.onTerminate();
// save mModel to disk
}
void refresh() {
/** time-consuming */
}
getState() {
return mModel.getState();
}
}
/** Activity displaying result */
class MyActivity extends ListActivity {
onResume() {
super.onResume();
// in case some top Activities have refreshed
// and user is navigating back
updateViews(((MyApplication)getApplicationContext()).getState());
}
/** Invokes time-consuming update */
refresh() {
new AsyncTask() {
doInBackground() {
((MyApplication)getApplicationContext()).refresh();
}
onPostExecute() {
// update the ListView according to result
updateViews(((MyApplication)getApplicationContext()).getState());
}
}.execute();
}
}
Weaknesses I can think of for the Service approach is complexity, since Binding is asynchronous. And it's very likely that I have to repeat some code because I have both ListActivity and Activity
For the Application approach, the documentation says not to rely on onTerminate() being called.
I know I'm being very awkward. What is the conventional way to solve this sort of problem?
Many thanks.

Services are mostly suitable for something that is not bound to a single Activity (and usually work together with NotificationManager or a Widget). This doesn't seem to be the case.
So my suggestion is to have a well-engineered AsyncTask that manages state via SharedPreferences/SQLite itself (instead of abusing Applicaion) and will be launched from the ListActivity.

Well, you could extend a BroadcastReceiver instead of a Service, and when it's done doing what it needs to do, it loads up an Activity with the results.

You didn't explain what kind of info are you getting but this line is important:
These tasks involve states that need
to be loaded and saved gracefully when
the application starts and stops
If that is the case, why don't you do an AsynTask inside the Activity?
I had your same worries about sending Intents with ArrayList inside but I have an app which does exactly that and I am not having performance issues.

Related

Is there any alternative of Intent to send data for Class (Not Activity)?

Previously, I was using activities in my project and was sending data using Intent from one activity to another which works perfectly fine.
Now requirement changes, and I have to show all things on Dialogs, instead of activities, so there will separate 3-4 dialog class and single activity.
Now I want the same flow on dialog also, but there is a problem to pass data temporarily exactly how intent works!
I tried with Singleton, but the problem is it remains data until the whole lifecycle, but I don't want that.
I can't use the interface also because there are lots of things to pass.
Also, I can't use bundle fundle n all those, because this all depends on runtime, I meant it depends upon if user fill input
Question: How can I pass data from one class to another class or activity? and it should not save value for the whole lifecycle.
statically sending data is an option but its not good way, because memory to static variables is assigned at Application level and can be cleared when memory needed. The best way is to use
Object Oriented approach
For example if you have a class, You can send data in class constructor, or can send it through function call
class class1
{
public class1(Object data) { // constructor
// you can use this data
}
//// Or through function call
public void func(Object data) { // this method can be called by other classes which has its object
// you can use this data
}
}
Now lets assume you have another class
class class2
{
class1 obj = new class1(your_data_object); // if you want to send through constructor
void someMethod() {
obj.func(your_data_object); // send data whatever you want to send
}
}
Obviously your case will not be as simple as my example, but to handle complex cases you can implement interfaces.
Interface Example
define an interface
interface myListener {
public void listen(Object data);
}
now lets say you want to call class2 method from class1. then class2 must implement this interface.
public class class2 implements myListener {
#override
public void listen(Object data)
{
/// you got data here, do whatever you want to do that with that data.
}
}
Now in class1 if you have interface object you can call class2 method
interfaceRef.listen(your_data);
Try with EventBus or BroadCastReceivers to pass data accordingly in local variables.
EventBus is a publish/subscribe event bus for Android and Java. EventBus... simplifies the communication between components. decouples event senders and receivers. performs well with Activities, Fragments, and background threads.
http://greenrobot.org/eventbus
First Register to EventBus in your Activity
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
Now pass the data from anywhere ,whether it is activity/fragment/background service etc etc etc like :
EventBus.getDefault().postSticky(new MessageEvent("your data here");
Now in your activity receive this message like :
#Subscribe(sticky = true,threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
Log.e("TAG","Event Received");
Log.e("TAG",event.getData);
}

Callback to activity from other class

I have Activity class, Controller class (normal java class use to control number of activity) and BusinessEngine class (normal java class use to process data).
When I need to do some calculation from activity, Activity will call Controller and Controller will call BusinessEngine to do the calculation. When BusinessEngine done with the calculation, it will pass the value back to Controller and finally let the activity know the calculation is complete.
The problem is how I callback Activity from Controller class? Or pass any data to Activity and notify it the data has been change?
Any "long" running tasks must be performed in a background thread. I'm not sure if your currently doing this for your task, so just in case your not, there are a couple of ways to do this. The first is to simply use a AsyncTask, the second is to create your own instance of AbstractExecutorService (AsyncTask uses ThreadPoolExecutor) and use that to post Runnable or Callables to. The later way may save you a lot of re factoring depending on your code base.
Assuming you're now running the task in a background thread, it's necessary to perform your UI updates on the UI thread. There are again a couple of ways to do this. One method is to post a runnable to the method Activity#runOnUiThread, the second is to use a Handler which has previously been created on the UI thread (which Activity#runOnUiThread does behind the scenes).
So, assume your Activity has a method #postResults(final Object o), and your controller has the method #doSomething(final Activity activity).
Your activity would look something like this.
public class MyActivity extends Activity {
Controller controller = ....
ExecutorService service = Executors.newFixedThreadPool(10);
private void startTask() {
Runnable r = new Runnable() {
public void run() {
c.doSomething(MyActivity.this);
}
}
service.submit(r);
}
public void postResults(final Object o) {
Runnable r = new Runnable() {
public void run() {
// Update your UI here
}
}
runOnUiThread(r)
}
}
and your controller
public class Controller {
public void doSomething(final Activity activity) {
// Perform some long running task here
activity.postResults(someObject);
}
}
Obviously this example could be tidied up (for example passing a interface to doSomething rather than the Activity), but hopefully it should be enough to understand what you need to do :)
Why are you looking for the controller to call you Activity? Normally, your Activity must call the controller via its methods and directly get results from them:
// Code in your Activity
result = controller.doSomething(args);
try using a android AsyncTask, if your method takes a long time to process. example
Add your classes to an Async task or if you're calling the classes and passing them from one class to the other.I would say to use static class. And provide some code so we can know how you are passing your data.
If not use general methods to call the superclass or the subclass.
My answer is a bit abstract as information is less.

get Activity's reference from a Service

I need to get a reference to the main Activity from a Service.
This is my design:
MainActivity.java
public class MainActivity extends Activity{
private Intent myIntent;
onCreate(){
myIntent=new Intent(MainActivity.this, MyService.class);
btnStart.setOnClickListener(new OnClickListener(){
public void onClick(View V){
startService(myIntent);
});
}}
MyService.java
class MyService extends Service{
public IBinder onBind(Intent intent) {
return null;
}
onCreate(){
//Here I need to have a MainActivity reference
//to pass it to another object
}
}
How can I do this?
[EDIT]
Thanks to all for the answers!
This app is a web server, that at this moment works only with threads, and I want to use a service instead, to make it work also in the background.
The problem is that I have a class that is responsible for getting the page from assets, and to do this operation I need to use this method:
InputStream iS =myActivity.getAssets().open("www/"+filename);
At this moment my project has only one Activity and no services, so I can pass the main activity's reference directly from itself:
WebServer ws= new DroidWebServer(8080,this);
So, in order to make this app work with a service, what should I change in my design?
You didn't explain why you need this. But this is definitely bad design. Storing references to Activity is the first thing you shouldn't do with activities. Well, you can, but you must track Activity lifecycle and release the reference after its onDestroy() is called. If you are not doing this, you'll get a memory leak (when configuration changes, for example). And, well, after onDestroy() is called, Activity is considered dead and is most likely useless anyway.
So just don't store the reference in Service. Describe what you need to achieve instead. I'm sure there are better alternatives out there.
UPDATE
Ok, so you do not actually need reference to Activity. Instead you need reference to Context (which in your case should be ApplicationContext to not keep reference to Activity or any other component for that matter).
Assuming you have a separate class that handles WebService request:
class WebService
{
private final Context mContext;
public WebService(Context ctx)
{
//The only context that is safe to keep without tracking its lifetime
//is application context. Activity context and Service context can expire
//and we do not want to keep reference to them and prevent
//GC from recycling the memory.
mContext = ctx.getApplicationContext();
}
public void someFunc(String filename) throws IOException
{
InputStream iS = mContext.getAssets().open("www/"+filename);
}
}
Now you can create & use WebService instance from Service (which is recommended for such background tasks) or even from Activity (which is much trickier to get right when web service calls or long background tasks are involved).
An example with Service:
class MyService extends Service
{
WebService mWs;
#Override
public void onCreate()
{
super.onCreate();
mWs = new WebService(this);
//you now can call mWs.someFunc() in separate thread to load data from assets.
}
#Override
public IBinder onBind(Intent intent)
{
return null;
}
}
To communicate between your service and activity you should use AIDL.
More info on this link:
EDIT: (Thanks Renan Malke Stigliani)
http://developer.android.com/guide/components/aidl.html
The AIDL is overkill unless the activity and service are in seperate apks.
Just use a binder to a local service.
(full example here: http://developer.android.com/reference/android/app/Service.html)
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
Agree with inazaruk's comments. But, In terms of communicating between an Activity and a Service, you have a few choices - AIDL (as mentioned above), Messenger, BroadcastReicever, etc. The Messenger method is similar to AIDL but doesn't require you to define the interfaces. You can start here:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/MessengerService.html

Background task, progress dialog, orientation change - is there any 100% working solution?

I download some data from internet in background thread (I use AsyncTask) and display a progress dialog while downloading. Orientation changes, Activity is restarted and then my AsyncTask is completed - I want to dismiss the progess dialog and start a new Activity. But calling dismissDialog sometimes throws an exception (probably because the Activity was destroyed and new Activity hasn't been started yet).
What is the best way to handle this kind of problem (updating UI from background thread that works even if user changes orientation)? Did someone from Google provide some "official solution"?
Step #1: Make your AsyncTask a static nested class, or an entirely separate class, just not an inner (non-static nested) class.
Step #2: Have the AsyncTask hold onto the Activity via a data member, set via the constructor and a setter.
Step #3: When creating the AsyncTask, supply the current Activity to the constructor.
Step #4: In onRetainNonConfigurationInstance(), return the AsyncTask, after detaching it from the original, now-going-away activity.
Step #5: In onCreate(), if getLastNonConfigurationInstance() is not null, cast it to your AsyncTask class and call your setter to associate your new activity with the task.
Step #6: Do not refer to the activity data member from doInBackground().
If you follow the above recipe, it will all work. onProgressUpdate() and onPostExecute() are suspended between the start of onRetainNonConfigurationInstance() and the end of the subsequent onCreate().
Here is a sample project demonstrating the technique.
Another approach is to ditch the AsyncTask and move your work into an IntentService. This is particularly useful if the work to be done may be long and should go on regardless of what the user does in terms of activities (e.g., downloading a large file). You can use an ordered broadcast Intent to either have the activity respond to the work being done (if it is still in the foreground) or raise a Notification to let the user know if the work has been done. Here is a blog post with more on this pattern.
The accepted answer was very helpful, but it doesn't have a progress dialog.
Fortunately for you, reader, I have created an extremely comprehensive and working example of an AsyncTask with a progress dialog!
Rotation works, and the dialog survives.
You can cancel the task and dialog by pressing the back button (if you want this behaviour).
It uses fragments.
The layout of the fragment underneath the activity changes properly when the device rotates.
I've toiled for a week to find a solution to this dilemma without resorting to editing the manifest file. The assumptions for this solution are:
You always need to use a progress dialog
Only one task is performed at a time
You need the task to persist when the phone is rotated and the progress dialog to be automatically dismisses.
Implementation
You will need to copy the two files found at the bottom of this post into your workspace. Just make sure that:
All your Activitys should extend BaseActivity
In onCreate(), super.onCreate() should be called after you initialize any members that need to be accessed by your ASyncTasks. Also, override getContentViewId() to provide the form layout id.
Override onCreateDialog() like usual to create dialogs managed by the activity.
See code below for a sample static inner class to make your AsyncTasks. You can store your result in mResult to access later.
final static class MyTask extends SuperAsyncTask<Void, Void, Void> {
public OpenDatabaseTask(BaseActivity activity) {
super(activity, MY_DIALOG_ID); // change your dialog ID here...
// and your dialog will be managed automatically!
}
#Override
protected Void doInBackground(Void... params) {
// your task code
return null;
}
#Override
public boolean onAfterExecute() {
// your after execute code
}
}
And finally, to launch your new task:
mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();
That's it! I hope this robust solution will help someone.
BaseActivity.java (organize imports yourself)
protected abstract int getContentViewId();
public abstract class BaseActivity extends Activity {
protected SuperAsyncTask<?, ?, ?> mCurrentTask;
public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getContentViewId());
mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
if (mCurrentTask != null) {
mCurrentTask.attach(this);
if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
&& mDialogMap.get((Integer) mCurrentTask.dialogId)) {
mCurrentTask.postExecution();
}
}
}
#Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
mDialogMap.put(id, true);
}
#Override
public Object onRetainNonConfigurationInstance() {
if (mCurrentTask != null) {
mCurrentTask.detach();
if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
&& mDialogMap.get((Integer) mCurrentTask.dialogId)) {
return mCurrentTask;
}
}
return super.onRetainNonConfigurationInstance();
}
public void cleanupTask() {
if (mCurrentTask != null) {
mCurrentTask = null;
System.gc();
}
}
}
SuperAsyncTask.java
public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected BaseActivity mActivity = null;
protected Result mResult;
public int dialogId = -1;
protected abstract void onAfterExecute();
public SuperAsyncTask(BaseActivity activity, int dialogId) {
super();
this.dialogId = dialogId;
attach(activity);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
mActivity.showDialog(dialogId); // go polymorphism!
}
protected void onPostExecute(Result result) {
super.onPostExecute(result);
mResult = result;
if (mActivity != null &&
mActivity.mDialogMap.get((Integer) dialogId) != null
&& mActivity.mDialogMap.get((Integer) dialogId)) {
postExecution();
}
};
public void attach(BaseActivity activity) {
this.mActivity = activity;
}
public void detach() {
this.mActivity = null;
}
public synchronized boolean postExecution() {
Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
if (dialogExists != null || dialogExists) {
onAfterExecute();
cleanUp();
}
public boolean cleanUp() {
mActivity.removeDialog(dialogId);
mActivity.mDialogMap.remove((Integer) dialogId);
mActivity.cleanupTask();
detach();
return true;
}
}
Did someone from Google provide some "official solution"?
Yes.
The solution is more of an application architecture proposal rather that just some code.
They proposed 3 design patterns that allows an application to work in-sync with a server, regardless of the application state (it will work even if the user finishes the app, the user changes screen, the app gets terminated, every other possible state where a background data operation could be interrumpted, this covers it)
The proposal is explained in the Android REST client applications speech during Google I/O 2010 by Virgil Dobjanschi. It is 1 hour long, but it is extremely worth watching.
The basis of it is abstracting network operations to a Service that works independently to any Activity in the application. If you're working with databases, the use of ContentResolver and Cursor would give you an out-of-the-box Observer pattern that is convenient to update UI without any aditional logic, once you updated your local database with the fetched remote data. Any other after-operation code would be run via a callback passed to the Service (I use a ResultReceiver subclass for this).
Anyway, my explanation is actually pretty vague, you should definititely watch the speech.
While Mark's (CommonsWare) answer does indeed work for orientation changes, it fails if the Activity is destroyed directly (like in the case of a phone call).
You can handle the orientation changes AND the rare destroyed Activity events by using an Application object to reference your ASyncTask.
There's an excellent explanation of the problem and the solution here:
Credit goes completely to Ryan for figuring this one out.
After 4 years Google solved the problem just calling setRetainInstance(true) in Activity onCreate. It will preserve your activity instance during device rotation. I have also a simple solution for older Android.
you should call all activity actions using activity handler. So if you are in some thread you should create a Runnable and posted using Activitie's Handler. Otherwise your app will crash sometimes with fatal exception.
This is my solution: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog
Basically the steps are:
I use onSaveInstanceState to save the task if it is still
processing.
In onCreate I get the task if it was saved.
In onPause I discard the ProgressDialog if it is shown.
In onResume I show the ProgressDialog if the task is still
processing.

Google Analytics in Android app - dealing with multiple activities

I was pretty excited to see how easy it is to set up Google Analytics with my app, but the lack of documentation has me sitting with a few questions. The only information that I can find is right from the documentation here, which only looks at reporting PageViews and Events from one Activity. I want to report PageViews and Events across multiple Activities in my app.
Right now in the onCreate() of all of my activities, I am calling:
tracker = GoogleAnalyticsTracker.getInstance();
tracker.start("UA-xxxxxxxxx", this);
And in the onDestroy() of all of my activities:
tracker.stop();
I then track PageViews and Events as needed, and Dispatch them along with another HTTP request I am performing. But I'm not so sure this is the best way. Should I be calling start() and stop() in each activity, or should I only call start() and stop() in my main launcher activity?
The problem with calling start()/stop() in every activity (as suggested by Christian) is that it results in a new "visit" for every activity your user navigates to. If this is okay for your usage, then that's fine, however, it's not the way most people expect visits to work. For example, this would make comparing android numbers to web or iphone numbers very difficult, since a "visit" on the web and iphone maps to a session, not a page/activity.
The problem with calling start()/stop() in your Application is that it results in unexpectedly long visits, since Android makes no guarantees to terminate the application after your last activity closes. In addition, if your app does anything with notifications or services, these background tasks can start up your app and result in "phantom" visits. UPDATE: stefano properly points out that onTerminate() is never called on a real device, so there's no obvious place to put the call to stop().
The problem with calling start()/stop() in a single "main" activity (as suggested by Aurora) is that there's no guarantee that the activity will stick around for the duration that your user is using your app. If the "main" activity is destroyed (say to free up memory), your subsequent attempts to write events to GA in other activities will fail because the session has been stopped.
In addition, there's a bug in Google Analytics up through at least version 1.2 that causes it to keep a strong reference to the context you pass in to start(), preventing it from ever getting garbage collected after its destroyed. Depending on the size of your context, this can be a sizable memory leak.
The memory leak is easy enough to fix, it can be solved by calling start() using the Application instead of the activity instance itself. The docs should probably be updated to reflect this.
eg. from inside your Activity:
// Start the tracker in manual dispatch mode...
tracker.start("UA-YOUR-ACCOUNT-HERE", getApplication() );
instead of
// Start the tracker in manual dispatch mode...
tracker.start("UA-YOUR-ACCOUNT-HERE", this ); // BAD
Regarding when to call start()/stop(), you can implement a sort of manual reference counting, incrementing a count for each call to Activity.onCreate() and decrementing for each onDestroy(), then calling GoogleAnalyticsTracker.stop() when the count reaches zero.
The new EasyTracker library from Google will take care of this for you.
Alternately, if you can't subclass the EasyTracker activities, you can implement this manually yourself in your own activity base class:
public abstract class GoogleAnalyticsActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Need to do this for every activity that uses google analytics
GoogleAnalyticsSessionManager.getInstance(getApplication()).incrementActivityCount();
}
#Override
protected void onResume() {
super.onResume();
// Example of how to track a pageview event
GoogleAnalyticsTracker.getInstance().trackPageView(getClass().getSimpleName());
}
#Override
protected void onDestroy() {
super.onDestroy();
// Purge analytics so they don't hold references to this activity
GoogleAnalyticsTracker.getInstance().dispatch();
// Need to do this for every activity that uses google analytics
GoogleAnalyticsSessionManager.getInstance().decrementActivityCount();
}
}
public class GoogleAnalyticsSessionManager {
protected static GoogleAnalyticsSessionManager INSTANCE;
protected int activityCount = 0;
protected Integer dispatchIntervalSecs;
protected String apiKey;
protected Context context;
/**
* NOTE: you should use your Application context, not your Activity context, in order to avoid memory leaks.
*/
protected GoogleAnalyticsSessionManager( String apiKey, Application context ) {
this.apiKey = apiKey;
this.context = context;
}
/**
* NOTE: you should use your Application context, not your Activity context, in order to avoid memory leaks.
*/
protected GoogleAnalyticsSessionManager( String apiKey, int dispatchIntervalSecs, Application context ) {
this.apiKey = apiKey;
this.dispatchIntervalSecs = dispatchIntervalSecs;
this.context = context;
}
/**
* This should be called once in onCreate() for each of your activities that use GoogleAnalytics.
* These methods are not synchronized and don't generally need to be, so if you want to do anything
* unusual you should synchronize them yourself.
*/
public void incrementActivityCount() {
if( activityCount==0 )
if( dispatchIntervalSecs==null )
GoogleAnalyticsTracker.getInstance().start(apiKey,context);
else
GoogleAnalyticsTracker.getInstance().start(apiKey,dispatchIntervalSecs,context);
++activityCount;
}
/**
* This should be called once in onDestrkg() for each of your activities that use GoogleAnalytics.
* These methods are not synchronized and don't generally need to be, so if you want to do anything
* unusual you should synchronize them yourself.
*/
public void decrementActivityCount() {
activityCount = Math.max(activityCount-1, 0);
if( activityCount==0 )
GoogleAnalyticsTracker.getInstance().stop();
}
/**
* Get or create an instance of GoogleAnalyticsSessionManager
*/
public static GoogleAnalyticsSessionManager getInstance( Application application ) {
if( INSTANCE == null )
INSTANCE = new GoogleAnalyticsSessionManager( ... ,application);
return INSTANCE;
}
/**
* Only call this if you're sure an instance has been previously created using #getInstance(Application)
*/
public static GoogleAnalyticsSessionManager getInstance() {
return INSTANCE;
}
}
The SDK now has a external library which takes care of all of this. Its called EasyTracker. You can just import it and extend the provided Activity or ListActivity, create a string resource with your code and you are done.
The tracker will only track the activity where it's executed. So, why don't you subclass an Activity which start it every time on onCreate:
public class GAnalyticsActivity extends Activity{
public void onCreate(Bundle icicle){
super.onCreate(icile);
tracker = GoogleAnalyticsTracker.getInstance();
tracker.start("UA-xxxxxxxxx", this);
}
// same for on destroy
}
Then, you extends that class for every activity you use:
public class YourActivity extends GAnalyticsActivity{
public void onCreate(Bundle icicle){
super.onCreate(icile);
// whatever you do here you can be sure
// that the tracker has already been started
}
}
The approach I am using is to use a Bound Service (I happen to be using one already so was spared the creation of extra boiler plate code.)
A Bound Service will only last as long as there are Activities bound to it. All the activities in my app bind to this service, so it lasts only as long as the user is actively using my application - therefore very much a real 'session'.
I start the tracker with a singleton instance of Application which I have extended and added a static getInstance() method to retrieve the instance:
// Non-relevant code removed
public IBinder onBind(Intent intent) {
tracker = GoogleAnalyticsTracker.getInstance();
tracker.startNewSession(PROPERTY_ID, MyApp.getInstance());
}
public boolean onUnbind(Intent intent) {
tracker.stopSession();
}
See: http://developer.android.com/guide/topics/fundamentals/bound-services.html
I did a time based split between visits in my app, working like this:
I've build a wrapper singleton Tracker object for the GoogleAnalyticsTracker where i keep the last time something got tracked. If that time's more then x seconds i treat it as a new visit.
Of course this is only useful if you track everything in your app, and may not be the best solution in every situation, works well for my app though.
It only supports trackPageView, but setCustomVar and trackEvent should be easily implemented..
Anywhere you need to track something just add the line:
Tracker.getInstance(getApplicationContext()).trackPageView("/HelloPage");
I usually do it in the onResume of an activity
Tracker gist
You will need something like this: http://mufumbo.wordpress.com/2011/06/13/google-analytics-lags-on-android-how-to-make-it-responsive/
That's on the previous version and used to work very well. Now I'm in the same struggle as you, as V2 doesn't seems to be very consistent.
I wonder if this is something that could be done using AOP.
Android can only use compile-time AOP methods so maybe something like AspectJ?
There's a little more info on using AspectJ in Android in this thread. The main issue being that you would still need to declare on classes you own.

Categories

Resources