Android - Best practice for retrying IntentService - android

My Android app sends a load of files to Amazon S3. Each file URI is passed in separate calls to IntentService which performs the upload.
However, I'm wondering what is the best way to handle failures... Should I detect the failure with my IntentService's onHandleIntent() method and retry within that same method, OR should I allow the failure to be handled outside of the method (and if so, how?)?
I'm personally leaning towards the first suggestion as I would prefer any file to be successfully uploaded before subsequent files are attempted to be uploaded, but I am not sure if detecting errors and performing retries within the onHandleIntent() method is good practice(?).

This is a very nice question. I was asked this in one interview and i had failed to answer it. But i will try and answer it here after some searching for the answer.
Step-1: You start an IntentService. You can start an IntentService either from an Activity or a Fragment.
/* Starting Download Service */
DownloadResultReceiver mReceiver = new DownloadResultReceiver(new Handler());
mReceiver.setReceiver(this);
Intent intent = new Intent(Intent.ACTION_SYNC, null, this, DownloadService.class);
/* Send optional extras to Download IntentService */
intent.putExtra("url", url);
intent.putExtra("receiver", mReceiver);
intent.putExtra("requestId", 101);
startService(intent);
Step-2: Make the class that extends IntentService.
public class DownloadService extends IntentService {
public static final int STATUS_RUNNING = 0;
public static final int STATUS_FINISHED = 1;
public static final int STATUS_ERROR = 2;
private static final String TAG = "DownloadService";
public DownloadService() {
super(DownloadService.class.getName());
}
#Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "Service Started!");
final ResultReceiver receiver = intent.getParcelableExtra("receiver");
String url = intent.getStringExtra("url");
Bundle bundle = new Bundle();
if (!TextUtils.isEmpty(url)) {
/* Update UI: Download Service is Running */
receiver.send(STATUS_RUNNING, Bundle.EMPTY);
try {
String[] results = downloadData(url);//make your network call here and get the data or download a file.
/* Sending result back to activity */
if (null != results && results.length > 0) {
bundle.putStringArray("result", results);
receiver.send(STATUS_FINISHED, bundle);
}
} catch (Exception e) {
/* Sending error message back to activity */
bundle.putString(Intent.EXTRA_TEXT, e.toString());
receiver.send(STATUS_ERROR, bundle);
}
}
Log.d(TAG, "Service Stopping!");
this.stopSelf();
}
}
Step-3: To receive results back from IntentService, we can use subclass of ResultReciever. Once results are sent from Service the onReceiveResult() method will be called. Your activity handles this response and fetches the results from the Bundle. Once results are recieved, accordingly the activity instance updates the UI.
public class DownloadResultReceiver extends ResultReceiver {
private Receiver mReceiver;
public DownloadResultReceiver(Handler handler) {
super(handler);
}
public void setReceiver(Receiver receiver) {
mReceiver = receiver;
}
public interface Receiver {
public void onReceiveResult(int resultCode, Bundle resultData);
}
#Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (mReceiver != null) {
mReceiver.onReceiveResult(resultCode, resultData);
}
}
}
Step-4: In your MainActivity:
#Override
public void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case DownloadService.STATUS_RUNNING:
//progress bar visible.
break;
case DownloadService.STATUS_FINISHED:
/* Hide progress & extract result from bundle */
/* Update ListView with result */
break;
case DownloadService.STATUS_ERROR:
/* Handle the error */
String error = resultData.getString(Intent.EXTRA_TEXT);
Toast.makeText(this, error, Toast.LENGTH_LONG).show();
/*It is here, i think, that you can again check (eg your net connection) and call the IntentService to restart fetching of data from the network. */
break;
}
}
I hope the above answer helps you. Any suggestions to improve the answer are most welcome. Thanks.

Related

Android perform ImageUpload in background and access intent data in callback

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;
}
};
}

is it possible to quit looper in onReceive method in BroadcastReceiver

