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.
Related
I have written an activity that create and start IntentService,the IntenteService send BroadCast messege to the Activity which received it successfully, now I want to send back data from the Activity to the IntentService, I tried by implementing i tried to implimente BroadCastReciver, however, it didn't work, any tips ? Thanks in advance.
public class ListenActivity extends AppCompatActivity {
/*** Attributes ***/
private Intent intentListeningService;
private Intent intentVictim;
//BroadcastReceiver
private BroadcastReceiver_addVictim broadcastReceiver_addVictim;
//IntentFilters
private IntentFilter intentFilter_addVictim;
private SwipeRefreshLayout mSwipeRefreshLayout;
// for send to Service
public static final String ACTION_Refresh = "com.bmm.bmmratvandroid.ListenActivity.Refresh";
public static final String EXTRA_KEY_r = "EXTRA_r";
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listen);
/*****************************************************
* initialization get info from precedent activity *
***************************************************/
Intent i = getIntent();
serverPort = i.getIntExtra("port",6000);
/****************************
* start service listening *
***************************/
intentListeningService = new Intent(this, ListeningService.class);
intentListeningService.putExtra(ListeningService.EXTRA_KEY_PORT, serverPort);
Toast.makeText(getApplicationContext(), "Starting Service ..", Toast.LENGTH_LONG).show();
startService(intentListeningService);
/********************************
* Create BroadcastReceivers *
*******************************/
broadcastReceiver_addVictim = new BroadcastReceiver_addVictim();
/*******************************
* register BroadcastReceivers *
*******************************/
intentFilter_addVictim = new IntentFilter(ListeningService.ACTION_MyUpdate);
intentFilter_addVictim.addCategory(Intent.CATEGORY_DEFAULT);
registerReceiver(broadcastReceiver_addVictim, intentFilter_addVictim);
// here shoud send message to Service what didn't work
Intent intentRefresh = new Intent();
intentRefresh.setAction(ACTION_Refresh);
intentRefresh.addCategory(Intent.CATEGORY_DEFAULT);
intentRefresh.putExtra(EXTRA_KEY_r, "REFRESH");
sendBroadcast(intentRefresh);
} // fin onCreate(Bundle savedInstanceState)
/*<<<<<<<<<<<<<<<<<<<<<<<<<<<<
< BroadcastReceiver Classes <
<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
public class BroadcastReceiver_addVictim extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
String ip =null;
ip = intent.getStringExtra(ListeningService.EXTRA_KEY_addVictim);
// Here I recive message from the IntentService
// process(ip)
}
}} // fin ListenActivity
public class ListeningService extends IntentService {
/*** Attributes ***/
public static final String ACTION_MyIntentService = "com.bmm.bmmratvandroid.ListeningService.RESPONSE";
public static final String ACTION_MyUpdate = "com.bmm.bmmratvandroid.ListeningService.UPDATE";
public static final String EXTRA_KEY_PORT = "EXTRA_PORT";
public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
public static final String EXTRA_KEY_addVictim = "EXTRA_addVictim";
public static final String EXTRA_KEY_deleteVictim = "EXTRA_deleteVictim";
int serverPort;
private String serviceMsg;
private ServerSocket serverSocket;
private Hashtable<String,Socket> sockets;
private int timeOut;
//BroadcastReceiver
private BroadcastReceiver_reponse broadcastReceiver_r;
//IntentFilters
private IntentFilter intentFilter_r;
/*****************
* Constructeur *
* ***************/
public ListeningService() {
super("com.bmm.bmmratvandroid.ListeningService");
sockets = new Hashtable<String,Socket>();
timeOut = 3000;
}
/************************
* Communication method *
************************/
#Override
protected void onHandleIntent(#Nullable Intent intent) {
serverPort = intent.getIntExtra(EXTRA_KEY_PORT,6000); // get port
/********************************
* Create BroadcastReceivers *
*******************************/
broadcastReceiver_r = new BroadcastReceiver_reponse();
/*******************************
* register BroadcastReceivers *
*******************************/
intentFilter_r = new IntentFilter(ListenActivity.ACTION_Refresh);
intentFilter_r.addCategory(Intent.CATEGORY_DEFAULT);
registerReceiver(broadcastReceiver_r, intentFilter_r);
}
//*-*-*-*-*-*-*-*-*-*-*-**-*-*-*//
//\\ Calling ListenActivity //\\
//*-*-*-*-*-*-*-*-*-*-*-*-*-*-*//
private void addVictim(String ip) {
Intent intentUpdate = new Intent();
intentUpdate.setAction(ACTION_MyUpdate);
intentUpdate.addCategory(Intent.CATEGORY_DEFAULT);
intentUpdate.putExtra(EXTRA_KEY_addVictim, ip);
sendBroadcast(intentUpdate);
}
//###############################
// methode manipulating sokets #
//###############################
/*<<<<<<<<<<<<<<<<<<<<<<<<<<<<
< BroadcastReceiver Classes <
<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
public class BroadcastReceiver_reponse extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// HERE I SHOUD RECIVE THE MESSAGE !?
//???????????????????????????????????
intent.getStringExtra(ListenActivity.EXTRA_KEY_r);
}
}}
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.
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.
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.
}
}
I've looked at a number of other threads with similar titles, and none seem to cover my problem. So, here goes.
I'm using the Google market expansion files (apkx) library and sample code, with a few modifications. This code relies on receiving callbacks from a service which handles background downloading, licence checks etc.
I have a bug where the service doesn't get correctly attached, which results in a softlock. To make this more unhelpful, this bug never happens on some devices, but occurs about two thirds of the time on other devices. I believe it to be independent of Android version, certainly I have two devices running 2.3.4, one of which (a Nexus S) doesn't have the problem, the other (an HTC Evo 3D) does.
To attempt to connect to the service, bindService is called and returns true. OnBind then gets called as expected and returns a sensible value but (when the bug occurs) onServiceConnected doesn't happen (I've waited 20 minutes just in case).
Has anyone else seen anything like this? If not, any guesses for what I might have done to cause such behaviour? If no-one has any thoughts, I'll post some code tomorrow.
EDIT: Here's the relevant code. If I've missed anything, please ask.
Whilst adding this code, I found a minor bug. Fixing it caused the frequency of the problem I'm trying to solve to change from 2 times in 3 to about 1 time in 6 on the phone I'm testing it on; no idea about effects on other phones. This continues to suggest to me a race condition or similar, but I've no idea what with.
OurDownloaderActivity.java (copied and changed from Google sample code)
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
//Test the licence is up to date
//if (current stored licence has expired)
{
startLicenceCheck();
initializeDownloadUI();
return;
}
...
}
#Override
protected void onResume() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.connect(this);
}
super.onResume();
}
private void startLicenceCheck()
{
Intent launchIntent = OurDownloaderActivity.this
.getIntent();
Intent intentToLaunchThisActivityFromNotification = new Intent(OurDownloaderActivity
.this, OurDownloaderActivity.this.getClass());
intentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentToLaunchThisActivityFromNotification.setAction(launchIntent.getAction());
if (launchIntent.getCategories() != null) {
for (String category : launchIntent.getCategories()) {
intentToLaunchThisActivityFromNotification.addCategory(category);
}
}
// Build PendingIntent used to open this activity from Notification
PendingIntent pendingIntent = PendingIntent.getActivity(OurDownloaderActivity.this,
0, intentToLaunchThisActivityFromNotification,
PendingIntent.FLAG_UPDATE_CURRENT);
DownloaderService.startLicenceCheck(this, pendingIntent, OurDownloaderService.class);
}
initializeDownloadUI()
{
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub
(this, OurDownloaderService.class);
//do a load of UI setup
...
}
//This should be called by the Stub's onServiceConnected method
/**
* Critical implementation detail. In onServiceConnected we create the
* remote service and marshaler. This is how we pass the client information
* back to the service so the client can be properly notified of changes. We
* must do this every time we reconnect to the service.
*/
#Override
public void onServiceConnected(Messenger m) {
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
DownloaderService.java (in Google market expansion library but somewhat edited )
//this is the onBind call that happens fine; the value it returns is definitely not null
#Override
public IBinder onBind(Intent paramIntent) {
return this.mServiceMessenger.getBinder();
}
final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this);
final private Messenger mServiceMessenger = mServiceStub.getMessenger();
//MY CODE, derived from Google's code
//I have seen the bug occur with a service started by Google's code too,
//but this code happens more often so is more repeatably related to the problem
public static void startLicenceCheck(Context context, PendingIntent pendingIntent, Class<?> serviceClass)
{
String packageName = serviceClass.getPackage().getName();
String className = serviceClass.getName();
Intent fileIntent = new Intent();
fileIntent.setClassName(packageName, className);
fileIntent.putExtra(EXTRA_LICENCE_EXPIRED, true);
fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
context.startService(fileIntent);
}
#Override
protected void onHandleIntent(Intent intent) {
setServiceRunning(true);
try {
final PendingIntent pendingIntent = (PendingIntent) intent
.getParcelableExtra(EXTRA_PENDING_INTENT);
if (null != pendingIntent)
{
mNotification.setClientIntent(pendingIntent);
mPendingIntent = pendingIntent;
} else if (null != mPendingIntent) {
mNotification.setClientIntent(mPendingIntent);
} else {
Log.e(LOG_TAG, "Downloader started in bad state without notification intent.");
return;
}
if(intent.getBooleanExtra(EXTRA_LICENCE_EXPIRED, false))
{
//we are here due to startLicenceCheck
updateExpiredLVL(this);
return;
}
...
}
}
//MY CODE, based on Google's, again
public void updateExpiredLVL(final Context context) {
Context c = context.getApplicationContext();
Handler h = new Handler(c.getMainLooper());
h.post(new LVLExpiredUpdateRunnable(c));
}
private class LVLExpiredUpdateRunnable implements Runnable
{
LVLExpiredUpdateRunnable(Context context) {
mContext = context;
}
final Context mContext;
#Override
public void run() {
setServiceRunning(true);
mNotification.onDownloadStateChanged(IDownloaderClient.STATE_LVL_UPDATING);
String deviceId = getDeviceId(mContext);
final APKExpansionPolicy aep = new APKExpansionPolicy(mContext,
new AESObfuscator(getSALT(), mContext.getPackageName(), deviceId));
// Construct the LicenseChecker with a Policy.
final LicenseChecker checker = new LicenseChecker(mContext, aep,
getPublicKey() // Your public licensing key.
);
checker.checkAccess(new LicenseCheckerCallback() {
...
});
}
}
DownloaderClientMarshaller.java (in Google market expansion library)
public static IStub CreateStub(IDownloaderClient itf, Class<?> downloaderService) {
return new Stub(itf, downloaderService);
}
and the Stub class from the same file:
private static class Stub implements IStub {
private IDownloaderClient mItf = null;
private Class<?> mDownloaderServiceClass;
private boolean mBound;
private Messenger mServiceMessenger;
private Context mContext;
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ONDOWNLOADPROGRESS:
Bundle bun = msg.getData();
if ( null != mContext ) {
bun.setClassLoader(mContext.getClassLoader());
DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData()
.getParcelable(PARAM_PROGRESS);
mItf.onDownloadProgress(dpi);
}
break;
case MSG_ONDOWNLOADSTATE_CHANGED:
mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE));
break;
case MSG_ONSERVICECONNECTED:
mItf.onServiceConnected(
(Messenger) msg.getData().getParcelable(PARAM_MESSENGER));
break;
}
}
});
public Stub(IDownloaderClient itf, Class<?> downloaderService) {
mItf = itf;
mDownloaderServiceClass = downloaderService;
}
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
//this is the critical call that never happens
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mServiceMessenger = new Messenger(service);
mItf.onServiceConnected(
mServiceMessenger);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mServiceMessenger = null;
mBound = false;
}
};
#Override
public void connect(Context c) {
mContext = c;
Intent bindIntent = new Intent(c, mDownloaderServiceClass);
bindIntent.putExtra(PARAM_MESSENGER, mMessenger);
if ( !c.bindService(bindIntent, mConnection, 0) ) {
if ( Constants.LOGVV ) {
Log.d(Constants.TAG, "Service Unbound");
}
}
}
#Override
public void disconnect(Context c) {
if (mBound) {
c.unbindService(mConnection);
mBound = false;
}
mContext = null;
}
#Override
public Messenger getMessenger() {
return mMessenger;
}
}
DownloaderServiceMarshaller.java (in Google market expansion library, unchanged)
private static class Proxy implements IDownloaderService {
private Messenger mMsg;
private void send(int method, Bundle params) {
Message m = Message.obtain(null, method);
m.setData(params);
try {
mMsg.send(m);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public Proxy(Messenger msg) {
mMsg = msg;
}
#Override
public void requestAbortDownload() {
send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle());
}
#Override
public void requestPauseDownload() {
send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle());
}
#Override
public void setDownloadFlags(int flags) {
Bundle params = new Bundle();
params.putInt(PARAMS_FLAGS, flags);
send(MSG_SET_DOWNLOAD_FLAGS, params);
}
#Override
public void requestContinueDownload() {
send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle());
}
#Override
public void requestDownloadStatus() {
send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle());
}
#Override
public void onClientUpdated(Messenger clientMessenger) {
Bundle bundle = new Bundle(1);
bundle.putParcelable(PARAM_MESSENGER, clientMessenger);
send(MSG_REQUEST_CLIENT_UPDATE, bundle);
}
}
private static class Stub implements IStub {
private IDownloaderService mItf = null;
final Messenger mMessenger = new Messenger(new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REQUEST_ABORT_DOWNLOAD:
mItf.requestAbortDownload();
break;
case MSG_REQUEST_CONTINUE_DOWNLOAD:
mItf.requestContinueDownload();
break;
case MSG_REQUEST_PAUSE_DOWNLOAD:
mItf.requestPauseDownload();
break;
case MSG_SET_DOWNLOAD_FLAGS:
mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS));
break;
case MSG_REQUEST_DOWNLOAD_STATE:
mItf.requestDownloadStatus();
break;
case MSG_REQUEST_CLIENT_UPDATE:
mItf.onClientUpdated((Messenger) msg.getData().getParcelable(
PARAM_MESSENGER));
break;
}
}
});
public Stub(IDownloaderService itf) {
mItf = itf;
}
#Override
public Messenger getMessenger() {
return mMessenger;
}
#Override
public void connect(Context c) {
}
#Override
public void disconnect(Context c) {
}
}
/**
* Returns a proxy that will marshall calls to IDownloaderService methods
*
* #param ctx
* #return
*/
public static IDownloaderService CreateProxy(Messenger msg) {
return new Proxy(msg);
}
/**
* Returns a stub object that, when connected, will listen for marshalled
* IDownloaderService methods and translate them into calls to the supplied
* interface.
*
* #param itf An implementation of IDownloaderService that will be called
* when remote method calls are unmarshalled.
* #return
*/
public static IStub CreateStub(IDownloaderService itf) {
return new Stub(itf);
}