6

in my application I call the stock camera app to take a picture. In my activity result I start a asynctask to process the photo. (Rotate and upload the photo).

I also want to remove the photo from the gallery. To do this I exectute the following code in my asynctask.

// Delete image from gallery
        String[] imageColumns = { MediaStore.Images.Media._ID };
        String imageOrderBy = MediaStore.Images.Media._ID + " DESC";
        String imageWhere = MediaStore.Images.Media._ID + ">?";
        String[] imageArguments = { Integer.toString(captureLastID) };

        CursorLoader imageLoader = new CursorLoader(mActivity);

        imageLoader.setUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        imageLoader.setProjection(imageColumns);
        imageLoader.setSelection(imageWhere);
        imageLoader.setSelectionArgs(imageArguments);
        imageLoader.setSortOrder(imageOrderBy);

        Cursor imageCursor = imageLoader.loadInBackground();

        if (imageCursor.getCount() > 0) {
            while (imageCursor.moveToNext()) {
                int id = imageCursor.getInt(imageCursor
                        .getColumnIndex(MediaStore.Images.Media._ID));

                ContentResolver cr = mActivity.getContentResolver();
                cr.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                        MediaStore.Images.Media._ID + "=?",
                        new String[] { Long.toString(id) });
                break;
            }
        }

        imageCursor.close();

I get the error

can't create handler inside thread that has not called Looper.prepare

What causes this error and how can I fix it? Does it has something to do with the fact that I call loadInBackground, while asynctask already runs in the background?

This is my logcat:

java.lang.RuntimeException: An error occured while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:200)
    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:274)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:125)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:308)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
    at java.lang.Thread.run(Thread.java:1019)


   Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
    at android.os.Handler.<init>(Handler.java:121)
    at android.support.v4.content.Loader$ForceLoadContentObserver.<init>(Loader.java:52)
    at android.support.v4.content.CursorLoader.<init>(CursorLoader.java:96)
    at tasks.ProcessPictureTask.doInBackground(ProcessPictureTask.java:78)
    at tasks.ProcessPictureTask.doInBackground(ProcessPictureTask.java:1)
    at android.os.AsyncTask$2.call(AsyncTask.java:185)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:306)
    ... 4 more
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
    at android.os.Handler.<init>(Handler.java:121)
    at android.support.v4.content.Loader$ForceLoadContentObserver.<init>(Loader.java:52)
    at android.support.v4.content.CursorLoader.<init>(CursorLoader.java:96)
    at tasks.ProcessPictureTask.doInBackground(ProcessPictureTask.java:78)
    at tasks.ProcessPictureTask.doInBackground(ProcessPictureTask.java:1)
    at android.os.AsyncTask$2.call(AsyncTask.java:185)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:306)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
    at java.lang.Thread.run(Thread.java:1019)
Robby Smet
  • 4,649
  • 8
  • 61
  • 104
  • Possible duplicate of http://stackoverflow.com/questions/5009816/android-cant-create-handler-inside-thread-that-has-not-called-looper-prepare – sandrstar Oct 24 '12 at 14:36

3 Answers3

13

Actually, the accepted answer is incorrect. You seem to be interested in the reason to why this happen, and it might also help the responsiveness of your app, so I will give another answer:

You are not altering the UI, thus this is not the reason for this crash. The reason is that your background thread does not have a Looper associated with it, as the error states. A looper is a message queue that is occasionally checked for messages. For example, it is what you use when you use a Handler.

You can add a looper simply by adding this line of code before the call that crashes your app:

Looper.prepare();

There is a big advantage to doing heavy queries on a background thread instead of on the UI thread, since your UI will not be responsive while the cursor is loading. A call to the db should be done on a background thread if responsiveness is important to you.

Now, there really is no reason for the CursorLoader to need a looper in this case. It is just clumsy implementation that the exception is thrown, as far as I can tell at least. Really, using CursorLoader gets a lot easier if you use a LoaderManager with it.

If you don't want to mess around with loaders and background-threads, then an easier approach is to set a listener with registerListener(int, OnLoadCompleteListener) and start the loading with startLoading(). This automatically handles all threading for you, and might spare you some code.

angryITguy
  • 9,332
  • 8
  • 54
  • 82
pgsandstrom
  • 14,361
  • 13
  • 70
  • 104
  • Thanks for the extra explanation, I'll look into the LoaderManager class. Adding the Looper.prepare() line fixed the error. – Robby Smet Oct 25 '12 at 07:33
  • Adding the Looper.prepare(); will attach your Thread to the UI thread. In this case do not use AsynTask anymore. Change to sample Thread or handler. – Anis BEN NSIR Oct 25 '12 at 07:57
3

You are hacking the MVC pattern, you are getting this error because you are trying to update UI component from your onBackground Method in the AsynTask. Move any UI or initialisation to Post or Pre execute method. it seems that :

 CursorLoader imageLoader = new CursorLoader(mActivity);

must be place on the protected void onPreExecute () method.

Anis BEN NSIR
  • 2,555
  • 20
  • 29
2

You are trying to perform an UI operation in a background thread, you should run this in the UI Thread.

Carnal
  • 21,744
  • 6
  • 60
  • 75
  • http://stackoverflow.com/questions/6053369/android-cursorloader contains some good info on how to do what you're trying to do – mfsiega Oct 24 '12 at 14:35