0

I have a service that I have started from MainActivity with:

    Intent intent = new Intent(getBaseContext(), MyService.class);
    getBaseContext().startService(intent); 

Inside MyService, I create and start a thread, giving it a reference to the Service's Context:

    mThread = new MyThread(this);
    mThread.start();

Then inside the thread, I want to display a ProgressDialog. I tried this:

           mProgressDialog = ProgressDialog.show(mContext,
             "", "Receiving file...", true);
             mProgressDialog.show();

but I get "RuntimeException: Can't create handler inside thread that has not called Looper.prepare()". This makes sense, so I tried this instead:

            HandlerThread progressHandlerThread = new HandlerThread(
                    "ProgressHandlerThread");
            progressHandlerThread.start();

            Handler progressHandler = new Handler(
                    progressHandlerThread.getLooper());
            progressHandler.post(new Runnable()
            {
                @Override
                public void run()
                {
                    mProgressDialog = ProgressDialog.show(mContext, "",
                            "Receiving file...", true);
                    mProgressDialog.show();
                }
            });

but I get "BadTokenException: Unable to add window token is not for an application" but I don't understand what that error means.

I have seen this: Show ProgressDialog from thread inside the Service

and the conclusion seems to be that I need to runOnUIThread, but I don't have a reference to an Activity to do that since I am in a Service. Can anyone explain this BadTokenException and suggest a good way to do this?

Community
  • 1
  • 1
David Doria
  • 9,873
  • 17
  • 85
  • 147
  • the error is usually thrown with bad context, how are you getting the context? Why are you showing a dialog from a service anyway? – tyczj Oct 08 '13 at 16:58
  • @tyczj The context is 'this' from the Service. I am running a FileReceiver service (to listen for and receive files over a socket) and I want it to block the user from doing anything while it is receiving (for more accurate timing purposes for testing, etc.). – David Doria Oct 08 '13 at 17:13
  • a service does not have a UI therefore you cannot display a UI from a service. you should not be stopping the user from doing stuff while you "process". A service runs even when your app is not in the foreground, so what happens if you display a dialog while a user is doing something else...I would not like your app preventing me from doing other things because you don't want to code properly – tyczj Oct 08 '13 at 17:18
  • @tyczj Sure, I definitely agree with that for a real "app", but I am just doing some local testing so I wanted this "strange" behavior for my use case. – David Doria Oct 08 '13 at 17:19
  • see this link http://blog.blundell-apps.com/notification-for-a-user-chosen-time/ ..must you will find help about context and uses. – Ranjit Oct 09 '13 at 11:08

3 Answers3

1

I think the fact that you're trying to directly manipulate UI from a Service means that You're Doing It Wrong™

Services don't have a UI, and therefore should never directly influence UI. Instead, you should pipe an event from your Service to a listening Activity or Fragment, for instance.

Take a look a https://github.com/square/otto for some extremely flexible and saucy event bussing.

edit) Take a look at the comments below for what the dirty solution was to David's problem.

