I have created a background intent service to update data in the firebase database.
When my application is in foreground, the data is updated properly. But when my application is killed, the data is not updated in the firebase database.
Service declare in manifest file
<service
android:name=".service.MyIntentService"
android:enabled="true"
android:exported="true"></service>
The Intent service class that works properly when my app is in the foreground but not when the app is in the background.
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.softwebsolutions.datetime.DateTime;
import com.softwebsolutions.devicemanagement.bean.WifiStatus;
import com.softwebsolutions.devicemanagement.utils.Utility;
/**
* An {#link IntentService} subclass for handling asynchronous task requests in
* a service on a separate handler thread.
* <p>
* TODO: Customize class - update intent actions, extra parameters and static
* helper methods.
*/
public class MyIntentService extends IntentService {
// TODO: Rename actions, choose action names that describe tasks that this
// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
private static final String ACTION_FOO =
"com.softwebsolutions.devicemanagement.service.action.FOO";
private static final String ACTION_BAZ =
"com.softwebsolutions.devicemanagement.service.action.BAZ";
// TODO: Rename parameters
private static final String EXTRA_PARAM1 =
"com.softwebsolutions.devicemanagement.service.extra.PARAM1";
private static final String EXTRA_PARAM2 =
"com.softwebsolutions.devicemanagement.service.extra.PARAM2";
private static final String EXTRA_PARAM3 =
"com.softwebsolutions.devicemanagement.service.extra.PARAM3";
private static final String TAG = MyIntentService.class.getSimpleName();
public MyIntentService() {
super("MyIntentService");
}
// TODO: Customize helper method
public static void startActionFoo(Context context, String param1, String param2, String param3) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_FOO);
intent.putExtra(EXTRA_PARAM1, param1);
intent.putExtra(EXTRA_PARAM2, param2);
intent.putExtra(EXTRA_PARAM3, param3);
context.startService(intent);
}
#Override protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
final String wifiMac = intent.getStringExtra(EXTRA_PARAM1);
final String strSSID = intent.getStringExtra(EXTRA_PARAM2);
final String macAddress = intent.getStringExtra(EXTRA_PARAM3);
handleActionFoo(wifiMac, strSSID, macAddress, getApplicationContext());
}
}
private void handleActionFoo(final String wifiMac, final String strSSID,
final String macAddress, final Context context) {
Log.e(TAG, "onReceive.......service........");
DatabaseReference mDatabaseTmp =
FirebaseDatabase.getInstance().getReference("Android").child("wifiList").child(wifiMac);
mDatabaseTmp.addValueEventListener(new ValueEventListener() {
#Override public void onDataChange(DataSnapshot dataSnapshot) {
Log.e(TAG, "onReceive.......addValueEventListener");
if (dataSnapshot != null) {
Log.e(TAG, "onReceive.......dataSnapshot...NOT NULL");
String floorName = "Not detect";
if (dataSnapshot.getValue() != null) {
floorName = dataSnapshot.getValue().toString();
Log.e(TAG, "onReceive: ----------->" + floorName);
}
}
}
#Override public void onCancelled(DatabaseError databaseError) {
}
});
mDatabaseTmp.addListenerForSingleValueEvent(new ValueEventListener() {
#Override public void onDataChange(DataSnapshot dataSnapshot) {
Log.e(TAG, "onReceive.......dataSnapshot...");
if (dataSnapshot != null) {
Log.e(TAG, "onReceive.......dataSnapshot...NOT NULL");
String floorName = "Not detect";
if (dataSnapshot.getValue() != null) {
floorName = dataSnapshot.getValue().toString();
}
String currentDate =
DateTime.getInstance().getCurrentDateTime(" yyyy-MM-dd'T'HH:mm:ss.SSSS'Z'");
Log.e(TAG, "onReceive.......dataSnapshot..."
+ currentDate
+ " Floor Name -------->"
+ floorName);
String deviceId = Utility.getDeviceID(context);
WifiStatus wifiStatus = new WifiStatus();
wifiStatus.setDeviceId(deviceId);
wifiStatus.setName(strSSID);
wifiStatus.setMacAddress(macAddress);
wifiStatus.setDate(currentDate);
wifiStatus.setStatus(WifiStatus.STATUS_CONNECTED);
wifiStatus.setFloorName(floorName);
Utility.updateWifiStatus(context, wifiStatus);
} else {
Log.e(TAG, "onReceive.......dataSnapshot...NULL");
}
}
#Override public void onCancelled(DatabaseError databaseError) {
}
});
}
}
An IntentService only stays active for as long as it takes handleIntent() to service the next intent, and there are no more pending intents. You can think of each intent as a "command" to the service, and it will run for as long as it takes that command to complete. When the last command is done, it stops itself. As such, it does not typically stay running for very long. If you're expecting an IntentService to stay running for a long time, you probably don't want to be using an IntentService at all.
Also, Android Services don't care if the app is in the foreground (visible) or background (invisible). They can be started and stopped regardless. The process that hosts the app may stay running indefinitely.
You haven't really stated what you're trying to accomplish with this service, so it's impossible to say what you should be doing instead. If you want a listener to be active for as long as the service is "started", then IntentService is not the right tool. You should look into a custom implementation.
Related
Here is the Google sample app. It's set up to pull metadata from a URL with a JSON. I would like to know how to have Firebase be my source.
Here is my attempt in changing the RemoteJSONSource class:
package com.mm.android.uamp.model;
import android.support.v4.media.MediaMetadataCompat;
import android.util.Log;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.mm.android.uamp.utils.LogHelper;
import java.util.ArrayList;
import java.util.Iterator;
public class RemoteJSONSource implements MusicProviderSource {
private static final String TAG = LogHelper.makeLogTag(RemoteJSONSource.class);
DatabaseReference mRootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference mMusic = mRootRef.child("music");
ArrayList<MediaMetadataCompat> tracksFromFB = new ArrayList<>();
public void buildFromFirebase(){
mMusic.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot music : dataSnapshot.getChildren()){
String title = music.child("title").getValue(String.class);
String album = music.child("album").getValue(String.class);
String artist = music.child("artist").getValue(String.class);
String genre = music.child("genre").getValue(String.class);
String source = music.child("source").getValue(String.class);
String id = String.valueOf(source.hashCode());
String iconUrl = music.child("image").getValue(String.class);
int trackNumber = music.child("trackNumber").getValue(Integer.class);
int totalTrackCount = music.child("totalTrackCount").getValue(Integer.class);
int duration = music.child("duration").getValue(Integer.class);
MediaMetadataCompat theMetadataFB = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
.putString(MusicProviderSource.CUSTOM_METADATA_TRACK_SOURCE, source)
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration)
.putString(MediaMetadataCompat.METADATA_KEY_GENRE, genre)
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, iconUrl)
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, trackNumber)
.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, totalTrackCount)
.build();
tracksFromFB.add(theMetadataFB);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
});
}
#Override
public Iterator<MediaMetadataCompat> iterator() {
buildFromFirebase();
ArrayList<MediaMetadataCompat> tracksFB = tracksFromFB;
return tracksFB.iterator();
}
}
The firebase onDataChange is asynchronous so I think it hasn't finished pulling the data yet before the iterator method returns tracksFB.iterator cause tracksFB array is null. Weird thing is when I run in debug mode with a line break on
ArrayList tracksFB = tracksFromFB;
It works. From my research I think I need a callback or some type of pausing task, but I just cant figure it out.
Possible relevant code connected to the iterator method
public interface MusicProviderSource {
String CUSTOM_METADATA_TRACK_SOURCE = "__SOURCE__";
Iterator<MediaMetadataCompat> iterator();
}
next
public class MusicProvider {
private static final String TAG = LogHelper.makeLogTag(MusicProvider.class);
private MusicProviderSource mSource;
private ConcurrentMap<String, List<MediaMetadataCompat>> mMusicListByGenre;
private final ConcurrentMap<String, MutableMediaMetadata> mMusicListById;
private final Set<String> mFavoriteTracks;
enum State {
NON_INITIALIZED, INITIALIZING, INITIALIZED
}
private volatile State mCurrentState = State.NON_INITIALIZED;
public interface Callback {
void onMusicCatalogReady(boolean success);
}
public MusicProvider() {
this(new RemoteJSONSource());
}
public MusicProvider(MusicProviderSource source) {
mSource = source;
mMusicListByGenre = new ConcurrentHashMap<>();
mMusicListById = new ConcurrentHashMap<>();
mFavoriteTracks = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
}
public Iterable<String> getGenres() {
if (mCurrentState != State.INITIALIZED) {
return Collections.emptyList();
}
return mMusicListByGenre.keySet();
}
/**
* Get an iterator over a shuffled collection of all songs
*/
public Iterable<MediaMetadataCompat> getShuffledMusic() {
if (mCurrentState != State.INITIALIZED) {
return Collections.emptyList();
}
List<MediaMetadataCompat> shuffled = new ArrayList<>(mMusicListById.size());
for (MutableMediaMetadata mutableMetadata: mMusicListById.values()) {
shuffled.add(mutableMetadata.metadata);
}
Collections.shuffle(shuffled);
return shuffled;
}
/**
* Get music tracks of the given genre
*
*/
public Iterable<MediaMetadataCompat> getMusicsByGenre(String genre) {
if (mCurrentState != State.INITIALIZED || !mMusicListByGenre.containsKey(genre)) {
return Collections.emptyList();
}
return mMusicListByGenre.get(genre);
}
}
Also the musicService.java in the link above might be relevant. PLEASE help!
There are two ways I can think to do this, but I'm not familiar enough with Firebase to provide working code.
The sample executes iterator() in an AsyncTask, expecting it to block until it can provide a response. So the first, and probably easiest, way to fix it would be to cause iterator() to wait on the data being loaded, or it failing to load. This could be a spinlock or something like wait/notify.
if (!dataloaded) {
try {
wait();
} catch (InterruptedException e) {}
}
ArrayList<MediaMetadataCompat> tracksFB = tracksFromFB;
return tracksFB.iterator();
I'd call buildFromFirebase(); in the constructor though, rather than waiting.
The second option would be to refactor UAMP to have it load the catalog asynchronously. This would be a lot more work, but it may result in a better design in the long run.
I have created a native module in Java which is responsible for running a service in background.
I have this method in ClassA:
#ReactMethod
public void startService(final Promise promise) {
Intent intent = new Intent(getCurrentActivity(), BackgroundService.class);
intent.putExtra(BackgroundService.FILENAME, "test123.html");
intent.putExtra(BackgroundService.URL,
"http://www.vogella.com/index.html");
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject("Activity doesnt exist");
} else {
getCurrentActivity().startService(intent);
}
}
Here is the code in BackgroundService:
public class BackgroundService extends IntentService {
private int result = Activity.RESULT_CANCELED;
public static final String URL = "urlpath";
public static final String FILENAME = "filename";
public static final String FILEPATH = "filepath";
public static final String RESULT = "result";
public static final String NOTIFICATION = "com.vogella.android.service.receiver";
public BackgroundService() {
super("BackgroundService");
}
public String getName() {
return "BackgroundService";
}
#Override
protected void onHandleIntent(Intent intent) {
//do stuff
ClassA.servicePromise.resolve("done with service!");
}
servicePromise is defined in classA as so :
public static Promise servicePromise = null;
The problem, if I am understanding it correctly, is that getCurrentActivity.startService(intent) does not execute properly. I am able to successfully return a promise inside startService after getCurrentActivity.startService(intent), so I know the problem is not with how I am calling startService. However, I am unable to return the promise from inside BackgroundService, which is what I want.
Inside the onHandleIntent, I tried calling ClassA.servicePromise.resolve("done with service!"); before executing any other code just to test if it works, but it doesn't.
In my manifest, I have added the service as follows:
<service android:name="com.smaplePakcage.name.BackgroundService"
android:exported="false">
</service>
I would like to store class object in android sharedpreference. I did some basic search on that and I got some answers like make it serializable object and store it but my need is so simple. I would like to store some user info like name, address, age and boolean value is active. I made one user class for that.
public class User {
private String name;
private String address;
private int age;
private boolean isActive;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isActive() {
return isActive;
}
public void setActive(boolean isActive) {
this.isActive = isActive;
}
}
Thanks.
Download gson-1.7.1.jar from this link: GsonLibJar
Add this library to your android project and configure build path.
Add the following class to your package.
package com.abhan.objectinpreference;
import java.lang.reflect.Type;
import android.content.Context;
import android.content.SharedPreferences;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
public class ComplexPreferences {
private static ComplexPreferences complexPreferences;
private final Context context;
private final SharedPreferences preferences;
private final SharedPreferences.Editor editor;
private static Gson GSON = new Gson();
Type typeOfObject = new TypeToken<Object>(){}
.getType();
private ComplexPreferences(Context context, String namePreferences, int mode) {
this.context = context;
if (namePreferences == null || namePreferences.equals("")) {
namePreferences = "abhan";
}
preferences = context.getSharedPreferences(namePreferences, mode);
editor = preferences.edit();
}
public static ComplexPreferences getComplexPreferences(Context context,
String namePreferences, int mode) {
if (complexPreferences == null) {
complexPreferences = new ComplexPreferences(context,
namePreferences, mode);
}
return complexPreferences;
}
public void putObject(String key, Object object) {
if (object == null) {
throw new IllegalArgumentException("Object is null");
}
if (key.equals("") || key == null) {
throw new IllegalArgumentException("Key is empty or null");
}
editor.putString(key, GSON.toJson(object));
}
public void commit() {
editor.commit();
}
public <T> T getObject(String key, Class<T> a) {
String gson = preferences.getString(key, null);
if (gson == null) {
return null;
}
else {
try {
return GSON.fromJson(gson, a);
}
catch (Exception e) {
throw new IllegalArgumentException("Object stored with key "
+ key + " is instance of other class");
}
}
} }
Create one more class by extending Application class like this
package com.abhan.objectinpreference;
import android.app.Application;
public class ObjectPreference extends Application {
private static final String TAG = "ObjectPreference";
private ComplexPreferences complexPrefenreces = null;
#Override
public void onCreate() {
super.onCreate();
complexPrefenreces = ComplexPreferences.getComplexPreferences(getBaseContext(), "abhan", MODE_PRIVATE);
android.util.Log.i(TAG, "Preference Created.");
}
public ComplexPreferences getComplexPreference() {
if(complexPrefenreces != null) {
return complexPrefenreces;
}
return null;
} }
Add that application class in your manifest's application tag like this.
<application android:name=".ObjectPreference"
android:allowBackup="false"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
....your activities and the rest goes here
</application>
In Your Main Activity where you wanted to store value in Shared Preference do something like this.
package com.abhan.objectinpreference;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
private final String TAG = "MainActivity";
private ObjectPreference objectPreference;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
objectPreference = (ObjectPreference) this.getApplication();
User user = new User();
user.setName("abhan");
user.setAddress("Mumbai");
user.setAge(25);
user.setActive(true);
User user1 = new User();
user1.setName("Harry");
user.setAddress("London");
user1.setAge(21);
user1.setActive(false);
ComplexPreferences complexPrefenreces = objectPreference.getComplexPreference();
if(complexPrefenreces != null) {
complexPrefenreces.putObject("user", user);
complexPrefenreces.putObject("user1", user1);
complexPrefenreces.commit();
} else {
android.util.Log.e(TAG, "Preference is null");
}
}
}
In another activity where you wanted to get the value from Preference do something like this.
package com.abhan.objectinpreference;
import android.app.Activity;
import android.os.Bundle;
public class SecondActivity extends Activity {
private final String TAG = "SecondActivity";
private ObjectPreference objectPreference;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
objectPreference = (ObjectPreference) this.getApplication();
ComplexPreferences complexPreferences = objectPreference.getComplexPreference();
android.util.Log.i(TAG, "User");
User user = complexPreferences.getObject("user", User.class);
android.util.Log.i(TAG, "Name " + user.getName());
android.util.Log.i(TAG, "Address " + user.getAddress());
android.util.Log.i(TAG, "Age " + user.getAge());
android.util.Log.i(TAG, "isActive " + user.isActive());
android.util.Log.i(TAG, "User1");
User user1 = complexPreferences.getObject("user", User.class);
android.util.Log.i(TAG, "Name " + user1.getName());
android.util.Log.i(TAG, "Address " + user1.getAddress());
android.util.Log.i(TAG, "Age " + user1.getAge());
android.util.Log.i(TAG, "isActive " + user1.isActive());
} }
Hope this can help you. In this answer I used your class for the reference 'User' so you can better understand. However we can not relay on this method if you opted to store very large objects in preference as we all know that we have limited memory size for each app in data directory so that if you are sure you have only limited data to store in shared preference you can use this alternative.
Any suggestions on this implement are most welcome.
the other way is to save each property by itself..Preferences accept only primitive types, so you can't put a complex Object in it
You can use the global class
public class GlobalState extends Application
{
private String testMe;
public String getTestMe() {
return testMe;
}
public void setTestMe(String testMe) {
this.testMe = testMe;
}
}
and then Locate your application tag in nadroid menifest, and add this into it :
android:name="com.package.classname"
and you can set and get the data from any of your activity by using the following code.
GlobalState gs = (GlobalState) getApplication();
gs.setTestMe("Some String");</code>
// Get values
GlobalState gs = (GlobalState) getApplication();
String s = gs.getTestMe();
You could just add some normal SharedPreferences "name", "address", "age" & "isActive" and simply load them when instantiating the class
Simple solution of how to store login value in by SharedPreferences.
You can extend the MainActivity class or other class where you will store the "value of something you want to keep". Put this into writer and reader classes:
public static final String GAME_PREFERENCES_LOGIN = "Login";
Here InputClass is input and OutputClass is output class, respectively.
// This is a storage, put this in a class which you can extend or in both classes:
//(input and output)
public static final String GAME_PREFERENCES_LOGIN = "Login";
// String from the text input (can be from anywhere)
String login = inputLogin.getText().toString();
// then to add a value in InputCalss "SAVE",
SharedPreferences example = getSharedPreferences(GAME_PREFERENCES_LOGIN, 0);
Editor editor = example.edit();
editor.putString("value", login);
editor.commit();
Now you can use it somewhere else, like other class. The following is OutputClass.
SharedPreferences example = getSharedPreferences(GAME_PREFERENCES_LOGIN, 0);
String userString = example.getString("value", "defValue");
// the following will print it out in console
Logger.getLogger("Name of a OutputClass".class.getName()).log(Level.INFO, userString);
I am currently in the process of creating a high performance mobile application. Now i am looking at various design patterns for consuming rest services. One such pattern that stands out is the Google IO discussion here. How i have am looking at the code to develop this design. I will be using Spring Rest for doing the actual HTTP Rest and serialization to POJO with the Serialization Library. I came across this implementation here, and will be using it as a blue print for my application. Now a major question is here.
public interface HttpMethods {
public Object getForObject(Object ... params);
public Object putForObject(Object ... params);
}
public class LocationsHttpMethods implements HttpMethods{
private final Context mContext;
public LocationsHttpMethods(Context context)
{
mContext=context;
}
#Override
public Location[] getForObject(Object... params) {
return null;
}
#Override
public Object putForObject(Object... params) {
return null;
}
}
My Location is just a pojo class. Now the question that troubles me is that the second link that i have given just uses Boolean to return data. I will be returning an array of something.
package com.confiz.rest.services;
import java.util.ArrayList;
import java.util.HashMap;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import com.confiz.rest.providers.IProvider;
import com.confiz.rest.providers.LocationsProvider;
public class ProcessorService extends Service
{
private Integer lastStartId;
private final Context mContext = this;
/**
* The keys to be used for the required actions to start this service.
*/
public static class Extras
{
/**
* The provider which the called method is on.
*/
public static final String PROVIDER_EXTRA = "PROVIDER_EXTRA";
/**
* The method to call.
*/
public static final String METHOD_EXTRA = "METHOD_EXTRA";
/**
* The action to used for the result intent.
*/
public static final String RESULT_ACTION_EXTRA = "RESULT_ACTION_EXTRA";
/**
* The extra used in the result intent to return the result.
*/
public static final String RESULT_EXTRA = "RESULT_EXTRA";
}
private final HashMap<String, AsyncServiceTask> mTasks = new HashMap<String, AsyncServiceTask>();
/**
* Identifier for each supported provider.
* Cannot use 0 as Bundle.getInt(key) returns 0 when the key does not exist.
*/
public static class Providers
{
public static final int LOATIONS_PROVIDER = 1;
}
private IProvider GetProvider(int providerId)
{
switch(providerId)
{
case Providers.LOATIONS_PROVIDER:
return new LocationsProvider(this);
}
return null;
}
/**
* Builds a string identifier for this method call.
* The identifier will contain data about:
* What processor was the method called on
* What method was called
* What parameters were passed
* This should be enough data to identify a task to detect if a similar task is already running.
*/
private String getTaskIdentifier(Bundle extras)
{
String[] keys = extras.keySet().toArray(new String[0]);
java.util.Arrays.sort(keys);
StringBuilder identifier = new StringBuilder();
for (int keyIndex = 0; keyIndex < keys.length; keyIndex++)
{
String key = keys[keyIndex];
// The result action may be different for each call.
if (key.equals(Extras.RESULT_ACTION_EXTRA))
{
continue;
}
identifier.append("{");
identifier.append(key);
identifier.append(":");
identifier.append(extras.get(key).toString());
identifier.append("}");
}
return identifier.toString();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
// This must be synchronised so that service is not stopped while a new task is being added.
synchronized (mTasks)
{
// stopSelf will be called later and if a new task is being added we do not want to stop the service.
lastStartId = startId;
Bundle extras = intent.getExtras();
String taskIdentifier = getTaskIdentifier(extras);
Log.i("ProcessorService", "starting " + taskIdentifier);
// If a similar task is already running then lets use that task.
AsyncServiceTask task = mTasks.get(taskIdentifier);
if (task == null)
{
task = new AsyncServiceTask(taskIdentifier, extras);
mTasks.put(taskIdentifier, task);
// AsyncTasks are by default only run in serial (depending on the android version)
// see android documentation for AsyncTask.execute()
task.execute((Void[]) null);
}
// Add this Result Action to the task so that the calling activity can be notified when the task is complete.
String resultAction = extras.getString(Extras.RESULT_ACTION_EXTRA);
if (resultAction != "")
{
task.addResultAction(extras.getString(Extras.RESULT_ACTION_EXTRA));
}
}
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent)
{
return null;
}
public class AsyncServiceTask extends AsyncTask<Void, Void, Object>
{
private final Bundle mExtras;
private final ArrayList<String> mResultActions = new ArrayList<String>();
private final String mTaskIdentifier;
/**
* Constructor for AsyncServiceTask
*
* #param taskIdentifier A string which describes the method being called.
* #param extras The Extras from the Intent which was used to start this method call.
*/
public AsyncServiceTask(String taskIdentifier, Bundle extras)
{
mTaskIdentifier = taskIdentifier;
mExtras = extras;
}
public void addResultAction(String resultAction)
{
if (!mResultActions.contains(resultAction))
{
mResultActions.add(resultAction);
}
}
#Override
protected Object doInBackground(Void... params)
{
Log.i("ProcessorService", "working " + mTaskIdentifier);
Object result = false;
final int providerId = mExtras.getInt(Extras.PROVIDER_EXTRA);
final int methodId = mExtras.getInt(Extras.METHOD_EXTRA);
if (providerId != 0 && methodId != 0)
{
final IProvider provider = GetProvider(providerId);
if (provider != null)
{
try
{
result = provider.RunTask(methodId, mExtras);
} catch (Exception e)
{
result = false;
}
}
}
return result;
}
#Override
protected void onPostExecute(Object result)
{
// This must be synchronised so that service is not stopped while a new task is being added.
synchronized (mTasks)
{
Log.i("ProcessorService", "finishing " + mTaskIdentifier);
// Notify the caller(s) that the method has finished executing
for (int i = 0; i < mResultActions.size(); i++)
{
Intent resultIntent = new Intent(mResultActions.get(i));
//What to do here
resultIntent.put(Extras.RESULT_EXTRA, true);
//What to do here ends.
resultIntent.putExtras(mExtras);
resultIntent.setPackage(mContext.getPackageName());
mContext.sendBroadcast(resultIntent);
}
// The task is complete so remove it from the running tasks list
mTasks.remove(mTaskIdentifier);
// If there are no other executing methods then stop the service
if (mTasks.size() < 1)
{
stopSelf(lastStartId);
}
}
}
}
}
Now if you browse to the code that contain the AsyncService, and puts the resultIntent.put(Extras.RESULT_EXTRA, true);
Now how should i pass the data back to the intent. I heard Serializable is bad, and Parceable is ugly code. What else can i use. Secondly, where do i add the SQL cache retrieve code. How can i add this code to the framework. Hope i make sense.
I've been looking through the code of the GoogleIO Android app and I notice the their did not call stop() function on the GoogleAnalytics' instance. What will happen if we don't call stop()?
This is the code:
package com.google.android.apps.iosched.util;
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
/**
* Helper singleton class for the Google Analytics tracking library.
*/
public class AnalyticsUtils {
private static final String TAG = "AnalyticsUtils";
GoogleAnalyticsTracker mTracker;
private Context mApplicationContext;
/**
* The analytics tracking code for the app.
*/
// TODO: insert your Analytics UA code here.
private static final String UACODE = "INSERT_YOUR_ANALYTICS_UA_CODE_HERE";
private static final int VISITOR_SCOPE = 1;
private static final String FIRST_RUN_KEY = "firstRun";
private static final boolean ANALYTICS_ENABLED = true;
private static AnalyticsUtils sInstance;
/**
* Returns the global {#link AnalyticsUtils} singleton object, creating one if necessary.
*/
public static AnalyticsUtils getInstance(Context context) {
if (!ANALYTICS_ENABLED) {
return sEmptyAnalyticsUtils;
}
if (sInstance == null) {
if (context == null) {
return sEmptyAnalyticsUtils;
}
sInstance = new AnalyticsUtils(context);
}
return sInstance;
}
private AnalyticsUtils(Context context) {
if (context == null) {
// This should only occur for the empty Analytics utils object.
return;
}
mApplicationContext = context.getApplicationContext();
mTracker = GoogleAnalyticsTracker.getInstance();
// Unfortunately this needs to be synchronous.
mTracker.start(UACODE, 300, mApplicationContext);
Log.d(TAG, "Initializing Analytics");
// Since visitor CV's should only be declared the first time an app runs, check if
// it's run before. Add as necessary.
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mApplicationContext);
final boolean firstRun = prefs.getBoolean(FIRST_RUN_KEY, true);
if (firstRun) {
Log.d(TAG, "Analytics firstRun");
String apiLevel = Integer.toString(Build.VERSION.SDK_INT);
String model = Build.MODEL;
mTracker.setCustomVar(1, "apiLevel", apiLevel, VISITOR_SCOPE);
mTracker.setCustomVar(2, "model", model, VISITOR_SCOPE);
// Close out so we never run this block again, unless app is removed & =
// reinstalled.
prefs.edit().putBoolean(FIRST_RUN_KEY, false).commit();
}
}
public void trackEvent(final String category, final String action, final String label,
final int value) {
// We wrap the call in an AsyncTask since the Google Analytics library writes to disk
// on its calling thread.
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... voids) {
try {
mTracker.trackEvent(category, action, label, value);
Log.d(TAG, "iosched Analytics trackEvent: "
+ category + " / " + action + " / " + label + " / " + value);
} catch (Exception e) {
// We don't want to crash if there's an Analytics library exception.
Log.w(TAG, "iosched Analytics trackEvent error: "
+ category + " / " + action + " / " + label + " / " + value, e);
}
return null;
}
}.execute();
}
public void trackPageView(final String path) {
// We wrap the call in an AsyncTask since the Google Analytics library writes to disk
// on its calling thread.
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... voids) {
try {
mTracker.trackPageView(path);
Log.d(TAG, "iosched Analytics trackPageView: " + path);
} catch (Exception e) {
// We don't want to crash if there's an Analytics library exception.
Log.w(TAG, "iosched Analytics trackPageView error: " + path, e);
}
return null;
}
}.execute();
}
/**
* Empty instance for use when Analytics is disabled or there was no Context available.
*/
private static AnalyticsUtils sEmptyAnalyticsUtils = new AnalyticsUtils(null) {
#Override
public void trackEvent(String category, String action, String label, int value) {}
#Override
public void trackPageView(String path) {}
};
}
As you can see, they start the tracker with 5 minutes interval mTracker.start(UACODE, 300, mApplicationContext); but never call the mTracker.stop() method. Will there be any consequences? Does it mean the service will dispatch the data even when the app is closed or stopped?
EasyTracker - I found this blog entry in the google analytics blog. There they describe an EasyTracker class, that wraps the normal Tracker class and has some nice features.
Configuration via resource file (no coding needed)
Everything's done in a separate thread (not in the ui-thread as with the normal Tracker)
...
And this EasyTracker does not need to be stopped explicity either.
There shouldn't be happen much -- when you close or stop the app, and even if it still lives in the background and there's no activity anymore, no new data will be gathered (because no one invokes track() anymore. So I think it's just good manners to explicitly stop the tracker.