I'm using IntentService for handling my messages from push notification from FCM. It works perfectly as required when the message comes one by one but when the device is not connected to the network and after when device again connected FCM send the bulk of messages at a time and at this scenario service causes some ambiguous while handling intent data which causes unexpected behavior in calling web services.
My push-notification message handler Class :
public class PushMessageHandler extends FirebaseMessagingService {
private final static String TAG = "PushMessageHandler";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
if (remoteMessage.getData() != null){
Log.d(TAG, String.valueOf(remoteMessage.getData()));
Intent notificationService = new Intent(this, NotificationService.class);
notificationService.putExtra(ResponseConstants.NOTIFICATION_FIELD,remoteMessage.getData().get(ResponseConstants.NOTIFICATION_FIELD));
notificationService.putExtra(ResponseConstants.NOTIFICATION_DATA,remoteMessage.getData().get(ResponseConstants.NOTIFICATION_DATA));
notificationService.putExtra(ResponseConstants.NOTIFICATION_TYPE,remoteMessage.getData().get(ResponseConstants.NOTIFICATION_TYPE));
try {
notificationService.putExtra(ResponseConstants.NOTIFICATION_IMAGE,remoteMessage.getData().get(ResponseConstants.NOTIFICATION_IMAGE));
notificationService.putExtra(ResponseConstants.NOTIFICATION_TITLE, remoteMessage.getData().get(ResponseConstants.NOTIFICATION_TITLE));
} catch (Exception e){
Crashlytics.logException(e);
}
try {
notificationService.putExtra(ResponseConstants.DATASETS,remoteMessage.getData().get(ResponseConstants.DATASETS));
} catch (Exception e){
Crashlytics.logException(e);
}
startService(notificationService);
} else {
Log.d(TAG, "Notification data is null");
}
}
}
And my notification handler service class :
public class NotificationService extends IntentService implements NotificationContract.View {
#Inject
public NotificationPresenter mNotificationPresenter;
private NotificationContract.Presenter mPresenter;
private static final String TAG = "NotificationService";
private Intent mIntent;
public NotificationService() {
super("NotificationService");
}
#Override
protected void onHandleIntent(#Nullable Intent intent) {
mIntent = intent;
DaggerNotificationPresenterComponent.builder()
.notificationViewModule(new NotificationViewModule(this))
.remoteDataSourceComponent(MyApplication.getInstance().providesRemoteDataSource())
.localDataSourceComponent(MyApplication.getInstance().providesLocalDataSource())
.build().inject(this);
}
#Override
public synchronized void setPresenter(NotificationContract.Presenter presenter) {
this.mPresenter = presenter;
final String notificationField = mIntent.getStringExtra(ResponseConstants.NOTIFICATION_FIELD);
Log.d(TAG, notificationField);
Handler handler = new Handler(getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
switch (notificationField.trim()){
case Constants.NOTIFICATION_FIELD_CACHEHOMEFEEDS :
mPresenter.prefetchData(Integer.parseInt(
mIntent.getStringExtra(ResponseConstants.NOTIFICATION_DATA)),
new JSONObject(mIntent.getStringExtra(ResponseConstants.DATASETS)));
break;
case Constants.NOTIFICATION_FIELD_UPDATEFEEDS :
mPresenter.getPostDetailById(Integer.parseInt(
mIntent.getStringExtra(ResponseConstants.NOTIFICATION_DATA)),
new JSONObject(mIntent.getStringExtra(ResponseConstants.DATASETS)));
break;
case Constants.NOTIFICATION_FIELD_ARTICLES :
mPresenter.getPostDetailsPostUrl(mIntent.getStringExtra(ResponseConstants.NOTIFICATION_DATA));
break;
case Constants.NOTIFICATION_FIELD_POSTDELETED :
mPresenter.deleteFeed(Integer.parseInt(
mIntent.getStringExtra(ResponseConstants.NOTIFICATION_DATA)));
break;
}
}
});
}
}
In the case of bulk push messages, I'm getting the interchangeable value of NOTIFICATION_DATA i.e the value that I'm expected when notification field is "NOTIFICATION_FIELD_CACHEHOMEFEEDS" is "post: 1234" and for field "NOTIFICATION_FIELD_ARTICLES" is "post: 'post-URL'" but I'm getting "post:1234" for filed "NOTIFICATION_FIELD_ARTICLES", the value is getting interchangeable in any sequence depends on message calling of push notification.
According to the documentation of IntentService handles the requests one by one in queue manner. Then why this happening. Is any method to handle this perfectly.
IntentService -> onHandleIntent is executed on a background thread. If you have time consuming operation you should execute it there.
If not - just use normal Service.
Now in onHandleIntent you are injecting presenter multiple times from background thread - i think you should move the injection to the constructor.
Then in onHandleIntent call your presenter methods (mPresenter.prefetchData, mPresenter.getPostDetailById etc).
Related
Well I've thinking through this problem and i need some help from community.
I want to upload image(s)(5 images) to server which is currently done in async task of app. Hence upload can be stopped when user closes the app so i want to perform this operation using IntentService.
So I have created my Intent Service to perform image upload.
public class ImageUploadService extends IntentService {
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* #param name Used to name the worker thread, important only for debugging.
*/
public ImageUploadService(String name) {super(name);}
public ImageUploadService(){
this(ImageUploadService.class.getName());
}
#Override
protected void onHandleIntent(Intent intent) {
//todo perform long running image upload operation
KbUserService kbUserService = JacksonRestRequestBuilder
.setupUploadRestService(RestUrls.BASE_URL_CUSTOMER, true,
KbUserService.class, getApplicationContext());
LinkedHashMap<String, Object> requestBody = new LinkedHashMap<>();
Call<LinkedHashMap<String,Object>> call = kbUserService.uploadFile(requestBody);
call.enqueue(new KbCallback() {
#Override
public void failure(Map<String, Object> serverErrorResponse, String genericErrorMessage) {
//todo send notification that image upload failed and also send broadcast event that image upload failed.
}
#Override
public void success(Map<String, Object> successResponse) {
Map<String, Object> modelDataFromResponseAsMap =
ClientModelUtils.getModelDataFromResponseAsMap(successResponse);
// Log.i(NAME,"Background Image upload completed for "+msg.arg1);
String fileUrl = ClientModelUtils.getString(modelDataFromResponseAsMap,
ModelConstants.UserConstants.IMAGE_URL);
//todo send notification that image upload success and send broadcast that image upload success.
// uploadFileSuccess(fileUrl, profName);
// bean.setUploadInProgress(false);
// view.uploadFileSuccess(fileUrl,profName);
}
#Override
public void networkOrUnexpectedError(String message) {
//todo send notification that image upload failed and also send broadcast event that image upload failed.
}
});
}
protected void showToast(final String msg){
//gets the main thread
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
// run this code in the main thread
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
});
}
}
So here i send a broadcast in success response to all registered activities.
I'm not able to solve the following problems,
1.Suppose when came out of app and came back to activity, how to know what are the currently uploading images?
2.How to access intent data in onHandleIntent() inside callback?, Because in intent data i have information about the imageType so when request is success i want to send an broadcast with imageUrl and imageType.
This might not be the ideal answer but I normally have broadcast receivers on both the service and the activity. This way they can communicate with each other. For example when the activity starts again I send a broadcast to the service notifying it. The service will then send a broadcast with its information.
public class SomeService extends Service {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
registerReceiver(receiver, new IntentFilter(SOME_SERVICE_ACTIONS));
}
#Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
switch (intent.getIntExtra(SERVICE_COMMAND, 0)) {
case ACTIVITY_STARTED:
sendCallback();
break;
}
};
}
I am updaiting database on each app launch, i was using IntentService before, but my activity was starting before data is updated, so data list was empty, i have rewriten it using Service(i have created my own Service acting like IntentService so i could controll thread created by the Service). As you see first of all i am saving data, and only after that i am starting my activity. But my activity is starting before data is saved anyway, any suggestions why, or how can i fix this?
Also
If you have some expirience working with ORM/Database async it would be great, i am just trying to learn how to work with database properly. Any suggestions are apreciated.
public class DatabaseWorkService extends Service {
private ServiceHandler mServiceHandler;
private DatabaseReference mDatabase;
private ConnectivityManager conMan;
private NetworkInfo netInfo;
private String currentTask;
private Intent tempIntent;
private Looper mServiceLooper;
private ResultReceiver resultReceiver;
private Context context =this;
public DatabaseWorkService delegate = null;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
#Override
public void handleMessage(Message msg) {
if(msg.getData()!=null) {
switch (msg.getData().getString(Utils.INTENT_SERVICE_INVOKE)) {
case Utils.LOAD_All_DATA: {
saveActivities();
savePersons();
savePictureData();
Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
Log.e("order", "forth");
break;
}
case Utils.READ_ACTIONS_DATA: {
readActionData();
break;
}
case Utils.READ_PERSONS_DATA: {
readPersonsData();
break;
}
}
}
stopSelf(msg.arg1);
}
}
#Override
public void onCreate() {
// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block. We also make it
// background priority so CPU-intensive work will not disrupt our UI.
HandlerThread thread = new HandlerThread("ServiceStartArguments");
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("service","service started");
Log.e("data",intent.getStringExtra(Utils.INTENT_SERVICE_INVOKE));
Bundle bundle = new Bundle();
bundle.putString(Utils.INTENT_SERVICE_INVOKE,intent.getStringExtra(Utils.INTENT_SERVICE_INVOKE));
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.setData(bundle);
mServiceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
#Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
if these quires returning any values after insertion in database, then only you have to launch activity
saveActivities();
savePersons();
savePictureData();
try below code launch your activity inside Main thread handler
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
I have asked this question here but it was marked as duplicate -
however I didn't find any solution helpful mentioned in comments.
Here, I am asking again with more details ...
I am doing a sample app (PoC) on HCE and using HostApduService as per Android user guide. I have created two apps
1) ReaderApp - acting as card reader
2) HCEApp - emulating a card
In HCEApp, I have created a class 'MyService' extending HostApduService
public class MyService extends HostApduService {
private int messageCounter;
private final String TAG = "MyService";
Intent mIntent;
#Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate");
mIntent = new Intent(this, MyActivity.class);
mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(mIntent);
}
/**
* returned bytes will be sent as response. This method runs in Main thread
* so return ASAP.
*/
#Override
public byte[] processCommandApdu(byte[] apdu, Bundle extras) {
if (selectAidApdu(apdu)) {
Log.i(TAG, "Application selected");
return getWelcomeMessage();
} else {
Log.i(TAG, "Received: " + new String(apdu));
return getNextMessage();
}
}
private byte[] getWelcomeMessage() {
return "Hello Desktop!".getBytes();
}
private byte[] getNextMessage() {
return ("Message from android: " + messageCounter++).getBytes();
}
private boolean selectAidApdu(byte[] apdu) {
if (apdu != null) {
for (byte b : apdu) {
System.out.printf("0x%02X", b);
}
}
return apdu.length >= 2 && apdu[0] == (byte) 0
&& apdu[1] == (byte) 0xa4;
}
#Override
public void onDeactivated(int reason) {
Log.i(TAG, "Deactivated: " + reason);
}
#Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
As you can see in onCreate(), I am launching MyActivity provides user to enter some information and needs to be sent back to MyService.
I think I can not use binding as 'onBind()' is declared final in HostApduService as below
#Override
public final IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
Please let me know if I am understading it correctly. Appreciate any help.
Thanks
iuq
Whether you can use onBind or not I do not know, but I recently worked with a BroadcastReceiver from which I had to start a Service. You cannot bind a Service from a BroadcastReceiver according to docs, you can only start it. I needed to send some data to the Service from my BroadcastReceiver at some later point, and since the binder techniques was not available to me, I had to find a different way to communicate with the Service, much like your case where you don't have a reference to it.
I did some research but could not find any solution, but then I remembered that you can pass intent data with the startService(intent) call. I start my Service work in onCreate instead, as onCreate is only called once when the Service is created.
In your Activity
public void sendDataToService(){
Intent intent = new Intent(context, MyService.class);
intent.putExtra("message", SOME_DATA);
context.startService(intent);
}
In your Service
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Check if intent has extras
if(intent.getExtras() != null){
// Get message
int message = intent.getExtras().getInt("message");
}
return START_NOT_STICKY;
}
This may be some sort what of a hack since "startService" does not sound like it should be used to send messages, and am not sure if this is exactly what you need, but it worked for me, so I hope it works for you. Cheers
Edit: BTW. I use it to tell a LocationService that a particular activity no longer want location updates.
I ended up taking a different approach to solving this same problem. When I bind to my HostApduService subclass, I grab a handle to the Messenger interface returned by the HostApduService onBind implementation.
Here's some sample code. This would all go in your activity implementation (calling it MyActivity here, communicating with MyHostApduServiceSubclass). Here's what MyActivity would need to include:
private Messenger mAPDUMessenger;
...
#Override
protected void onStart() {
super.onStart();
Context context = getApplicationContext();
Intent apduIntent = new Intent(montext, ContactlessApduService.class);
context.bindService(apduIntent, mAPDUConnection, Context.BIND_AUTO_CREATE);
}
...
private ServiceConnection mAPDUConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
// The HostApduService has a final override on the onBind() service method that returns
// an IMessageHandler interface that we can grab and use to send messages back to the
// terminal - would be better to get a handle to the running instance of the service so
// that we could make use of the HostApduService#sendResponseApdu public method
mAPDUMessenger = new Messenger(service);
registerAPDUMessengerIntentFilters();
// ^ This method sets up my handlers for local broadcast messages my BroadcastReceiver processes.
}
...
}
...
private void registerAPDUMessengerIntentFilters() {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(MyActivity.this);
IntentFilter intentFilter = new IntentFilter(MyHostApduServiceSubclass.ACTION_PPSE_APDU_SELECT);
lbm.registerReceiver(apduMessageBroadcastReceiver, intentFilter);
}
...
BroadcastReceiver apduMessageBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(MyHostApduServiceSubclass.ACTION_PPSE_APDU_SELECT)) {
sendResponseApdu(MyActivity.PPSE_APDU_SELECT_RESPONSE_BYTES);
}
}
};
...
public final void sendResponseApdu(byte[] responseApdu) {
Message responseMsg = Message.obtain(null, MyHostApduServiceSubclass.MSG_RESPONSE_APDU);
// ^ Note here that because MSG_RESPONSE_APDU is the message type
// defined in the abstract HostApduService class, I had to override
// the definition in my subclass to expose it for use from MyActivity.
// Same with the KEY_DATA constant value below.
Bundle dataBundle = new Bundle();
dataBundle.putByteArray(MyHostApduServiceSubclass.KEY_DATA, responseApdu);
responseMsg.setData(dataBundle);
try {
mAPDUMessenger.send(responseMsg);
} catch (RemoteException e) {
// Do something with the failed message
}
}
And then your HostApduService subclass would just need to send a broadcast to your activity indicating what APDU command was received. Here is what would need to be included in MyHostApduServiceSubclass:
public static final String ACTION_PPSE_APDU_SELECT = "ACTION_PPSE_APDU_SELECT";
// Abstract super class constant overrides
public static final String KEY_DATA = "data";
public static final int MSG_RESPONSE_APDU = 1;
#Override
public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
Context context = getApplicationContext();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
if (Arrays.equals(MyHostApduServiceSubclass.PPSE_APDU_SELECT_BYTES, commandApdu)) {
lbm.sendBroadcast(new Intent(ACTION_PPSE_APDU_SELECT));
}
return null;
// ^ Note the need to return null so that the other end waits for the
// activity to send the response via the Messenger handle
}
I need to call the Google activity recognition service through a service (not activity) and run it in the background, of course when the user starts the app, which has an activity (But the service does not called directly from activity).
Therefore I have created a service class (ActivitySensor) and another class (ActivityRecognitionScan).
When I install the app on my Galaxy Nexus S device, the service starts calling onCreate and onDestroy automatically. Even without doing anything in the GUI
It is very strange behaviour. Does anybody has the same experience or solution for it?
I mean I get something as follows in the debug console:
Activity-Logging --- onCreate
Activity-Logging --- onDestroy
Activity-Logging --- onCreate
Activity-Logging --- onDestroy
Activity-Logging --- onCreate
Activity-Logging --- onDestroy
...
Here are my two classes:
public class ActivitySensor extends IntentService {
private ActivityRecognitionScan myascan;
private Intent inIntent;
private static long ACTIVITY_LOG_INTERVAL = 30000L;
private static JsonEncodeDecode jsonencoder = new JsonEncodeDecode();
public ActivitySensor() {
super("ActivitySensor");
}
#Override
public void onCreate(){
super.onCreate();
Log.d("Activity-Logging", "--- onCreate");
try {
myascan = new ActivityRecognitionScan(getApplicationContext());
myascan.startActivityRecognitionScan();
} catch (Exception e) {
Log.e("[Activity-Logging]","----------Error:"+e.getLocalizedMessage());
e.printStackTrace();
}
}
#Override
public void readSensor() {
// Log.e("Activity-Logging", "ActivityRecognitionResult.hasResult: "+String.valueOf(ActivityRecognitionResult.hasResult(inIntent)));
if (ActivityRecognitionResult.hasResult(inIntent)) {
ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(inIntent);
DetectedActivity activity = result.getMostProbableActivity();
final int type = activity.getType();
String strType = new String();
switch(type){
case DetectedActivity.IN_VEHICLE:
strType = "invehicle";
break;
case DetectedActivity.ON_BICYCLE:
strType ="onbicycle";
break;
case DetectedActivity.ON_FOOT:
strType = "onfoot";
break;
case DetectedActivity.STILL:
strType = "still";
break;
case DetectedActivity.TILTING:
strType ="tilting";
break;
case DetectedActivity.UNKNOWN:
strType ="unknown";
break;
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
Editor edt = prefs.edit();
String previousActv = prefs.getString("PREVIOUS_ACTIVIY","");
long previousDate = prefs.getLong("PREVIOUS_DATE", 0);
if (previousActv.length()==0){ // nothing was in the string and it is the first time just initialize
previousActv = strType;
previousDate = new Date().getTime();
// Log.e("-----FIRST TIME: type:", previousActv+" date:"+String.valueOf(previousDate));
edt.putString("PREVIOUS_ACTIVIY", strType);
edt.putLong("PREVIOUS_DATE", previousDate);
edt.commit();
}else {
if (!strType.equalsIgnoreCase(previousActv)){
Date readablePrevDate = new Date(previousDate);
Date nowDate = new Date();
String jsonstr = jsonencoder.EncodeActivity("Activity", readablePrevDate, nowDate, strType, activity.getConfidence());
// Log.e("[Activity-Logging] ----->",jsonstr);
edt.putString("PREVIOUS_ACTIVIY", strType);
edt.putLong("PREVIOUS_DATE", nowDate.getTime());
edt.commit();
DataAcquisitor.dataBuff.add(jsonstr);
}
}
}
}
#Override
protected void onHandleIntent(Intent intent) {
Log.d("Activity-Logging", "--- onHandleIntent"+ "---"+intent.getAction());
intent.putExtra("LOG_INTERVAL",ACTIVITY_LOG_INTERVAL );
intent.putExtra("STOP",false);
inIntent = intent;
readSensor();
}
#Override
public void onDestroy(){
Log.d("Activity-Logging", "--- onDestroy");
myascan.stopActivityRecognitionScan();
myascan=null;
//super.onDestroy();
}
}
This is the class that calls the Google Activity Recognition Service:
ActivityRecognitionScan implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener {
private Context ctx;
private static final String TAG = "ActivityRecognition";
private static ActivityRecognitionClient actrecClient;
private static PendingIntent callbackIntent;
private long ACTIVITY_LOG_INTERVAL=30000;
public ActivityRecognitionScan(Context context) {
ctx=context;
}
public void startActivityRecognitionScan(){
int resp = GooglePlayServicesUtil.isGooglePlayServicesAvailable(ctx);
if(resp == ConnectionResult.SUCCESS){
actrecClient = new ActivityRecognitionClient(ctx, this, this);
if (!actrecClient.isConnected()){
actrecClient.connect();
} else{
Log.e("ActivityRecognitionScan"," ---Activity recognition client is already connected");
}
}else{
Log.e("[Activity-Logging]", "Google Play Service hasn't installed");
}
}
public void stopActivityRecognitionScan(){
try{
if (actrecClient.isConnected() || actrecClient.isConnecting() ){
actrecClient.removeActivityUpdates(callbackIntent);
actrecClient.disconnect();
}
} catch (Exception e){
e.printStackTrace();
}
}
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.e("[ActivityRecognitionScan]", "Connection Failed");
}
#Override
public void onConnected(Bundle connectionHint) {
try{
Intent intent = new Intent(ctx, ActivitySensor.class);
Bundle bundle = intent.getExtras();
callbackIntent = PendingIntent.getService(ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
long interval = 5000;
if ( null!= bundle && bundle.containsKey("LOG_INTERVAL") ){
interval = bundle.getLong("LOG_INTERVAL");
}
actrecClient.requestActivityUpdates(interval, callbackIntent);
actrecClient.disconnect();
}catch(Exception ex){
Log.e("[Activity-Logging]","Error in requesting Activity update "+ex.getMessage());
ex.printStackTrace();
}
}
#Override
public void onDisconnected() {
callbackIntent.cancel();
actrecClient = null;
Log.e("[ActivityRecognitionScan]","---onDisconnected");
}
}
IntentService automatically stops itself on completion of onHandleIntent as per the source code (see ServiceHandler.handleMessage()) as per the description of an IntentService:
Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
Use a Service if you want it to run continuously in the background.
You have 2 issues with your code that is causing the problem you are experiencing.
When activity is detected, the pending intent that is called calls (and creates, since it is an IntentService) ActivitySensor. The onCreate will connect another ActivityRecognitionClient, which is unnecessary. This causes another activity to be detected which causes your logging loop.
You should separate the creation of the ActivityRecognitionClient from the handling of the detected activity. You don't need to keep recreating it as subsequent detections will use the same PendingIntent. This will prevent the logging loop.
I am implementing an application which is kind of VOIP application. So my application is kind of network application. Now I want to implement two part in my application, one is GUI part and one is network part. My GUI part will just contain activities and handling of user interaction. My Network part should handle all network related activities like handling incoming network data and sending data to network based on GUI interaction. Now whenever there is any incoming data, I want to update some activity whose reference is not there in Network module. So what could be the best way to update activity from some other class? In my case some other class is my Network class. So in short I would like to ask that what should be the architecture in such scenario? i.e. Network part should run in separate thread and from there it should update GUI?
Depending on the type/size of data you need to send to the activity, you can use one of a number of options.
Use one of the methods described here.
Use a BroadcastReceiver: register it in the Activity and then fire off matching Intents in the Service that handles the networking code.
Make your Activity bind to your Service and then pass in a Handler that you send Messages to.
I have written apps like this, and I prefer the Handler method. In fact I have written an Abstract Activity class to do all the hard work and simply extend it in any activity that want to be notified of a change.
To Use the following code, just get your Activity to extend UpdatableActivity and override the dataUpdated() method. This method is called when your Service notifies the handler that data has been updated. In the Service code put your code to do an update in the update() method (Or modify to call your existing code). This allows an activity to call this.updateService() to force an update. The service can call the sendMessageToUI() method to notify all interested activities that the data has been updated.
Here is what the abstract activity looks like:
public abstract class UpdatableActivity extends Activity {
public static final String TAG = "UpdatableActivity (Abstract)";
private final Messenger mMessenger = new Messenger(new IncomingHandler());
private Messenger mService = null;
private boolean mIsBound;
protected class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
if (Constants.LOG_DEBUG) Log.d(TAG, "Service has notified us of an update: ");
switch (msg.arg1) {
case UpdateService.MSG_DATA_UPDATED:
dataUpdated();
break;
default: super.handleMessage(msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
try {
Message msg = Message.obtain(null, UpdateService.MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
// In this case the service has crashed before we could even do anything with it
}
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been unexpectedly disconnected - process crashed.
mService = null;
}
};
/**Override this method in you acctivity to handle the update */
public abstract void dataUpdated();
void doBindService() {
if (Constants.LOG_DEBUG) Log.d(TAG, "Binding to service...");
bindService(new Intent(this, UpdateService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// If we have received the service, and hence registered with it, then now is the time to unregister.
if (mService != null) {
try {
Message msg = Message.obtain(null, UpdateService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
// There is nothing special we need to do if the service has crashed.
}
}
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
public void updateService() {
if (Constants.LOG_DEBUG) Log.d(TAG,"Updating Service...");
if (mIsBound) {
if (mService != null) {
try {
Message msg = Message.obtain(null, UpdateService.MSG_SET_INT_VALUE, UpdateService.MSG_DO_UPDATE, 0);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
if (Constants.LOG_ERROR) Log.e(TAG,Log.getStackTraceString(e));
}
}
} else {
if (Constants.LOG_DEBUG) Log.d(TAG, "Fail - service not bound!");
}
}
pu
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.doBindService();
}
#Override
protected void onDestroy() {
super.onDestroy();
try {
doUnbindService();
} catch (Throwable t) {
if (Constants.LOG_ERROR) Log.e(TAG, "Failed to unbind from the service", t);
}
}
}
And here is what the Service looks Like:
public class UpdateService extends Service {
public static final String TAG = "UpdateService";
public static final int MSG_DATA_UPDATED = 0;
public static final int MSG_REGISTER_CLIENT = 1;
public static final int MSG_UNREGISTER_CLIENT = 2;
public static final int MSG_DO_UPDATE = 3;
public static final int MSG_SET_INT_VALUE = 4;
private static boolean isRunning = false;
private Handler handler = new IncomingHandler();
private final Messenger mMessenger = new Messenger(handler);
private ArrayList<Messenger> mClients = new ArrayList<Messenger>(); // Keeps track of all current registered clients.
#Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
class IncomingHandler extends Handler { // Handler of incoming messages from clients.
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
case MSG_SET_INT_VALUE:
switch (msg.arg1) {
case MSG_DO_UPDATE:
if (Constants.LOG_DEBUG) Log.d(TAG,"UI has asked to update");
update();
break;
}
break;
default:
super.handleMessage(msg);
}
}
}
private void sendMessageToUI() {
if (Constants.LOG_DEBUG) Log.d(TAG, "Notifying "+mClients.size()+" UI clients that an update was completed");
for (int i=mClients.size()-1; i>=0; i--) {
try {
// Send data as an Integer
mClients.get(i).send(Message.obtain(null, MSG_SET_INT_VALUE, MSG_DATA_UPDATED, 0));
} catch (RemoteException e) {
// The client is dead. Remove it from the list; we are going through the list from back to front so this is safe to do inside the loop.
mClients.remove(i);
}
}
}
public static boolean isRunning()
{
return isRunning;
}
#Override
public void onCreate() {
super.onCreate();
isRunning = true;
if (Constants.LOG_DEBUG) Log.d(TAG, "Service Started");
update();
}
#Override
public void onDestroy() {
if (Constants.LOG_DEBUG) Log.d(TAG, "Service Destroyed");
isRunning = false;
}
private void update() {
/**Your code to do an update goes here */
}
}
Yes, personally i think that the network and UI should be in separate threads. The way I tend to communicate between the two, which is probably not the recommended proper way, but it works for me, is to create a global variable in your application class. hope this helps a little
I would directly post to the main UI thread,
Handler mHandler = new Handler(Looper.getMainLooper());
mHandler.post(new Runnable() {...});