Using the following code and when onReceive is fired,am getting the following error
Error receiving broadcast Intent { act=com.sample.service.ReminderActivityService flg=0x10 (has extras) }
in com.sample.common.UserActivity$1#41c2b4b0
The problem is this statement Looper.myLooper().quit();
How do I terminate my looper after receiving the broadcast in the code below?
public class UserActivity extends Thread implements
ConnectionCallbacks, OnConnectionFailedListener {
private String TAG;
// Constants that define the activity detection interval
public static final int MILLISECONDS_PER_SECOND = 1000;
public static final int DETECTION_INTERVAL_SECONDS = 30;
public static final int DETECTION_INTERVAL_MILLISECONDS = MILLISECONDS_PER_SECOND * DETECTION_INTERVAL_SECONDS;
IntentService is;
onActivityGot mCallback;
Handler mHandler;
Context mContext;
BroadcastReceiver br;
/*
* Store the PendingIntent used to send activity recognition events
* back to the app
*/
private PendingIntent mActivityRecognitionPendingIntent;
// Store the current activity recognition client
private ActivityRecognitionClient mActivityRecognitionClient;
public UserActivity(UserActivity.onActivityGot ints) {
is = (IntentService) ints;
mContext = is.getApplicationContext();
mHandler = new Handler();
TAG = this.getClass().getSimpleName();
// This makes sure that the container service has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (UserActivity.onActivityGot) ints;
} catch (ClassCastException e) {
throw new ClassCastException(ints.toString()
+ " must implement UserActivity.onActivityGot");
}
Log.i(TAG, "UserActivity constractor fired in activity");
}
#Override
public void run() {
if (servicesConnected()) {
Looper.prepare();
Log.i(TAG, "servicesConnected fired in activity");
/*
* Instantiate a new activity recognition client. Since the
* parent Activity implements the connection listener and
* connection failure listener, the constructor uses "this"
* to specify the values of those parameters.
*/
mActivityRecognitionClient =
new ActivityRecognitionClient(mContext, this, this);
// connect to the service
mActivityRecognitionClient.connect();
br = new BroadcastReceiver() {
#Override
public void onReceive(Context c, Intent i) {
//call calback with data
mCallback.activityKnown(i);
mActivityRecognitionClient.removeActivityUpdates(mActivityRecognitionPendingIntent);
mActivityRecognitionClient.disconnect();
mContext.unregisterReceiver(br);
Looper.myLooper().quit();
}
};
mContext.registerReceiver(br, new IntentFilter("com.sample.service.ReminderActivityService"));
Looper.loop();
}
}
#Override
public void onConnected(Bundle dataBundle) {
Log.i(TAG, "onConnected fired");
/*
* Create the PendingIntent that Location Services uses
* to send activity recognition updates back to this app.
*/
Intent intent = new Intent(
mContext, ReminderActivityService.class);
/*
* Return a PendingIntent that starts the IntentService.
*/
mActivityRecognitionPendingIntent =
PendingIntent.getService(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
/*
* Request activity recognition updates using the preset
* detection interval and PendingIntent. This call is
* synchronous.
*/
mActivityRecognitionClient.requestActivityUpdates(
DETECTION_INTERVAL_MILLISECONDS,
mActivityRecognitionPendingIntent);
}
#Override
public void onDisconnected() {
// Delete the client
mActivityRecognitionClient = null;
Looper.myLooper().quit();
Log.i(TAG, "onDisconnected fired");
}
#Override
public void onConnectionFailed(ConnectionResult cr) {
mHandler.post(new UiToastCommunicaton(mContext,
is.getResources().getString(R.string.action_connfailed)));
mCallback.activityFail();
Looper.myLooper().quit();
Log.i(TAG, "onConnectionFailed fired");
}
private boolean servicesConnected() {
// Check that Google Play services is available
int resultCode =
GooglePlayServicesUtil.
isGooglePlayServicesAvailable(is.getBaseContext());
if (ConnectionResult.SUCCESS == resultCode) {// If Google Play services is available
// In debug mode, log the status
Log.d("Activity Recognition",
"Google Play services is available.");
// Continue
return true;
} else {// Google Play services was not available for some reason
mHandler.post(new UiToastCommunicaton(mContext,
is.getResources().getString(R.string.gpserv_notfound)));
return false;
}
}
public interface onActivityGot {
public void activityKnown(Intent i);
public void activityFail();
}
}
found a way by storing a handle to the looper in a static variable. view below.
declare the variable
public static Handler looperHandle;
set the variable after preparing looper
Looper.prepare();
looperHandle = new Handler();
since i had instantiated the class in an object i just called
object.looperHandle.getLooper().quit();
am not comfortable with this solution because of using a static variable.
if someone has a better solution please post it here.

