Dori
Dori

Reputation: 18403

Android Looper and call stack

I was wondering how the Looper class actually processes Runnables (via the Handler class) in the Thread that the Looper is attached to? If a looper is looping through its messageQueue then surely it would be a blocking operation for that thread? I imagine it itself must be performing some threading trickery but then how does it add the posted Runnables run() method onto the host threads stack?

Many questions! Any help would be much appreciated. Thanks!

EDIT:

Looking through the Looper class file class i see the below, which confuses me even more as all comments refer to the looper running in the main thread but also that its a blocking operation while waiting for new messages on the MessageQueue. How is this not blocking the UI / main thread???

    /**
     *  Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static final void loop() {
        Looper me = myLooper();
        MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        while (true) {
            Message msg = queue.next(); // might block
            //if (!me.mRun) {
            //    break;
            //}
            if (msg != null) {
                if (msg.target == null) {
                    // No target is a magic identifier for the quit message.
                    return;
                }
                if (me.mLogging!= null) me.mLogging.println(
                        ">>>>> Dispatching to " + msg.target + " "
                        + msg.callback + ": " + msg.what
                        );
                msg.target.dispatchMessage(msg);
                if (me.mLogging!= null) me.mLogging.println(
                        "<<<<< Finished to    " + msg.target + " "
                        + msg.callback);

                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf("Looper", "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }

                msg.recycle();
            }
        }
    }


    /**
     * Returns the application's main looper, which lives in the main thread of the application.
     */
    public synchronized static final Looper getMainLooper() {
        return mMainLooper;
    }

   /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static final Looper myLooper() {
        return (Looper)sThreadLocal.get();
    }

    // sThreadLocal.get() will return null unless you've called prepare().
    private static final ThreadLocal sThreadLocal = new ThreadLocal();

    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

Upvotes: 5

Views: 2383

Answers (2)

Dori
Dori

Reputation: 18403

Ok, looking around Stack I have found another question which answers mine. My main confusion was coming from a not realising the below from this post. I was incorrectly thinking of everything apart from the stuff posted to Handlers (and added to the Loopers MessageQueue) was being executed in a linear call stack, but thinking about it this is more likely how any program with a UI is implemented, and makes a lot more sense that every thing on the UI thread originated from the Looper and all paths of excecution on this thread will lead back to it! Thanks to Berdon for attempting to answer my question, im sure i didnt explain my issue well enough :)

Regarding the android UI thread: At some point (probably before any activities and the like are created) the framework has set up a Looper (containing a MessageQueue) and started it. From this point on, everything that happens on the UI thread is through that loop. This includes activity lifecycle management and so on. All callbacks you override (onCreate(), onDestroy()...) are at least indirecty dispatched from that loop. You can see that for example in the stack trace of an exception. (You can try it, just write int a = 1 / 0; somewhere in onCreate()...)

Upvotes: 0

Austin Hanson
Austin Hanson

Reputation: 22040

You're absolutely right. The originating thread that constructs Looper.start() is called on becomes the thread that all posted Runnables and Messages are handled on. This indeed means that this thread is blocked.

Upvotes: 2

Related Questions