First, you need to understand what the Message class is like. A Message object contains among other fields the following:
Handler target; // a handler that enqueued the message
long when; // the time at which the message is to be processed
[RUNNABLE] Runnable callback; =
[SWITCHED] int what, int arg1, int arg2, Bundle data...
bool isAsynchronous; // I will talk about it in the end
What I tagged with [RUNNABLE] and [SWITCHED] represents the two non-overlapping means of processing a Message. If the callback is not null all the [SWITCHED] fields are ignored. If the callback is null than the Message is defined by [SWITCHED] fields and is processed in either the Handler's overriden handleMessage() or the handleMessage() of the Handler.Callback the handler was initialized with.
The MessageQueue is sorted by the when field. The Looper will not dequeue and process a message until the current time, as measured by SystemClock.uptimeMillis, is greater than or equal to the time stored in the message’s when field.
When you call Handler#post(Runnable r) the following things happen:
A Message is obtained from the pool (a simple static linked list
in the Message class)
Your Runnable is assigned to the message's callback field.
when field is simply set to the current time if no delay or specific
time was passed
The Message is enqueued to the MessageQueue. If when is
earlier than that of the head of the queue it becomes a new head. If
it's not, than it's inserted in the middle so that the MessageQueue
remains sorted by when
The Looper which was in a non-terminating loop dequeuing the messages
from the queue and processing them in sequence (no interweaving),
eventually, dequeues our message and calls dispatchMessage() on
the handler that originally posted the Runnable.
The handler decides whether the message is [RUNNABLE] or
[SWITCHED] and processes it accordingly. In particular it calls
run() on the callback if it's present
This should answer your questions on the behavior of your Runnable posted on the UI Thread during the blocking task - well, no, it does not interrupt the ongoing task, nor does it interweave. Everything that happens on the thread first gets into the MessageQueue, button clicks or your custom Runnables that you post from other threads. There is basically no way it could be happening some other way: Looper.loop() just makes the thread busy with its for(;;) loop.
There are ways to change the messages ordering though.
For instance, there is an interesting concept of sync-barrier in the Looper/Handler framework. A sync-barrier is by a convention just a Message with a null target (so it's basically just a flag-like thing, there is no handler to dispatch it). If it's put to the queue with postSyncBarrier(), the whole process of dequeuing changes, until the sync-barrier is removed from the queue with removeSyncBarrier(). The Messages not marked as isAsynchronous will be ignored and not dequeued and processed at all. Instead, the queue will be scanned until the message with isAsynchronous = true is found. It will then be scheduled according to its when and processed when its time comes.
Also, you can call a self-explanatory Handler#postAtFrontOfQueue(), though, as pointed out in the documentation
This method is only for use in very special circumstances -- it can
easily starve the message queue, cause ordering problems, or have
other unexpected side-effects.
I suggest you browse the source code of all the classes mentioned. It reads like a good fiction book.