Android AsyncTask - Changing listeners - android

I have an AsyncTask which never dies. The listener to the events in this AsyncTask keeps changing(depending on which activity/fragment is visible to the user). To accomplish this, I have a setup like the following :
An interface :
public interface TaskListener {
public void onItemChanged(String itemName)
}
AsyncTask which contains a TaskListener Member variable which is registered/unregistered using public methods.
public void registerListener(TaskListener listener) {
mListener = listener;
}
public void unregisterListener() {
mListener = null;
}
In the onPublishProgress() method (called via publishProgress() in the doInBackground() method) of the AsyncTask, I notify the listener.
Now, my question is are there any caveats for this situation while notifying the listener? In particular, I would like to know whether it is thread safe or not. i.e., if publishProgress() and registerListener() is called at the same time, will the right listener receives a callback?

I once had to debug an AsyncTask and followed my code even got into android's source code.
publishProgress() does not issue a direct call to onProgressUpdate() it just posts a message in a queue, and the queue handler eventually calls onProgressUpdate().
So strictly speaking, if publishProgress() and registerListener() are called at the same time registerListener() will get there first.
If you are concerned about interference between the two, just enclose the sensitive code in a synchronized block over whatever variable is there.
synchronized(mListener) {
// do stuff to mListener
}
do this in both onProgressUpdate() and registerListener() and anywhere else you want to be mutually exclusive on handling the listener.

AsyncTask should not live between activities. That's not really correct way.
Android has services for this.
http://developer.android.com/reference/android/app/Service.html
http://developer.android.com/guide/developing/tools/aidl.html

Related

Proper way of handling listener callbacks that happen on background threads

In Android using Java, when using a listener callback method that can be called from multiple threads according to the documentation, is there any harm in forcing it to run on the main thread by surrounding the method body with runOnUiThread like this:
#Override
public void onSomeBackgroundTaskCameToAnEndDueToReasonNumber21(final int taskID) {
runOnUiThread(new Runnable() {
// put all code here
}
}
in order to prevent accidentally "touching views" from a background thread in subsequent chains of method calls?
When the Android developers choose to make one of these callback fire on a background thread, what is the reason?
An example of such a class is UtteranceProgressListener used on TextToSpeech objects.

Can I block my Service thread while I call a method on the UI thread?

I have what seems like a stupid requirement:
I need to block my IntentService thread while I run a single method which must be accessed from the UI thread. How can I do this?
Obviously I can run the UI method via a Handler with Looper.getMainLooper() but of course the rest of my service processing would then continue.
A bit more detail:
My Service Sync's content while using binder callbacks to progressively update the UI with the new items. All methods which affect the list of items are UI-Thread bound to avoid my StaggeredGridLayout throwing ConcurrentModificationExceptions.
However, when my service starts I want to call the list to get current id's before I sync each source of content, this is what requires the UI thread access.
The reason I can't just provide this list while starting the Service is that the app must remain responsive (meaning an item can be deleted as we sync), and the list needs to be checked before each additional source of content is synced
Solutions:
The best solution I came up with, is to create two Handlers, one on the Main Looper for the UI method, the other for everything else and send messages between them. It doesn't feel like an acceptable, clean solution
The other was to make a threadsafe version of the same UI method, starting by doing an arraycopy of the contents and looping with the copy. I'm not sure if the arraycopy operation is unsafe (prone to ConcurrentModificationException) too, as it's hard to trigger the bug. So I'm not sure this is acceptable either.
I wrote a solution that uses AsyncTask.
Here's an Activity that calls the task:
public class CallingActivity extends Activity {
// method that does all the synchronization
private void doSync() {
// for each item you want to sync
int itemId;
new ItemUpdateAsyncTask(itemId, this).execute();
}
// this method is called for each synced item
public void syncItem(int itemId, Object syncedItem) {
// update list etc.
}
}
Now, the AsyncTask that updates the items in background:
public class ItemUpdateAsyncTask extends AsyncTask<Void, Void, Object> {
// the id of the item to be updated
private final int mItemId;
// reference to the activity that will be notified
// of the item update
private final WeakReference<CallingActivity> mCallingActivity;
public ItemUpdateAsyncTask(int itemId, CallingActivity callingActivity) {
super();
mItemId = itemId;
mCallingActivity = new WeakReference<>(callingActivity);
}
#Override
protected Object doInBackground(Void... params) {
// sync item in background
Object syncedItem;
return syncedItem;
}
#Override
protected void onPostExecute(Object syncedItem) {
CallingActivity callingActivity = mCallingActivity.get();
if (callingActivity != null) {
// update item in main thread
callingActivity.syncItem(mItemId, syncedItem);
}
}
}
I think it is easier to design this solution using AsyncTasks instead of Services because of the callback object passing. Services and Activities can only communicate with each other by means of Intents, which can carry only serializable data. As you can see, asynctasks can reference callback objects, which makes things easier. Or else, you would have to register a broadcast receiver in the activity and the service would have to send a broadcast with the updated item, which would need to be serializable. That's a viable solution as well, though.
the user can go between several activites during the time it is running
It is a point worth noting when you design an architecture like this, that does something in background and pushes the result to the caller. The caller may be e.g. an Activity that may be already finished and garbage-collected by the time that the background work finishes. That's the reason I wrapped the caller in a WeakReference.

Android Accelerometer issues

I am working on a game that involves using the accelerometer to control the character. My problem is this: I need to use the values recieved by the sensor in classes and methods that are not accessible inside OnSensorChanged(). I believe I need to implement a Callback from inside the OnSensorChanged, but I don't know how to do that. Can anyone help me out?
I believe the answer in this post (How to Define Callbacks in Android?) will help you out.
To summarize, create the callback interface:
// The callback interface
interface MyCallback {
void callbackCall(SensorEvent event);
}
Implement the call back interface in the class that is supposed to do calculations:
class Callback implements MyCallback {
void callbackCall(SensorEvent event) {
// callback code goes here
}
}
Make the call from your Activity where you have the onSensoreChanged():
// The class that takes the callback
class Worker extends Activity implements SensorEventListener {
MyCallback callback;
public void onSensorChanged(SensorEvent event) {
callback.callbackCall(event);
}
}
I hope this helps.
UPDATE:
I assume you already know about processes and threads (if not, please have a look at the Android doc about Processes and Threads).
The onSensorChanged method is an I/O and it is a good practice to do I/O operations in a separate thread (instead of the main UI thread).
Once the callback method is called, you can store the event in another variable and use those local variables in that class.
Since you are writing a game, it is unlikely for your app to require every single event. Therefore, while the app is busy calculating data for your game, the other events can be dropped. You can do this by setting a "busy" flag (boolean) and include the code for calculation within this if block.
void callbackCall(SensorEvent event) {
if (!busy) {
// Set the busy flag to block other event changes
busy = true;
// callback code goes here
// Once finished, reset the busy flag to allow other events to come in
busy = false;
}
}

main activity implementing a listener interface for updating UI

So first of all I figured out how to update the UI from another thread. I'm using AsyncTask and the Handler class for anything else
But in the first place I did it wrong and now I really want to find out what was wrong.
So my first try was to create an java interface MyListener with a methode updateUI() and let the main activity implement that listener interface. Than I registered the main activity as listener in a thread that handles http requests. From that thread I called MyListener.updateUI() after the request was done. updateUI() updates some textviews with the request's result.
This doesn't work in android. And I did not expect that. And I don't know why.
The exception told me that I have to make these updates from the UI thread.
I don't understand why updateUI() doesn't run in the UI-thread when I call MyListener.updateUI() with the main activity implementing updateUI() from the listener interface.
Could you explain me this behavior?
I see that you have excuted the http request in other thread, but you do not use AsyncTask, right? I suggest that you should use that class, excute http request in doInBackground method (because it works off main thread), and call MyListener.updateUI() on onPostExcute because this method is always get called in main thread. Have a try and let me know if it works or not.
Doesn't matter if you are calling MyListener.updateUI() in your MainActivity's onCreate() method or any other if you are doing that from another Thread it will throw you an exception, because you can update View's only in the applications Main Thread. To achieve this you can do something like that :
public static void updateUI(){
((Activity) mContext).runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
// update your UI here and be sure to give the activity context, not getApplicationContext();
}
});
}
put this in your Activity and in your onCreate() do something like that :
public static Context mContext;
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mContext = this;
}
and call updateUI() from whatever thread you want to do it.
Of course this isn't the only way you can update your interface.You can take a look at AsyncTask implementation in Android SDK. It's a really powerful class and I think it will do the thing which you want more efficiently.
You were getting this error because the control was still inside the Background thread... though you have called the interface method which has been implemented in you activity.So in order to get the control back to your UI thread you need to post using a handler.The control does not goes from the Background thread to the UI thread by just calling the method which is located in your activity.
Hope this clarifies your doubt.