Jon Willis
  • 6,993
  • 4
  • 43
  • 51
  • 1
    That seems a bit heavyweight for this little thing I want to do. So what you're saying I have to do is send a message (maybe using Intents?) from the Service to a particular Activity which will then produce the ProgressDialog? – David Doria Oct 08 '13 at 17:15
  • Exactly, and Intents/Receivers would be a fine way to do it without introducing more dependencies or tightly coupling. – Jon Willis Oct 08 '13 at 18:05
  • What if the Activity is not in the foreground? It seems that best practices (and necessity?) say that you should unregister the BroadcastReceiver in an Activity's onPause. In this case, when the Intent is broadcast, the Activity will not receive it. Is there any way to resume an activity when an intent is sent? I even tried intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); on the intent I'm broadcasting from the Service's Thread for the Activity to receive but it does not seem to bring the Activity to the foreground before sending the Intent (or at all...). – David Doria Oct 08 '13 at 20:02
  • Perhaps you should create a Notification that would launch an Activity or start a service task? Perhaps you save state to disk and when your app resumes, read that state? Without knowing more about what you are intending to do it is hard to recommend a reasonable solution. – Jon Willis Oct 08 '13 at 20:07
  • I have a FileTransferActivity that is an app that deals with sending and receiving files. When it starts, it starts a FileReceiverService that sits and listens for incoming file transfers, even when the user has left the Activity. Now when the Service starts receiving a file, I want to resume the FileTransferActivity so I can display a ProgressDialog. I can broadcast an intent from the Service, but the FileTransferActivity won't hear it if it is not open (paused). Does that clarify things? – David Doria Oct 08 '13 at 20:14
  • Somewhat. If your service is still around when someone leaves your app, you should just silently process the file transfer or consider not leaving your Service running. If they are able to navigate to different parts of your app, then you need multiple Activities to listen to your intent. Those activities could startActivity() and send the user back to your FileTransferActivity or handle displaying the ProgressDialog themselves. I think you may want to consider the user experience implications of this flow. – Jon Willis Oct 08 '13 at 20:53
  • @John Willis Don't get me wrong - this is definitely a terrible flow. I am doing this as a special case to test some things and I want to make sure my testers are not screwing around doing other things while something important is happening. So then there is no good way to "resume" (or even just start) an activity with an Intent from a Service? – David Doria Oct 09 '13 at 00:53
  • OK. Try something like... getApplication().startActivity(new Intent(getBaseContext(), FileTransferActivity.class)); I see you're having problems with similar techniques above. That's because `You're Doing It Wrong™` and would have better success by not making a flow that breaks the framework's guidelines. – Jon Willis Oct 09 '13 at 03:45
  • Oh, you'll need to add the `Intent.FLAG_ACTIVITY_NEW_TASK` flag to your activity intent. – Jon Willis Oct 09 '13 at 03:52
  • Beautiful, works like a charm! I was actually inside an AsyncTask of the Service, so I had to change it to Intent intent = new Intent(mContext.getApplicationContext(), MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getApplicationContext().startActivity(intent); where mContext is getApplicationContext() from the Service. Thanks for your help! – David Doria Oct 09 '13 at 11:55
0

Not much idea about badTokenException , but I can suggest you to use AsyncTask to solve this kind of problem. You can start the progressdialog in preExecute() method and dismiss it in postExecute() method because these both are running on UI thread.

Ranjit
  • 5,130
  • 3
  • 30
  • 66
  • i think getApplicationContext is sufficient for this otherwise try the current context . – Ranjit Oct 08 '13 at 18:15
  • I get "getApplicationContext is undefined for the type AsyncTask", and passing 'this' as the context does not work. – David Doria Oct 08 '13 at 18:32
  • just check by creating a constructor of asynctask class with context and assign it like : private Context context; ProgressDialog prog; public async(Context context) { this.context = context; } – Ranjit Oct 08 '13 at 18:40
  • I passed getApplicationContext() from the Service to the member mContext of the AsyncTask as you suggested, but I get the same "BadTokenException". – David Doria Oct 08 '13 at 18:50
  • is your mContext is the current cntext of the AsyncTask class which you initialize through the asynctask constructor ?? – Ranjit Oct 08 '13 at 18:56
  • I create the AsyncTask with 'new MyTask(getApplicationContext())' from the Service. That context is the one I am passing to the ProgressDialog. – David Doria Oct 08 '13 at 18:59
  • The problem with AsyncTask is that they tend to leak. see https://github.com/octo-online/robospice for some explanations – Jon Willis Oct 08 '13 at 20:54
0

I just implemented this from a thread here. Please read Rachit Mishra's answer further down the page talking about a ProgressBar:

Communication between Activity and Service

I have this in my service:

public void sendMessage(int state) {
    Message message = Message.obtain();
    switch (state) {
        case 1://SHOW:
            message.arg1 = 1;
            break;
        case 0:
            message.arg1 = 0;
            break;
    }
    try {
        messageHandler.send(message);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

Call sendMessage() with 1 or 0 to show or dismiss the ProgressDialog within your service.

And this is in my Main Activity:

private ProgressDialog progress;

public class MessageHandler extends Handler {
    @Override
    public void handleMessage(Message message) {
        int state = message.arg1;
        switch (state) {
            case 0://HIDE
                progress.dismiss();
                break;
            case 1://SHOW
                progress = ProgressDialog.show(MainActivity.this, (getResources().getString(R.string.CONNECTING) + "..."), (getResources().getString(R.string.PLEASE_WAIT) + "!"));  //show a progress dialog
                break;
        }
    }
}

The ProgressDialog cannot be shown from the service, it must be called from the activity or fragment. I hope I added all the code you need and that it works well for your needs. To be honest I'm not sure how the message handler works but it works for me! The naming is probably not the best either lol. Sorry.

naps1saps
  • 373
  • 5
  • 14