2

I'm developing an application that is meant to run only as a service. That is, it has no Activity (that is usually run, at least), and at any given moment, the only component of the application that will be running (usually) is a service. This is meant to be used with the screen locked. It already works.

In such a scenario, I seem to understand that the service's thread is the application's "main thread", even though it's not a UI thread proper (as there is no UI).

The question is: if the service uses a HandlerThread, can I call runOnUIThread() from a method that is called from the HandlerThread? Will this make it run from the main thread, without the need to launch an Activity (which would involve unlocking the screen)?

Basically my problem is that I need to use a SpeechRecognizer, lanching it from the service. Right now my service is using a Handler on a HandlerThread. When trying to init a SpeechRecognizer from methods that are (indirectly) called by the HandlerThread, I get an exception because SR has to be run from the main thread.

Can I use runOnUIThread()?

I see there is a similar question here: How to invoke Speechrecognizer methods from service with no main thread or activity
However, the answer involves invoking the SR from onCreate() or onStartCommand(), which is not viable in my case.

UPDATE: obviously, I can not call runOnUIThread(), as it is a method of Activity. So is there a way to have some call run on the main thread, which is in this case not a UI thread?

Community
  • 1
  • 1
matteo
  • 2,934
  • 7
  • 44
  • 59
  • If you create a `Handler` in onStartCommand, it will be attached to the main thread, and any code you post to that handler will run on the main thread – x-code Oct 12 '14 at 22:44
  • No. Well, yes or no: it depends on which looper you pass to the constructor of the Handler. And if you were to attach the Handler to the same looper of the main Service thread, then I wonder why you would be using a Handler in the first place. Consider this code: http://developer.android.com/guide/components/services.html under "Extending the Service class" – matteo Oct 13 '14 at 10:35

1 Answers1

3

This answer is not speech-recognizer specific which is why it started as a comment, but more space is needed to clarify...

Handler mainHandler;
public void onCreate() {
    super.onCreate();
    mainHandler = new Handler(); // this is attached to the main thread and the main looper
    // ...
}

// anywhere in a background thread:
mainHandler.post(new Runnable() {
    // ...
});

This code creates a handler which is attached to the main thread. Posting to this handler from a background thread will run code on the main thread as desired.

I'm not using SpeechRecognizer so I can't guarantee that it will solve the problem, but it seems like it should.

Edit: some more detail

The article I was thinking of that explains some big ideas is The Android Event Loop. In particular it links to this bit of code which shows how runOnUiThread is implemented by posting to a Handler as I am suggesting here:

 /**
     * Runs the specified action on the UI thread. If the current thread is the UI
     * thread, then the action is executed immediately. If the current thread is
     * not the UI thread, the action is posted to the event queue of the UI thread.
     *
     * @param action the action to run on the UI thread
     */
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }
x-code
  • 2,940
  • 1
  • 18
  • 19
  • Yeah, that's what I already understood from your comment, but a Handler attached to the main thread is pretty useless, isn't it? If you're not attaching it to a separate thread then why a Handler at all? I'm using a Handler that is attached to a HandlerThread's looper, and it needs to be so for normal operation. – matteo Oct 13 '14 at 15:10
  • 1
    Two threads, two different handlers. There are some good articles explaining in detail the relationship among threads, loopers, message queues and handlers and I will add a reference if I can find it. It is definitely not useless to have a handler that permits you to run code on a different thread. – x-code Oct 13 '14 at 15:22
  • Ok, that probably makes sense. However, I have a few methods in the Service that are called on the HandlerThread (not the main thread), and that is the way it normally needs to be, because you don't want heavy work to be done in the main thread (in case the service happens to be running while an activity of the same app is running too, though it's not the most usual scenario in my case). So the question is, when one of this methods, which inevitably is being run on a separate thread, needs to run something on the main thread, how to accomplish that. – matteo Oct 13 '14 at 16:09
  • To which I have found a solution: from the method (which belongs to the service, but which is called from the HandlerThread), I call startService to invoke onStartCommand on the same service itself. I pass it a different kind of Intent that it usually handles. In onStart(), I check the type of intent: if it is one of the intents that gets passed from startService from within the Service's methods, I call a method directly (without any Handler at all), so that is called from onStartCommand and hence run on the main thread – matteo Oct 13 '14 at 16:12
  • I could probably do basically the same thing using two Handlers as you suggest, one on a Handler thread and one on the default main thread. However, the solution still involves calling startService from the service itself, since the method that needs to have code run in the main thread is a method that is initially and inevitably called from the Handler attached to the HandlerThread. And in my case, for the second part I don't actually need a Handler. – matteo Oct 13 '14 at 16:15
  • 1
    I strongly suggest reading the article and using `handler.post(...)` when you need to run code on the main thread. The method you just described is pretzel logic. – x-code Oct 13 '14 at 16:55
  • Oh, I guess I get it now. By creating a second handler in the onCreate (the one attached to the main thread), I then just need to call that handler's .post(). – matteo Oct 13 '14 at 21:04
  • (btw I now notice I think you should replace "handler.post(" by "mainHandler.post(" in your code in the answer. – matteo Oct 13 '14 at 21:06
  • "pretzel logic" is a bit unfair (it actually does work), but you're right my workaround is overcomplicated and horrible, it's just the best I could come up with. – matteo Oct 13 '14 at 21:13
  • 1
    I should have said it was a roundabout method when a direct approach is available. My code also is fixed. – x-code Oct 13 '14 at 22:21