#Background and screen rotation when using AndroidAnnotations, how to make sure that the callbacks are received?

When using the #Background annotation we start a new thread. And if we
while this thread is executing where to rotate the screen, will we
then lose the callback from that thread or how is that handled? With
the Loaders this is sorted out behind the screen so we don't have to
worry about the problems that occurred frequently back when we used
async tasks.
But how does the #Background annotation deal with this ?
First of all, when you use the #Background annotation, the code is executed on a separate thread, but this doesn't necessarily mean that a new thread will be started, because we use a common thread pool (which can be replaced) for all #Background methods.
Like an AsyncTask, #Background does not handle any lifecycle changes on your activities. So, if you call an #Background method and then the screen is rotated, then the #Background code will be executed, no matter what, on the instance it was called on. If #Background is placed on a method that belongs to the activity, and in turns call a #UiThread method, there is a risk that the #UiThread method will be called on the wrong activity instance, if a configuration change occurred.
In Android, prior to Loaders, the usual way to handle that was to use AsyncTask, keep references to those tasks in onRetainNonConfigurationInstance(), and rebind them to the new activity after a config change.
In the latest release, AndroidAnnotations provides the #NonConfiguration instance annotation, which can be combined with #EBean / #Bean and #Background to achieve the same effect.
Here is a sample code (not tested, written from gmail) :
#EActivity
public class MyActivity extends Activity {
// Using #NonConfigurationInstance on a #Bean will automatically update the context ref on configuration changes (if the bean is not a singleton)
#NonConfigurationInstance
#Bean
MyBackgroundTask task;
#Click
void myButtonClicked() {
task.doSomethingInBackground();
}
void showResult(MyResult result) {
// do something with result
}
}
#EBean
public void MyBackgroundTask {
#RootContext
MyActivity activity;
#Background
void doSomethingInBackground() {
// do something
MyResult result = XXX;
updateUI(result);
}
// Notice that we manipulate the activity ref only from the UI thread
#UiThread
void updateUI(MyResult result) {
activity.showResult(result);
}
}
I think we could provide even better solutions, but there are a lot of different use cases, and we need to think about them all. So right now, #Background has a very simple behavior and I don't want to change that. We could, however, introduce new annotations with an advanced "thread + lifecycle" behavior.
Thanx to "Pierre-Yves Ricau" for providing this answer via the googlegroup for androidannotations. Hope this will help others who might get stuck with a similar problem.

Categories

Resources