How to communicate with HostApduService from an Activity

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
}

Android service calling onDestroy before workerthread ends

I have a service that I'm using to send SOAP Webservice calls. Everything is working perfectly and it never crashes, but I kinda think it should.
My problem is that when I have long running queries (10-50 sec.) onDestroy() is called before my workerthread is done (and I call stopSelfResult). Could it be that System.out.println isn't executed right away/out of sync (cached) in the LogCat window?
The is how a start the service through QueryBase class:
QueryBase someService = new QueryBase(myActivity);
someService.execute(...);
My QueryBase Class
public class QueryBase {
private WeakReference<Activity> currentActivity = null;
private static class ResponseHandler extends Handler {
private QueryBase mQueryBase;
public ResponseHandler(QueryBase vQueryBase) {
mQueryBase = vQueryBase;
};
public void handleMessage(Message message) {
Bundle extras = message.getData();
mQueryBase.handleResult(message.arg1,message.arg2,extras.getInt("FRAMEID"),extras.getString("RESPONSE"));
mQueryBase=null;
};
};
public QueryBase(Activity vActivity) {
currentActivity = new WeakReference<Activity>(vActivity);
}
/***************************************************************************
* Start the service
**************************************************************************/
public boolean execute(Activity vActivity, int cmdID, int frameID, String serverAddress, int requestType, String request) {
// Valid activity
if (vActivity==null) return false;
// Test to see if network is connected
if (!isOnline(vActivity)) return false;
Intent webService = new Intent(vActivity, WebService.class);
final ResponseHandler responseHD = new ResponseHandler(this);
Messenger messenger = new Messenger(responseHD);
webService.putExtra("QUERYRESULT_MESSENGER",messenger);
webService.putExtra("CMDID", cmdID);
webService.putExtra("FRAMEID",frameID);
webService.putExtra("SERVER_ADDRESS",serverAddress);
webService.putExtra("REQUEST_TYPE",requestType);
webService.putExtra("REQUEST",request);
vActivity.startService(webService);
return true;
}
/***************************************************************************
* Is my Android connected?
**************************************************************************/
private Boolean isOnline(Activity vActivity) {
ConnectivityManager connMgr = (ConnectivityManager) vActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) return true;
else return false;
}
/***************************************************************************
* Get current Activity
**************************************************************************/
public Activity getCurrentActivity() {
Activity ac = currentActivity.get();
if (ac!=null) {
if ((ac.isFinishing()) || (ac.activityDestroyed)) {
return null;
};
}
return ac;
};
/***************************************************************************
* XML result from webservice
**************************************************************************/
public void handleResult(int resultCode, int cmdID, int frameID, String response) {
System.out.println("DEFAULT HANDLER: ResultCode: " + resultCode);
};
}
My WebService Class
public class WebService extends Service {
public static final int WS_RT_BLOOSOAP = 0;
public static final int WS_RT_RSS = 1;
public static final int WS_RESULT_OK = 0;
public static final int WS_RESULT_UNABLE_TO_CONNECT = 2;
public static final int WS_RESULT_INVALID_REQUEST = 3;
public static final int WS_RESULT_UNKNOWN_ERROR = 999;
static private SparseBooleanArray workList=null; // Only one job with the same frameID is allowed to run
#Override
public void onCreate() {
System.out.println("#### WebService onCreate");
if (workList==null) workList = new SparseBooleanArray();
}
#Override
public void onDestroy() {
System.out.println("#### WebService onDestroy");
}
/***************************************************************************
* Start working
**************************************************************************/
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("WebService Start ID=" + startId);
final int currentID = startId;
final Intent currentIntent = intent;
Runnable workerRunnable = new Runnable() {
public void run() {
System.out.println("WebService Thread Start - ID=" + currentID);
int resultCode;
Bundle responseExtras = new Bundle();
resultCode = serverRequest(currentIntent,responseExtras);
sendResponse(currentIntent,resultCode,responseExtras);
System.out.println("WebService Thread End - ID=" + currentID);
Bundle extras = currentIntent.getExtras();
if (extras != null) {
int frameID = extras.getInt("FRAMEID");
System.out.println(">>>>>>> PUT FALSE " + frameID);
workList.put(frameID, false);
};
stopSelfResult(currentID);
}
};
if (intent!=null) {
Bundle extras = intent.getExtras();
if (extras != null) {
int frameID = extras.getInt("FRAMEID");
Boolean found = workList.get(frameID,false);
if (!found) {
System.out.println(">>>>>>> PUT TRUE FRAMEID=" + frameID);
workList.put(frameID, true);
Thread workerThread = new Thread(workerRunnable);
workerThread.start();
} else {
System.out.println(">>>>>>> Allready running FRAMEID=" + frameID);
}
};
};
return Service.START_STICKY;
};
/***************************************************************************
* No binding
**************************************************************************/
#Override
public IBinder onBind(Intent intent) {
return null;
}
/***************************************************************************
* Send webservice request and return result in responseExtras
**************************************************************************/
private int serverRequest(Intent intent, Bundle responseExtras) {
...
};
/***************************************************************************
* Send response back to service caller using Messenger.send()
**************************************************************************/
private boolean sendResponse(Intent intent, int resultCode, Bundle responseExtras) {
...
};
Your service is stopped if you call stopSelfResult() with the latest startId. So if the service gets started with an intent for startId=1 and another intent with startId=2 and the second is finished before the first, you call stopSelfResult(2) before you finished for startId=1. The service gets destroyed immediately if you call stopSelfResult() with the latest startId and no other intents are pending.
Hold the latest startId. Add all startIds you wish to process in an array (e.g. List<Integer> runningStartIds) and remove them when you've finished processing them. After removing on finishing, compare the current startId with the latest one and do not call stopSelfResult() if runningStartIds is not empty. So you will end up calling stopSelfResult() only for the latest startId, when all intents were processed and no more intents are pending.
Should work, although I haven't posted an example.
.:EDIT:.
Explenation:
The next Intent may come in as fast as you return from onStartCommand() regardless of what you're doing in the background.
.:EDIT:.
Not an Improvement(Improvement:
Thinking about that, in fact you only have to keep the mLastStartId. Just skip calling stopSelfResult() until the finished startId matches mLastStartId.)
Unfortunately, it always can be happen. Actually, android application components' life cycle aren't synchronized w/ any type of worker threads as default.
So, you may need to check the status of Service manually, for example you can have one boolean flag to indicate if a service is working or not. Another handy approach is using IntentService instead of using normal service, it handles worker thread and life-cycle features by itself.
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService ");
}
#Override
protected void onHandleIntent(Intent intent) {
// This callback-method is called inside worker thread,
// so you can do some long-time network job here.
SystemClock.sleep(30000); // 30 seconds
// In this timing the service will be stopped automatically.
}
}

