Android thread management-thread communication and android thread Communication
Thread communication, ActivityThread, and Thread classes are the key to understanding Android Thread management.
As the basic unit of CPU scheduling resources, threads play a very important and fundamental role in Android and other operating systems for embedded devices. This section mainly analyzes the following three aspects:
1. Handler, MessageQueue, Message, and logoff relationships
Handler, MessageQueue, Message, and logoff are common topics when developing Android multithreading applications. However, to thoroughly clarify the relationships between them, you need to thoroughly study their respective implementations. First, give a graph between them:
- Logoff depends on MessageQueue and Thread, because each Thread corresponds to only one logoff, and each logoff corresponds to only one MessageQueue.
- MessageQueue depends on Message. Each MessageQueue corresponds to multiple messages. That is, the Message is pushed into MessageQueue to form a Message set.
- Message depends on Handler for processing, and each Message can be processed by a maximum of one Handler. Handler depends on MessageQueue, logoff, and Callback.
According to the operating mechanism, Handler pushes the Message into MessageQueue, and logoff continuously extracts the Message from MessageQueue (when MessageQueue is empty, it enters the sleep state), and its target handler processes the Message. Therefore, to thoroughly understand the thread communication mechanism of Android, you need to understand the following three questions:
- Handler message delivery and processing process
- Attributes and operations of MessageQueue
- How logoff works
1.1 Handler message delivery and processing process
Handler mainly completes the Message queue and processing. The following analyzes the Message distribution and processing processes through the Handler source code. First, let's look at the method list of the Handler class:
We can see that the core methods of the Handler class include: 1) constructor; 2) Sending and distributing messages; 3) Processing messages; 4) sending messages by post; 5) sending messages by send; 6) remove messages and callbacks.
First, from the perspective of constructor polymorphism, the constructor is implemented by calling the following method to assign real parameters to the internal domain of the Handler class.
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;}
Second, the message queue is implemented through the post and send methods.
public final boolean postAtTime(Runnable r, long uptimeMillis) { return sendMessageAtTime(getPostMessage(r), uptimeMillis);}
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageAtTime(msg, uptimeMillis);}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis);}
The difference between the two is that the parameter types are different. The instance object passed in by the post method implements the Runnable interface, and then it is converted to Message through the getPostMessage method internally, and finally sent by the send method; the instance object passed in by the send method is of the Message type. in implementation, the Message is pushed into MessageQueue.
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m;}
After a Message is pushed to MessageQueue through Handler, logoff polls the Message and submits it to the target handler of the Message. Handler first distributes messages. First, judge whether the Callback processing interface of the Message is null. If it is not null, call this Callback for processing. Otherwise, judge whether the Callback interface of the Handler is null, if the value is not null, the Callback is called for processing. If the preceding Callback values are null, The handleMessage method is called for processing.
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}
The handleMessage method must be implemented in the Handler subclass. That is, the specific message processing is implemented by the application software.
/** * Subclasses must implement this to receive messages. */public void handleMessage(Message msg) {}
Return to Activity (Fragment) and implement the handleMessage method in the subclass of Handler. Pay attention to a memory leakage problem. Compare the following two implementation methods: the first is to directly define the implementation of Handler, and the second is to inherit Handler through static internal classes and define the instances of the inherited classes.
Handler mHandler = new Handler () {@ Override public void handleMessage (Message msg) {super. handleMessage (msg); // call the Activity Method Based on msg }};
Static class MyHandler extends Handler {WeakReference <DemoActivity> mActivity; public MyHandler (DemoActivity demoActivity) {mActivity = new WeakReference <DemoActivity> (demoActivity) ;}@ Override public void handleMessage (Message msg) {super. handleMessage (msg); DemoActivity theActivity = mActivity. get (); // call theActivity Method Based on msg}
The first method causes memory leakage, but the second method does not.
In the first method, mHandler is instantiated using an anonymous internal class. in Java,Internal classes strongly hold references to external classes(In the handleMessage method, you can directly call the Activity method.) After an external Activity calls the onDestroy () method, if the Handler's MessageQueue still has unprocessed messages, therefore, because the Handler holds the reference of the Activity, the Activity cannot be recycled by the system GC, causing memory leakage.
In the second method, the Handler inherits the static internal class defined by Handler. Because MyHandler is a static class, even if it is defined inside the Activity, there is no logical connection with the Activity, that is, it does not hold external Activity References. Second, in the static class, it defines the weak reference of external Activity, and the weak reference will be recycled by the system preferentially when the system resources are insufficient. Finally, in the handleMessage () method, use the get method of WeakReference to obtain the reference of the external Activity. If the weak reference has been recycled, The get method returns null.
struct GcSpec { /* If true, only the application heap is threatened. */ bool isPartial; /* If true, the trace is run concurrently with the mutator. */ bool isConcurrent; /* Toggles for the soft reference clearing policy. */ bool doPreserve; /* A name for this garbage collection mode. */ const char *reason;};
This code is defined in dalvik/vm/alloc/Heap. h, whereDoPreserveIf this parameter is set to true, objects referenced by soft references are not recycled during GC execution. If it is set to false, objects referenced by soft references are recycled during GC execution.
Finally, pay attention to the Handler usage, as mentioned in the previous method list. To prevent the Activity from calling onDestroy, there is still a Message in the MessageQueue of Handler. Generally, the removeCallbacksAndMessages () method is called in onDestroy.
@ Overrideprotected void onDestroy () {super. onDestroy (); // clear the Message Queue myHandler. removeCallbacksAndMessages (null );}
public final void removeCallbacksAndMessages(Object token) { mQueue.removeCallbacksAndMessages(this, token);}
The removeCallbacksAndMessages () method removes the callback and send messages sent by post whose obj is the token. When the token is null, all callback and messages are removed.
1.2 attributes and operations of MessageQueue
MessageQueue, a message queue. Its Attributes are similar to those of common queues, including queuing and queuing. Here we will briefly introduce the implementation of MessageQueue.
First, MessageQueue creates a queue by calling the local method nativeInit in its constructor. NativeInit creates the NativeMessageQueue object and assigns the value to the MessageQueue member variable mPtr. MPtr is an int type data that represents the memory pointer of NativeMessageQueue.
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit();}
Second, the Message queue is implemented through the enqueueMessage method. First, check whether the message meets the requirements (whether the target handler is in use and whether the target handler is null). Then, set prev. next = msg to complete the queuing operation.
boolean enqueueMessage(Message msg, long when);
Again, the team goes throughNext ()Method. Synchronization and lock issues are involved. We will not detail them here.
Again, there are two implementations for deleting elements. P. callback = r and p. what = what are used to identify messages respectively.
void removeMessages(Handler h, int what, Object object);void removeMessages(Handler h, Runnable r, Object object);
Finally, the destruction queue is the same as the creation of the queue, which is completed through a local function. The input parameter is the memory pointer of MessageQueue.
private native static void nativeDestroy(int ptr);
1.3 How logoff works
Logoff is the key to thread communication. It is precisely because of logoff that the entire thread communication mechanism can truly achieve "communication ".
In the process of application development, when the main thread needs to pass messages to the user's custom thread, it will define Handler in the Custom thread for message processing, the prepare () method and loop () method of logoff are called before and after Handler implementation. The general implementation is as follows:
new Thread(new Runnable() { private Handler mHandler; @Override public void run() { Looper.prepare(); mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; Looper.loop(); }});
The prepare () and loop () methods are highlighted here. We do not recommend that anonymous threads be defined in actual projects.
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));}
We can see that the focus of the prepare method is the sThreadLocal variable. What is the sThreadLocal variable?
// sThreadLocal.get() will return null unless you've called prepare().static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
ThreadLocal implements local thread storage. Taking a brief look at its class annotation document, ThreadLocal is a special global variable. It stores data related to its own thread and is inaccessible to other threads.
/** * Implements a thread-local storage, that is, a variable for which each thread * has its own value. All threads share the same {@code ThreadLocal} object, * but each sees a different value when accessing it, and changes made by one * thread do not affect the other threads. The implementation supports * {@code null} values. * * @see java.lang.Thread * @author Bob Lee */public class ThreadLocal<T> {}
Return to the prepare method, sThreadLocal adds a logoff object for the current thread. The prepare method can be called only once. Otherwise, a running exception is thrown.
After initialization, how does Handler ensure that messages are delivered to the MessageQueue held by logoff through the post and send methods? In fact, MessageQueue is a bridge between Handler and logoff. The Handler initialization method is mentioned in the Handler chapter. The Handler mlogoff object is obtained through myloiter (), while myloiter () is obtained by calling sThreadLocal. get (), that is, the Handler's mloue is the Looper object of the current thread, and the Handler's mQueue is mloue. mQueue.
……mLooper = Looper.myLooper();if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;……
public static Looper myLooper() { return sThreadLocal.get();}