1

A game I wrote some time ago has a problem with ANRs, and debugging suggests they're down to HTTP requests taking a long time (and thus causing the ANR).

I'd thought that by assigning the HTTP code into a Runnable called from within a Handler, I'd could avoid the ANR - but it seems this isn't the case?

The stack dumps suggest the runnable/handler code is still running within the 'Main' thread and thus still causes ANRs??

The task it's doing is asynchronous (uploading highscores and achievements) and so can be started and left to it's own devices entirely - what is the best way to implement this so that ANRs aren't going to become a problem?

One topic suggested that the Handler should be created in the Application class and not within the Game's Activity - but I can't find any detail on the differences between those cases??

All ideas greatly apprec.

p.s. extending this to ask - I assume an ANR relating to HTTP comes down to the phone being out-of-service/network/WiFi, because I've set a SHORT timeout for these requests (they're non-essential and can be retried later!?)

  • 3
    Handler execute code per default on the main thread. You need a `HandlerThread` if you want it to do that on a different one: http://stephendnicholas.com/archives/42 - Or use a regular `Thread`, `AsyncTask`, or an `IntentService` – zapl Nov 13 '12 at 21:08
  • 1
    @zapl. You should make this an answer. It's worth highlighting for others with a similar problem. – Simon Nov 13 '12 at 21:13

2 Answers2

3

A Handler will execute code / handle messages per default (any constructor without Looper e.g. new Handler()) in the current thread. That is in almost every case the main thread. If you want it to execute in a different thread you have to tell it which Looper thread it should use.

Android has a utility class called HandlerThread that creates a Thread with a Looper.

Short example:

public class MyActivity extends Activity {
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        HandlerThread handlerThread = new HandlerThread("background-handler");
        handlerThread.start();
        Looper looper = handlerThread.getLooper();
        mHandler = new Handler(looper);

        mHandler.post(new Runnable() {
            public void run() {
                // code executed in handlerThread
            }
        });
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // stops the HandlerThread
        mHandler.getLooper().quit();
    }
}

In case your task needs only a some information and does not need to report back, I'd go with an IntentService. Those don't go mad if your Activity-lifecycle recreates the Activity.

You would create a small Service in it's own file

public class SaveService extends IntentService {
    public SaveService() {
        super("SaveService");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        if ("com.example.action.SAVE".equals(intent.getAction())) {
            String player = intent.getStringExtra("com.example.player");
            int score = intent.getIntExtra("com.example.score", -1);
            magicHttpSave(player, score); // assuming there is an implementation here
        }
    }
}

Add it to the AndroidManifest.xml

<application ....

    <service android:name=".SaveService" />
</application>

And in your code start it with

Intent intent = new Intent(this /* context */, SaveService.class);
intent.setAction("com.example.action.SAVE");
intent.putExtra("com.example.player", "John123");
intent.putExtra("com.example.score", 5123);
startService(intent);

IntentService#onHandleIntent() runs on a background thread already so you don't have to bother about that.

zapl
  • 63,179
  • 10
  • 123
  • 154
  • Looking at the bigger picture, for this game an Asynctask is probably more appropriate - but another game with the same issue is getting the ANRs within Scoreloop (highscore/achivement tracking) code and that uses a Handler - which I could probably adapt (via your article/ideas) into it's own HandlerThread to avoid those errors? –  Nov 13 '12 at 21:52
  • `AsyncTask` works for anything that does not take forever since all AsyncTasks in your app will be executed sequentially and will block each other. See *Order of execution* section in http://developer.android.com/reference/android/os/AsyncTask.html - `AsyncTask` is an extension of `Handler`/`HandlerThread` with the goal to simplify tasks that need to run in background and need to report back to the main thread. For fixing existing Handler problems, above code should work fine – zapl Nov 13 '12 at 22:05
  • I think I have to accept Zap's answer as the most constructive - or at least the first! thanks all!! –  Nov 15 '12 at 03:25
1

Your Handler runs on the main thread. That is what causes ANR. Even if you create it in the Application, by default (no parameters given to Handler) will be created on the main thread. You have to create a Looper, with its own Thread. See here.

A Handler initialized with your own Looper, that is a viable option to solve ANR...

A more simple alternative solution can be, if you place your async network operation into an AsyncTask. A simple approach is to place the AsyncTask into your Activity. A somewhat more complex could be to create a Service (holder for non-ui related functionality), that does the communication, and cleans itself from memory, once the communication is over...

I'd use AsyncTask and place it into the Activity / fire it up from the Activity...

Finally, HERE you can find a nice tutorial on threads in android.

Vajk Hermecz
  • 5,413
  • 2
  • 34
  • 25
  • Asynctask certainly seems a simpler solution (all I need to do is run one method in the background, once in a while so having a whole thread for that seems overkill) Also - Asynctask shares 'scope' I assume - e.g. it has access to the methods/functions/objects which the Activity owns? –  Nov 13 '12 at 21:41