Wait for executed AsyncTask with ProgressDialog

I have a method public void writeEntry(Activity ctx, Entry entry) which get some data and have to call a native method, which takes longer to finish.
So I created an AsyncTask which handles the ProgressDialog and the native method. It works great in an own Activity to test it, in that Activity I used a callback interface and so on.
In my case I have the above described method and have to execute the AsyncTask. The executing can't be in that method because it doesn't halt the further execution.
I need the result from the native method before I can continue with the execution.
Is there any possibility to wait for the AsyncTask til it is finished? The method wait() isn't an option because the UI Thread will wait, too and so the sense of a ProgressDialog will be lost.
Can I use the method runOnUiThread() from the given parameters or is the only solution to start an own Activity?
so I will try to explain as much as I can
Start your heavy process inside an AsyncTask, but whatever code you want to execute after completion of AsyncTask put it in a separate public method. Now once you finish with your heavy process call that separately created method in onPostExecute().
So psuuedo code will look like this,
class main extends Activity {
class Something extends AsyncTask<String, Integer, String> {
protected void onPreExecute() {
// Start your progress bar...
}
protected String doInBackground(String... params) {
// Do your heavy stuff...
return null;
}
protected void onPostExecute(String result) {
// close your progress dialog and than call method which has
// code you are wishing to execute after AsyncTask.
}
}
}
Hope this will help,
Good Luck!
My first solution was to use callback methods with an interface implementation see the example https://stackoverflow.com/a/6396376/390177.
After chatting a while in the Android chat I heard that there is a more praticable solution.
You can use of an IntentService in combination with a PendingIntent.
The communication is realized with Intent's.
If you want to use a ProgressDialog, you need an own Activity for it, which register for example a BroadcastReciever and the IntentService sends it actual status per Broadcast.
But lets start now.
First we create the Activity, which contains the ProgressDialog and a registered BroadcastReceiver. The BroadcastReceiver listen for messages about updating and finishing the dialog.
For the Activity we needs the layout ...
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:background="#80000000">
</LinearLayout>
... and the related code:
public class ProgressActivity extends Activity {
/**
* ProgressDialog which is shown
*/
private ProgressDialog progessDialog_g;
/**
* Instance of the BroadcastReceiver
*/
private BroadcastReceiver receiver_g;
/**
* Identifier for the different settings of the ProgressDialog
*/
public static final String PROGRESS_DIALOG_BOOL_HORIZONTAL_BAR = "pbar_horizontal_bar";
public static final String PROGRESS_DIALOG_BOOL_CANCELABLE = "pbar_horizontal_cancelable";
public static final String PROGRESS_DIALOG_STR_MESSAGE = "pbar_message";
public static final String PROGRESS_DIALOG_INT_MAX = "pbar_max_bar";
public static final String PROGRESS_DIALOG_INT_VALUE = "pbar_value";
protected static final int PROGRESS_DIALOG_INT_MAX_VALUE = 100;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.progress);
progessDialog_g = new ProgressDialog(this);
// Reads and sets the settings for the ProgressDialog
Intent i = getIntent();
progessDialog_g.setCancelable(i.getBooleanExtra(
PROGRESS_DIALOG_BOOL_CANCELABLE, false));
if (i.getBooleanExtra(
PROGRESS_DIALOG_BOOL_HORIZONTAL_BAR, false)) {
progessDialog_g.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
} else {
progessDialog_g.setProgressStyle(ProgressDialog.STYLE_SPINNER);
}
progessDialog_g
.setMessage(i
.getStringExtra(PROGRESS_DIALOG_STR_MESSAGE));
progessDialog_g.setMax(i.getIntExtra(
PROGRESS_DIALOG_INT_MAX, 100));
// Create the IntentFilter for the different broadcast messages
IntentFilter iFilter =
new IntentFilter(
ExampleProgressService.PROGRESS_DIALOG_BROADCAST_INIT);
iFilter.addAction(ExampleProgressService.PROGRESS_DIALOG_BROADCAST_UPDATE);
iFilter.addAction(ExampleProgressService.PROGRESS_DIALOG_BROADCAST_FINISH);
// Creates the BroadcastReceiver
receiver_g = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent){
Log.d(DefaultPreferences.DEBUG_PREFIX + "ProgressActivity",
intent.getAction());
if (ExampleProgressService.PROGRESS_DIALOG_BROADCAST_INIT
.equals(intent.getAction())) {
// Sets the ProgressDialog style
if (intent
.getBooleanExtra(
PROGRESS_DIALOG_BOOL_HORIZONTAL_BAR,
false)) {
progessDialog_g
.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
} else {
progessDialog_g
.setProgressStyle(ProgressDialog.STYLE_SPINNER);
}
// Shows the ProgressDialog
progessDialog_g.show();
} else if (ExampleProgressService.PROGRESS_DIALOG_BROADCAST_UPDATE
.equals(intent.getAction())) {
// Updates the ProgressDialog
int value =
intent.getIntExtra(
PROGRESS_DIALOG_INT_VALUE,
-1);
if (value != -1) {
progessDialog_g.setProgress(value);
}
} else if (ExampleProgressService.PROGRESS_DIALOG_BROADCAST_FINISH
.equals(intent.getAction())) {
// Finishs the ProgressDialog
progessDialog_g.cancel();
finish();
}
}
};
// Registers the BroadcastReceiver
registerReceiver(receiver_g, iFilter);
}
#Override
protected void onDestroy(){
unregisterReceiver(receiver_g);
super.onDestroy();
}
}
Now we want to use the Activity, so lets start with calling it:
final Intent i = new Intent(parentActivity, <packages>.ProgressActivity);
i.putExtra(ProgressActivity.PROGRESS_DIALOG_BOOL_CANCELABLE, cancelable_g);
i.putExtra(ProgressActivity.PROGRESS_DIALOG_BOOL_HORIZONTAL_BAR, showProgress_g);
i.putExtra(ProgressActivity.PROGRESS_DIALOG_STR_MESSAGE, message_g);
i.putExtra(ProgressActivity.PROGRESS_DIALOG_INT_MAX, ProgressActivity.PROGRESS_DIALOG_INT_MAX_VALUE);
parentActivity.startActivity(i);
So we habe a running ProgressActivity, which waits for different broadcasts. But first we need the IntentService, which sends the broadcasts.
So lets go:
public class ExampleProgressService extends IntentService {
/**
* PendingIntent for callback.
*/
protected PendingIntent pi_g = null;
private static final String DEBUG_TAG = "ExampleProgressService";
/**
* Message identifier for ProgressDialog init
*/
public static final String PROGRESS_DIALOG_BROADCAST_INIT = "Dialog.Progress.Init";
/**
* Message identifier for ProgressDialog finish
*/
public static final String PROGRESS_DIALOG_BROADCAST_FINISH = "Dialog.Progress.Finish";
/**
* Message identifier for ProgressDialog update
*/
public static final String PROGRESS_DIALOG_BROADCAST_UPDATE = "Dialog.Progress.Update";
/**
* Identifier of the result for intent content
*/
public static final String PROGRESS_DATA_RESULT = "Result";
/**
* Identifier of the result error for intent content
*/
public static final String PROGRESS_DATA_RESULT_ERROR_MESSAGE = "Result.Error.Message";
/**
* Identifier of the result error exception for intent content
*/
public static final String PROGRESS_DATA_RESULT_ERROR_EXCEPTION = "Result.Error.Exception";
/**
* Identifier of the result status for intent content
*/
public static final String PROGRESS_DATA_RESULT_STATUS_BOOL = "Result.Status.boolean";
/**
* Identifier of the pending intent for intent content
*/
public static final String PROGRESS_DATA_PENDING_RESULT = "PendingResult";
public ExampleProgressService() {
super("ExampleProgressService");
}
/**
* Send the finish message.
*/
private void closeProgressActivity() {
Intent intent = new Intent(PROGRESS_DIALOG_BROADCAST_FINISH);
sendBroadcast(intent);
}
/**
* Do some magic with the intent content
*/
private void extractVariablesFromIntentAndPrepare(Intent intent)
throws Exception {
pi_g = (PendingIntent) intent
.getParcelableExtra(PROGRESS_DATA_PENDING_RESULT);
if (pi_g == null) {
throw new Exception("There is no pending intent!");
}
/**
* Sends an error message.
*/
private void failed(Exception e, String message) {
Intent i = new Intent();
i.putExtra(PROGRESS_DATA_RESULT_ERROR_EXCEPTION, e);
i.putExtra(PROGRESS_DATA_RESULT_ERROR_MESSAGE, message);
send(i, false);
}
/**
* Sends the init message.
*/
private void initProgressActivity() {
Intent intent = new Intent(PROGRESS_DIALOG_BROADCAST_INIT);
intent.putExtra(PROGRESS_DIALOG_BOOL_HORIZONTAL_BAR,
multipart_g);
sendBroadcast(intent);
}
/**
* (non-Javadoc)
*
* #see android.app.IntentService#onHandleIntent(android.content.Intent)
*/
#Override
protected void onHandleIntent(Intent intent) {
extractVariablesFromIntentAndPrepare(intent);
initProgressActivity();
// do your calculation here and implements following code
Intent intent = new Intent(PROGRESS_DIALOG_BROADCAST_UPDATE);
intent.putExtra(PROGRESS_DIALOG_INT_VALUE, progressValue);
sendBroadcast(intent);
// If you finished, use one of the two methods to send the result or an error
success(result);
failed(exception, optionalMessage);
}
/**
* Sends the data to the calling Activity
*/
private void send(Intent resultData, boolean status) {
resultData.putExtra(PROGRESS_DATA_RESULT_STATUS_BOOL, status);
closeProgressActivity();
try {
pi_g.send(this, Activity.RESULT_OK, resultData);
} catch (PendingIntent.CanceledException e) {
Log.e(DEBUG_TAG,
"There is something wrong with the pending intent", e);
}
}
/**
* Sends the result message.
*/
private void success(String result) {
Intent i = new Intent();
i.putExtra(PROGRESS_DATA_RESULT, result);
send(i, true);
}
}
The result of the calculation progress shall be available in the parentActivity, so we create the PendingIntent in that Activity and call the IntentService.
// Some identifier for the call
int requestCode = 12345;
final Intent sI = new Intent(ExampleProgressService.PROGRESS_SERVICE_ACTION);
// Callback
sI.putExtra(ExampleProgressService.PROGRESS_DATA_PENDING_RESULT, parentActivity
.createPendingResult(requestCode, null,
PendingIntent.FLAG_CANCEL_CURRENT));
// Service start
parentActivity.startService(sI);
For receiving the results, we have to override the method onActivityResult(int requestCode, int resultCode, Intent data).
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
// Compares the requestCode with the requestCode from above
if (requestCode == ...) {
if (data.getBooleanExtra(ExampleProgressService.PROGRESS_DATA_RESULT_STATUS_BOOL, false)) {
// Calculation was success
data.getStringExtra(ExampleProgressService.PROGRESS_DATA_RESULT);
} else
{
// Calculation is failed
data.getStringExtra(ExampleProgressService.PROGRESS_DATA_RESULT_ERROR_MESSAGE);
((Exception) data.getSerializableExtra(ExampleProgressService.PROGRESS_DATA_RESULT_ERROR_EXCEPTION));
}
}
}
That was the magic, I hope it will help you.

Categories

Resources