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.
Related
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.
I am generating protobuf class using Squareup Wire protobuf libary
here is my proto file
syntax = "proto2";
package squareup.dinosaurs;
option java_package = "com.squareup.dinosaurs";
message Dinosaur {
// Common name of this dinosaur, like "Stegosaurus".
optional string name = 1;
// URLs with images of this dinosaur.
repeated string picture_urls = 2;
}
and here is my auto generated code
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: dinosaur/dinosaur.proto at 8:1
package com.squareup.dinosaurs;
import com.squareup.wire.FieldEncoding;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.ProtoReader;
import com.squareup.wire.ProtoWriter;
import java.io.IOException;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.List;
import okio.ByteString;
public final class Dinosaur extends Message<Dinosaur, Dinosaur.Builder> {
public static final ProtoAdapter<Dinosaur> ADAPTER = new ProtoAdapter<Dinosaur>(FieldEncoding.LENGTH_DELIMITED, Dinosaur.class) {
#Override
public int encodedSize(Dinosaur value) {
return (value.name != null ? ProtoAdapter.STRING.encodedSizeWithTag(1, value.name) : 0)
+ ProtoAdapter.STRING.asRepeated().encodedSizeWithTag(2, value.picture_urls)
+ value.unknownFields().size();
}
#Override
public void encode(ProtoWriter writer, Dinosaur value) throws IOException {
if (value.name != null) ProtoAdapter.STRING.encodeWithTag(writer, 1, value.name);
if (value.picture_urls != null) ProtoAdapter.STRING.asRepeated().encodeWithTag(writer, 2, value.picture_urls);
writer.writeBytes(value.unknownFields());
}
#Override
public Dinosaur decode(ProtoReader reader) throws IOException {
Builder builder = new Builder();
long token = reader.beginMessage();
for (int tag; (tag = reader.nextTag()) != -1;) {
switch (tag) {
case 1: builder.name(ProtoAdapter.STRING.decode(reader)); break;
case 2: builder.picture_urls.add(ProtoAdapter.STRING.decode(reader)); break;
default: {
FieldEncoding fieldEncoding = reader.peekFieldEncoding();
Object value = fieldEncoding.rawProtoAdapter().decode(reader);
builder.addUnknownField(tag, fieldEncoding, value);
}
}
}
reader.endMessage(token);
return builder.build();
}
#Override
public Dinosaur redact(Dinosaur value) {
Builder builder = value.newBuilder();
builder.clearUnknownFields();
return builder.build();
}
};
private static final long serialVersionUID = 0L;
public static final String DEFAULT_NAME = "";
/**
* Common name of this dinosaur, like "Stegosaurus".
*/
public final String name;
/**
* URLs with images of this dinosaur.
*/
public final List<String> picture_urls;
public Dinosaur(String name, List<String> picture_urls) {
this(name, picture_urls, ByteString.EMPTY);
}
public Dinosaur(String name, List<String> picture_urls, ByteString unknownFields) {
super(unknownFields);
this.name = name;
this.picture_urls = immutableCopyOf("picture_urls", picture_urls);
}
#Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.name = name;
builder.picture_urls = copyOf("picture_urls", picture_urls);
builder.addUnknownFields(unknownFields());
return builder;
}
#Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof Dinosaur)) return false;
Dinosaur o = (Dinosaur) other;
return equals(unknownFields(), o.unknownFields())
&& equals(name, o.name)
&& equals(picture_urls, o.picture_urls);
}
#Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (name != null ? name.hashCode() : 0);
result = result * 37 + (picture_urls != null ? picture_urls.hashCode() : 1);
super.hashCode = result;
}
return result;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (name != null) builder.append(", name=").append(name);
if (picture_urls != null) builder.append(", picture_urls=").append(picture_urls);
return builder.replace(0, 2, "Dinosaur{").append('}').toString();
}
public static final class Builder extends com.squareup.wire.Message.Builder<Dinosaur, Builder> {
public String name;
public List<String> picture_urls;
public Builder() {
picture_urls = newMutableList();
}
/**
* Common name of this dinosaur, like "Stegosaurus".
*/
public Builder name(String name) {
this.name = name;
return this;
}
/**
* URLs with images of this dinosaur.
*/
public Builder picture_urls(List<String> picture_urls) {
checkElementsNotNull(picture_urls);
this.picture_urls = picture_urls;
return this;
}
#Override
public Dinosaur build() {
return new Dinosaur(name, picture_urls, buildUnknownFields());
}
}
}
now the issue is i want to directly store the value of Dinosaur into the database using Realm in android. i want Dinosaur class to act as a model.
but the problem is Dinosaur class is declared as final so i cant even derive it.
So is there any design pattern or way that exists to reuse or convert Dinosaur class into model?
You cannot use the Wire Dinosaur with Realm as Wire also require you to extend the Message class, while Realm require you to extend RealmObject.
If you want to combine the two you can create a RealmDinosaur class that accept the wire Dinosaur. Something like this:
public class RealmDinosaur extends RealmObject {
private String name;
private RealmList<RealmString> pictureUrls;
public RealmDinosaur(Dinosaur dino) {
// Fill Realm fields. Note that Realm doesn't support Lists
// with primitive strings yet.
// See https://realm.io/docs/java/latest/#primitive-lists
}
// getter and setters
}
realm.beginTransaction();
realm.copyToRealm(new RealmDinosaur(wireDinosaur));
realm.commitTransaction();
Short answer: no.
For me, this is one of several show-stoppers for wide adoption of Realm.
The developers of Realm don't seem to have considered real-world use-cases such as yours, where your data objects already inherit from something.
They also seem don't seem to get Android's threading requirements.
If you really want to use Realm, I think that you'll have to create another set of objects, likely in another package, that you only use with Realm. Then, you'd have to copy your data from your 'real' objects into the Realm objects.
Personally, for anything non-trivial, I'd either use the built-in SQLite, or find another database that better meets your needs.
This question already has answers here:
How does Log.wtf() differ from Log.e()?
(7 answers)
Closed 7 years ago.
I encountered the method
public static void wtf(String format, Object... args) {
Log.wtf(TAG, buildMessage(format, args));
}
public static int wtf(String tag, String msg, Throwable tr) {
throw new RuntimeException("Stub!");
}
When I was having a closer look on the Android Volley, However this method is used to log the error in the Volley By the Library Developers but is this making any other sense other than the usual ones?
I am not sure if a programmer should have such naming conventions?
It's made very clear in the API docs that WTF stands, in this case for What a Terrible Failure. You can take a page out of Dr. Evil's book and say it like this: ;).
Quoting from developer.android.com:
What a Terrible Failure: Report a condition that should never happen.
The error will always be logged at level ASSERT with the call stack.
Used to detect log like:
package com.android.volley;
import android.os.SystemClock;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/** Logging helper class. */
public class VolleyLog {
public static String TAG = "Volley";
public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
/**
* Customize the log tag for your application, so that other apps
* using Volley don't mix their logs with yours.
* <br />
* Enable the log property for your tag before starting your app:
* <br />
* {#code adb shell setprop log.tag.<tag>}
*/
public static void setTag(String tag) {
d("Changing log tag to %s", tag);
TAG = tag;
// Reinitialize the DEBUG "constant"
DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
}
public static void v(String format, Object... args) {
if (DEBUG) {
Log.v(TAG, buildMessage(format, args));
}
}
public static void d(String format, Object... args) {
Log.d(TAG, buildMessage(format, args));
}
public static void e(String format, Object... args) {
Log.e(TAG, buildMessage(format, args));
}
public static void e(Throwable tr, String format, Object... args) {
Log.e(TAG, buildMessage(format, args), tr);
}
public static void wtf(String format, Object... args) {
Log.wtf(TAG, buildMessage(format, args));
}
public static void wtf(Throwable tr, String format, Object... args) {
Log.wtf(TAG, buildMessage(format, args), tr);
}
/**
* Formats the caller's provided message and prepends useful info like
* calling thread ID and method name.
*/
private static String buildMessage(String format, Object... args) {
String msg = (args == null) ? format : String.format(Locale.US, format, args);
StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace();
String caller = "<unknown>";
// Walk up the stack looking for the first caller outside of VolleyLog.
// It will be at least two frames up, so start there.
for (int i = 2; i < trace.length; i++) {
Class<?> clazz = trace[i].getClass();
if (!clazz.equals(VolleyLog.class)) {
String callingClass = trace[i].getClassName();
callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1);
callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1);
caller = callingClass + "." + trace[i].getMethodName();
break;
}
}
return String.format(Locale.US, "[%d] %s: %s",
Thread.currentThread().getId(), caller, msg);
}
/**
* A simple event log with records containing a name, thread ID, and timestamp.
*/
static class MarkerLog {
public static final boolean ENABLED = VolleyLog.DEBUG;
/** Minimum duration from first marker to last in an marker log to warrant logging. */
private static final long MIN_DURATION_FOR_LOGGING_MS = 0;
private static class Marker {
public final String name;
public final long thread;
public final long time;
public Marker(String name, long thread, long time) {
this.name = name;
this.thread = thread;
this.time = time;
}
}
private final List<Marker> mMarkers = new ArrayList<Marker>();
private boolean mFinished = false;
/** Adds a marker to this log with the specified name. */
public synchronized void add(String name, long threadId) {
if (mFinished) {
throw new IllegalStateException("Marker added to finished log");
}
mMarkers.add(new Marker(name, threadId, SystemClock.elapsedRealtime()));
}
/**
* Closes the log, dumping it to logcat if the time difference between
* the first and last markers is greater than {#link #MIN_DURATION_FOR_LOGGING_MS}.
* #param header Header string to print above the marker log.
*/
public synchronized void finish(String header) {
mFinished = true;
long duration = getTotalDuration();
if (duration <= MIN_DURATION_FOR_LOGGING_MS) {
return;
}
long prevTime = mMarkers.get(0).time;
d("(%-4d ms) %s", duration, header);
for (Marker marker : mMarkers) {
long thisTime = marker.time;
d("(+%-4d) [%2d] %s", (thisTime - prevTime), marker.thread, marker.name);
prevTime = thisTime;
}
}
#Override
protected void finalize() throws Throwable {
// Catch requests that have been collected (and hence end-of-lifed)
// but had no debugging output printed for them.
if (!mFinished) {
finish("Request on the loose");
e("Marker log finalized without finish() - uncaught exit point for request");
}
}
/** Returns the time difference between the first and last events in this log. */
private long getTotalDuration() {
if (mMarkers.size() == 0) {
return 0;
}
long first = mMarkers.get(0).time;
long last = mMarkers.get(mMarkers.size() - 1).time;
return last - first;
}
}
}
i'm a newbie in android. In my app i create a many-to-many chat, and need to update from server a list of Messages. In order to do so, i created a service that updates every second from the server.
My problem is that i don't know how to pass data back to the application. I know that I should do it using intent and broadcast receiver, but in that I stuck with Bundle object that i have to serialize in order to pass it to the app, and it does not make sense to me, since this operation is not that efficient.
For now i'm using the ref to my application (i think it's not that good but don't know why), and after every update from server in the service i activate the application function, and updates it's fields directly. Moreover i think maybe my code will do some good for beginners as well :)
public class UpdateChatService extends Service {
private static final long DELAY_FOR_CHAT_TASK = 0;
private static final long PERIOD_FOR_CHAT_TASK = 1;
private static final TimeUnit TIME_UNIT_CHAT_TASK = TimeUnit.SECONDS;
//private Task retryTask; TODO: check this out
private ScheduledExecutorService scheduler;
private boolean timerRunning = false;
private long RETRY_TIME = 200000;
private long START_TIME = 5000;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
scheduleChatUpdate();
}
private void scheduleChatUpdate() {
BiggerGameApp app = (BiggerGameApp) getApplication();
this.scheduler = Executors.newScheduledThreadPool(3);
this.scheduler.scheduleAtFixedRate(new UpdateChatTask(app),
DELAY_FOR_CHAT_TASK, PERIOD_FOR_CHAT_TASK,
TIME_UNIT_CHAT_TASK);
timerRunning = true;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!timerRunning) {
scheduleChatUpdate();
}
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onDestroy() {
super.onDestroy();
if (scheduler != null) {
scheduler.shutdown();
}
timerRunning = false;
}
}
Here is the code of the asynchronous task the runs in the service.
Please tell me what i'm doing wrong, and how should pass data from the service to the application.
public void run() {
try {
if (this.app.getLastMsgFromServer() == null) {
this.app.setLastMsgFromServer(new Message(new Player(DEFAULT_EMAIL), "", -1));
this.app.getLastMsgFromServer().setMessageId(-1);
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(DateTime.class, new DateTimeTypeConverter())
.create();
ServerHandler serverHandler = new ServerHandler();
String jsonString = gson.toJson(this.app.getLastMsgFromServer());
// Sending player to servlet in server
String resultString = serverHandler.getResultFromServlet(jsonString, "GetListOfMessages");
if (resultString.contains("Error")) {
return;
}
// Parsing answer
JSONObject json = new JSONObject(resultString);
Status status = null;
String statusString = json.getString("status");
if (statusString == null || statusString.length() == 0)
return;
status = Status.valueOf(statusString);
if (Status.SUCCESS.equals(status)) {
ArrayList<Message> tempChat = null;
JSONArray jsonList = json.getJSONArray("data");
MyJsonParser jsonParser = new MyJsonParser();
tempChat = jsonParser.getListOfMessagesFromJson(jsonList.toString());
if (tempChat != null && tempChat.size() != 0) {
// After getting the chat from the server, it saves the last msg
// For next syncing with the server
this.app.setLastMsgFromServer(tempChat.get(LAST_MSG_INDEX));
tempChat.addAll(this.app.getChat());
if (tempChat.size() > SIZE_OF_USER_CHAT) {
tempChat = (ArrayList<Message>) tempChat.subList(0, SIZE_OF_USER_CHAT - 1);
}
this.app.setChat(tempChat);
this.app.updateViews(null);
}
}
return;
Is the Service local only (I'm going to assume "yes")?
Communication with a local-only service can be done by passing an instance of android.os.Binder back, as shown below:
public class UpdateChatService extends Service {
public static final class UpdateChat extends Binder {
UpdateChatService mInstance;
UpdateChat(UpdateChatService instance) {
mInstance = instance;
}
public static UpdateChat asUpdateChat(IBinder binder) {
if (binder instanceof UpdateChat) {
return (UpdateChat) binder;
}
return null;
}
public String pollMessage() {
// Takes a message from the list or returns null
// if the list is empty.
return mInstance.mMessages.poll();
}
public void registerDataSetObserver(DataSetObserver observer) {
mInstance.mObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mInstance.mObservable.unregisterObserver(observer);
}
}
private ScheduledExecutorService mScheduler;
private LinkedList<String> mMessages;
private DataSetObservable mObservable;
#Override
public IBinder onBind(Intent intent) {
return new UpdateChat(this);
}
#Override
public void onCreate() {
super.onCreate();
mObservable = new DataSetObservable();
mMessages = new LinkedList<String>();
mScheduler = Executors.newScheduledThreadPool(3);
mScheduler.scheduleAtFixedRate(new UpdateChatTask(), 0, 1, TimeUnit.SECONDS);
}
#Override
public void onDestroy() {
super.onDestroy();
mScheduler.shutdownNow();
mObservable.notifyInvalidated();
}
class UpdateChatTask implements Runnable {
int mN = 0;
public void run() {
// This example uses a list to keep all received messages, your requirements may vary.
mMessages.add("Message #" + (++mN));
mObservable.notifyChanged();
}
}
}
This example could be used to feed an Activity (in this case a ListActivity) like this:
public class ChattrActivity extends ListActivity implements ServiceConnection {
LinkedList<String> mMessages;
ArrayAdapter<String> mAdapter;
UpdateChat mUpdateChat;
DataSetObserver mObserver;
Runnable mNotify;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMessages = new LinkedList<String>();
mNotify = new Runnable() {
public void run() {
mAdapter.notifyDataSetChanged();
}
};
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mMessages);
getListView().setAdapter(mAdapter);
// Bind to the Service if you do not need it to persist when this Activity
// dies - otherwise you must call #startService(..) before!
bindService(new Intent(this, UpdateChatService.class), this, BIND_AUTO_CREATE);
}
/**
* #see android.app.ListActivity#onDestroy()
*/
#Override
protected void onDestroy() {
super.onDestroy();
if (mUpdateChat != null) {
mUpdateChat.unregisterDataSetObserver(mObserver);
unbindService(this);
}
}
public void onServiceConnected(ComponentName name, IBinder service) {
mUpdateChat = UpdateChat.asUpdateChat(service);
mObserver = new DataSetObserver() {
#Override
public void onChanged() {
String message;
while ((message = mUpdateChat.pollMessage()) != null) {
mMessages.add(message);
}
runOnUiThread(mNotify);
}
#Override
public void onInvalidated() {
// Service was killed - restart or handle this error somehow.
}
};
// We use a DataSetObserver to notify us when a message has been "received".
mUpdateChat.registerDataSetObserver(mObserver);
}
public void onServiceDisconnected(ComponentName name) {
mUpdateChat = null;
}
}
If you need to communicate across processes you should look into implementing an AIDL interface - but for "local" versions this pattern works just fine & doesn't involve abusing the global Application instance.
You can use a static memory shared between your service and rest of application (activities). If you do not plan to expose this service to external apps, then sharing static memory is better than serializing/deserializing data via bundles.
Bundles based approach is encouraged for components that are to be exposed to outside world. A typical app usually has just the primary activity exposed in app manifest file.
If your don't pulibc your service , the static memory and the callback function can do.
If not , you can send broadcast.
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.