Android multi-thread Analysis 3: Handler, logoff implementation, androidlogoff
Android multi-thread Analysis 3: Implementation of Handler and logoff
Luo chaohui (http://www.cnblogs.com/kesalin/) CC license, reprinted please indicate the source
In the previous article "Android multi-Thread Analysis II: Thread implementation", we have analyzed in detail how Android threads are created, run, and destroyed. The focus of this article is to analyze the corresponding native methods, today, I will focus on the classes related to multithreading In the Android Framework layer: Handler, logoff, MessageQueue, Message, and their relationships with threads. We can use an inappropriate analogy to describe the association between them: If we compare Thread to a production workshop, logoff Is the production line in the workshop, this production line continuously obtains the material Messsage from MessageQueue and distributes and processes the Message. (because the Message is generally complete, logoff only schedules the Message Handler to process the Message in most cases ). It is precisely because the message needs to be processed in logoff, and logoff needs to run in Thread, so it is not possible to perform UI operations in non-UI threads casually. UI operations are usually implemented through message delivery. Only messages delivered to the correct logoff can be processed. For the UI, this logoff must be running in the UI thread.
During app writing, Handler is often used as follows:
Handler mHandler = new Handler();mHandler.post(new Runnable(){@Overridepublic void run() {// do somework}});
Or, as shown in the first article in this series:
private Handler mHandler= new Handler(){ @Override public void handleMessage(Message msg) { Log.i("UI thread", " >> handleMessage()"); switch(msg.what){ case MSG_LOAD_SUCCESS: Bitmap bitmap = (Bitmap) msg.obj; mImageView.setImageBitmap(bitmap); mProgressBar.setProgress(100); mProgressBar.setMessage("Image downloading success!"); mProgressBar.dismiss(); break; case MSG_LOAD_FAILURE: mProgressBar.setMessage("Image downloading failure!"); mProgressBar.dismiss(); break; } } }; Message msg = mHandler.obtainMessage(MSG_LOAD_FAILURE, null); mHandler.sendMessage(msg);
So what happened behind Handler's post/sendMessage? Let's take a look at this mystery.
First, we will analyze the Handler constructor:
final MessageQueue mQueue; final Looper mLooper; final Callback mCallback; final boolean mAsynchronous; public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } public Handler(Callback callback, boolean async) { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } public Handler() { this(null, false); }
Some member variables of Handler are listed above:
Mlo: indicates the message processing cycle of a thread. Note: Not every thread has a message processing cycle. Therefore, threads in the Framework can be divided into two types: loose and loose. To facilitate app development, Framework provides a loose Thread implementation: HandlerThread. In the previous article "Thread implementation", we also mentioned the differences between the run () methods of two different threads.
/** * Handy class for starting a new thread that has a looper. The looper can then be * used to create handler classes. Note that start() must still be called. */public class HandlerThread extends Thread { Looper mLooper; /** * Call back method that can be explicitly overridden if needed to execute some * setup before Looper loops. */ protected void onLooperPrepared() { } public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; } /** * This method returns the Looper associated with this thread. If this thread not been started * or for any reason is isAlive() returns false, this method will return null. If this thread * has been started, this method will block until the looper has been initialized. * @return The looper. */ public Looper getLooper() { if (!isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; }}
Compared with Thread, HandlerThread has one more type of logoff member variable mlow.it is created in the run () function by low.prepare () and saved in TLS:
/** 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 void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
Then, you can use Logoff: mylogoff () to obtain the created Logoff:
/** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static Looper myLooper() { return sThreadLocal.get(); }
Finally, the Logoff: Loop () method is used to run the message processing cycle: extract the message from MessageQueue, distribute and process the message, and continuously Loop the process. This method will be introduced later.
The member variable mQueue of Handler is the member variable of its member variable mloader. Here, it is used as the member variable of Handler to simplify writing; the member variable mCallback provides another convenient way to use Handler: you only need to implement the Callback interface Callback, without the need to subclass Handler, as described below:
/** * Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. */ public interface Callback { public boolean handleMessage(Message msg); }
The member variable mAsynchronous identifies whether to process messages asynchronously. If yes, all messages obtained through the Handler obtain are forcibly set to asynchronous.
Similar to whether there is a logoff to distinguish Thread, Handler constructor can also be divided into built-in logoff and external Logoff: If logoff is provided, the message will be processed in the logoff, otherwise, the message will be processed in the Looper of the current thread. Note that the current thread must have Looper. All UI threads have Looper s, because the view/widget implementation uses a large number of messages and requires the UI thread to provide Looper for processing. For details, refer to view. java:
view.java public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true; }
ViewRootImpl.java private void performTraversals() { .... // Execute enqueued actions on every traversal in case a detached view enqueued an action getRunQueue().executeActions(attachInfo.mHandler); ... } static RunQueue getRunQueue() { RunQueue rq = sRunQueues.get(); if (rq != null) { return rq; } rq = new RunQueue(); sRunQueues.set(rq); return rq; } /** * The run queue is used to enqueue pending work from Views when no Handler is * attached. The work is executed during the next call to performTraversals on * the thread. * @hide */ static final class RunQueue { ... void executeActions(Handler handler) { synchronized (mActions) { final ArrayList<HandlerAction> actions = mActions; final int count = actions.size(); for (int i = 0; i < count; i++) { final HandlerAction handlerAction = actions.get(i); handler.postDelayed(handlerAction.action, handlerAction.delay); } actions.clear(); } } }
From the code above, we can see that the view as the base class of all controls provides the post method, which is used to send messages to the UI Thread and process these messages in the Logoff of the UI Thread, the UI Thread must have logoff, which is guaranteed by ActivityThread:
public final class ActivityThread {... final Looper mLooper = Looper.myLooper();}
The UI operation needs to send messages to the UI thread and process these messages in its logoff. This is why we cannot update the UI in a non-UI thread.When a Handler is constructed in a non-UI thread, a RunTimeException is thrown either because the non-UI thread does not have logoff, or even if a logoff is provided, however, this logoff is not the Logoff of the UI thread and cannot process control messages. Therefore, there is a method in ViewRootImpl that forcibly checks whether UI operations are processed in the UI thread checkThread (): The mThread in this method is assigned a value in the ViewRootImpl constructor, which is the UI thread; thread. currentThread () is the current thread for UI operations. If this thread is not a non-UI thread, an exception CalledFromWrongThreadException will be thrown.
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
If you modify the example in "Use Thread asynchronous download image", after downloading the image bitmap, set the ImageView image to the bitmap in the run () function of the Thread, the preceding exception is thrown:
W/dalvikvm(796): threadid=11: thread exiting with uncaught exception (group=0x40a71930)E/AndroidRuntime(796): FATAL EXCEPTION: Thread-75E/AndroidRuntime(796): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.E/AndroidRuntime(796): at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4746)E/AndroidRuntime(796): at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:823)E/AndroidRuntime(796): at android.view.View.requestLayout(View.java:15473)E/AndroidRuntime(796): at android.view.View.requestLayout(View.java:15473)E/AndroidRuntime(796): at android.view.View.requestLayout(View.java:15473)E/AndroidRuntime(796): at android.view.View.requestLayout(View.java:15473)E/AndroidRuntime(796): at android.view.View.requestLayout(View.java:15473)E/AndroidRuntime(796): at android.widget.ImageView.setImageDrawable(ImageView.java:406)E/AndroidRuntime(796): at android.widget.ImageView.setImageBitmap(ImageView.java:421)E/AndroidRuntime(796): at com.example.thread01.MainActivity$2$1.run(MainActivity.java:80)
Currently, Handler constructor is introduced here. Next we will introduce handleMessage and dispatchMessage:
/** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { } /** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
As mentioned above, there are two methods to set the code for Message Processing: one is to set the Callback, and the other is to subclass the Handler. The sub-classes of Handler must implement handleMessage to process custom messages, as in the previous example of anonymous sub-classes. DispatchMessage is called in Logoff: Loop (), that is, it is called in the thread's message processing cycle, so that the Handler can continuously process various messages. In the implementation of dispatchMessage, we can see that if the Message has its own Message processing callback, the Message's own Message processing callback will be called first:
private static void handleCallback(Message message) { message.callback.run(); }
Otherwise, check whether Handler has a message processing callback mCallback. If yes and mCallback successfully processes the message, return. Otherwise, call handleMessage (usually a subclass implementation) to process the message.
Before analyzing the key function Logoff: Loop (), let's take a look at the relationships among Thread, logoff, Handler, and MessageQueue: the Thread must have a logoff to process the message (that is, the logoff is running in the Thread). This is done by calling Logoff: prepare () and logoff: In the run () function of the custom Thread :: loop (), and then get the Message delivered to it by Handler from MessageQueue continuously in Logoff: loop, call the dispatchMessage of the Handler member variable of the Message to process the Message.
Next let's take a look at the logoff constructor:
final MessageQueue mQueue; final Thread mThread; volatile boolean mRun; private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mRun = true; mThread = Thread.currentThread(); }
Logoff constructor is simple. Create MessageQueue and save the current thread to mThread. However, it is private and can only be called through two static functions prepare ()/prepareMainLooper (). prepare () has been introduced earlier. The following describes prepareMainLooper ():
/** * Initialize the current thread as a looper, marking it as an * application's main looper. The main looper for your application * is created by the Android environment, so you should never need * to call this function yourself. See also: {@link #prepare()} */ public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
Preparemainloe is implemented by calling prepare. However, if the input parameter is false, the main logoff cannot be aborted. After the creation, The logoff is kept in the static variable smainloe. The preparemainlogoff method is called in two parts of the Framework:
ServerThread in SystemServer. java does not matter the importance of ServerThread. Most Android services are initialized in this thread. This thread is started using the init2 () method during Android startup:
public static final void init2() { Slog.i(TAG, "Entered the Android system server!"); Thread thr = new ServerThread(); thr.setName("android.server.ServerThread"); thr.start(); }class ServerThread extends Thread { @Override public void run() { ... Looper.prepareMainLooper(); ... Looper.loop(); Slog.d(TAG, "System ServerThread is exiting!"); }}
And the main () method of ActivityThread. java:
public static void main(String[] args) { .... Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
The importance of ActivityThread is self-evident. It is the main thread of Activity, that is, the UI thread. Note that AsyncTask. init () will be described in detail later. Here we only mention that AsyncTask can perform UI operations because init () is called here ().
With the preparations above, we can analyze the key function Logoff: Loop:
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; ... for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } msg.target.dispatchMessage(msg); msg.recycle(); } }
The implementation of loop () is very simple, as we have already said: we constantly get messages from MessageQueue, send messages, and recycle messages. From the code above, we can see that loop () is only a Production Pipeline for continuous cyclic jobs, while MessageQueue provides raw material Message for it to distribute and process. As for how Handler submits a message to MessageQueue, how does MessageQueue manage the message and is to be decomposed below.
Help android thread communication handler message and so on
Inter-thread communication (three methods )::
Public class AnrActivity extends Activity implements OnClickListener {
TextView text;
Handler handler;
ProgressBar bar;
Private class MyAsycnTask extends AsyncTask <URL, Integer, String> {
@ Override
Protected void onProgressUpdate (Integer... values ){
Bar. setProgress (values [0]);
Text. setText ("Progress:" + values [0] + "% ");
Super. onProgressUpdate (values );
}
@ Override
Protected String doInBackground (URL... params ){
For (int I = 0; I <100; I ++ ){
Try {
PublishProgress (I + 1 );
Thread. sleep (100 );
} Catch (InterruptedException e ){
E. printStackTrace ();
}
}
Return "success ";
}
@ Override
Protected void onPostExecute (String result ){
Text. setText (result );
}
}
@ Override
Public void onCreate (Bundle savedInstanceState ){
Super. onCreate (savedInstanceState );
SetContentView (R. layout. main );
Button down = (Button) this. findViewById (R. id. button1 );
Button Cycler = (Button) this. findViewById (R. id. button2 );
Button update = (Button) this. findViewById (R. id. button3 );
Bar = (ProgressBar) this. findViewById (R. id. progressBar1 );
Text = (TextView) this. findViewById (R. id. text );
Update. setOnClickListener (this );
Down. setOnClickListener (this );
Receiver. setOnClickListener (this );
}
@ Override
Public void onClick (final View v ){
Switch (v. getId ()){
Case R. id. button1:
// Communicate through Asycntask
MyAsycnTask myAsycnTask = new MyAsycnTask ();
MyAsycnT ...... remaining full text>
Handler problems in ANDROID
Handler is not equal to the thread used.
Traditional UI architecture, such as swing. Place the rendering display and event distribution in the main thread (UI) thread.
The implementation model of the UI thread is usually an endless loop that constantly accepts messages. Organization distribution
In android, Handler-MessageQueue-Logoff forms the model of this endless loop + message communication. PostDelayed actually puts a Runnable task into MessageQueue and is expected to be executed in 3000 milliseconds.
In addition, do not mistakenly think that Runnable is a thread. In java. util. concurrent, Dogn has clearly separated the execution process from the task. The Runnable interface only indicates a task that can be executed simultaneously or in a new thread.
Why Handler instead of Thread. In addition to the classic mode of the UI framework, the message model also involves the restriction that the UI components do not allow cross-thread access. Whether it is. NET, swing, or android, operations in non-UI threads are not allowed.
Handler is the official channel for asynchronous thread code in the android framework to reach the synchronization thread. From another perspective, this message-based communication mode is sometimes useful. Related examples include IntentService and HandlerThread.