-
- preface
- Questions
- Why does Looper loop death not cause apps to freeze
- What is the message loop mechanism for the main thread
- What motivates ActivityThread
- How does Handler handle thread switching
- What methods do child threads have to update the UI
- runOnUiThread
- Create a Handler and pass in the getMainLooper
- ViewpostRunnable r
- ToastshowDialog method in the child thread
- How do I handle memory leaks caused by improper use of Handlers
- conclusion
preface
Android message mechanism is mainly refers to the Handler mechanism, for everyone is already familiar with the Handler, but really master the Handler? This paper mainly through several questions around the Handler in-depth and expanded understanding. This article may require some theoretical knowledge of the Activity startup process. And have some understanding of Handler.
Handler has been introduced to the classic good article, so the wall crack recommended first understand the following 2 articles. Gityuan – Message mechanism Handler
Standing on the shoulders of giants will see further. If you are interested, you can also go to Gityuan’s blog to learn more about it. It is all dry goods. What’s more, he writes with authority. After all, he is also a key member of Xiaomi’s system engineers.
Questions
- Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources?
- What is the message loop mechanism for the main thread (how does an infinite loop process other transactions)?
- What motivates ActivityThread? (ActivityThread what thread executes Looper)
- How does the Handler switch threads to send messages? (Thread to thread communication)
- What methods do child threads have to update the UI?
- Toast showDialog method in the child thread. (Does it have to do with child threads not being able to update the UI?)
- How do I handle memory leaks caused by improper use of Handlers?
1. Why does Looper loop death not cause apps to freeze?
Threads do not have Looper by default. If you want to use a Handler, you must create a Looper for the thread. The main thread we often refer to, also known as the UI thread, is an ActivityThread. An ActivityThread initializes a Looper when it is created, which is why you can use a Handler in the main thread by default.
Let’s start with a piece of code
new Thread(new Runnable() {
@Override
public void run() {
Log.e("qdx"."step 0 ");
Looper.prepare(a);
Toast.makeText(MainActivity.this."run on Thread", Toast.LENGTH_SHORT).show(a);
Log.e("qdx"."step 1 ");
Looper.loop(a);
Log.e("qdx"."step 2 ");}}).start(a);Copy the code
We know thatLooper.loop();
An infinite loop is maintained, so in theory the above code should execute
Step 0 – > step 1
So the cycle is inLooper.prepare();
withLooper.loop();
In between.
In a child thread, if a Looper is manually created for it, the quit method should be called to terminate the message loop after everything is done. Otherwise, the child thread will remain in a waiting (blocking) state. If Looper is quit, the thread will terminate immediately. It is therefore recommended to terminate Looper when it is not needed.
The result is exactly what we said. If we know about ActivityThread, and in the main method we see that the main thread also maintains a message loop in Looper mode.
public static void main(String[] args) {
` `` `` `
Looper.prepareMainLooper();// Create Looper and MessageQueue objects to handle messages for the main thread
ActivityThread thread = new ActivityThread();
thread.attach(false);// Create a new thread.
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
// The application crashes or exits if you execute the following method...
throw new RuntimeException("Main thread loop unexpectedly exited");
}Copy the code
So back to our question, does this loop cause the application to freeze, and even if it doesn’t, does it slowly consume more and more resources?
From: Gityuan
A thread is a piece of executable code. When the executable code completes, the thread life cycle terminates and the thread exits. For the main thread, we do not want to be run for a period of time, and then exit, so how to ensure that it is always alive? Binder threads, for example, also use an infinite loop to write and write to binder drivers in a different way. Of course, they do not simply loop forever, falling asleep when there is no message. But that might raise another question: how do you do anything else if it’s an infinite loop? By creating a new thread. Really getting stuck the operation of the main thread is in the callback method onCreate/onStart/onResume operating time is too long, can lead to a frame, even ANR, stars. The loop itself does not result in application.
Is the main thread running in an endless loop particularly CPU intensive? If the main thread MessageQueue has no message, it blocks the nativePollOnce() method in the loop queue.next(). The main thread then releases CPU resources and goes to sleep until the next message arrives or a transaction occurs, waking up the main thread by writing data to the pipe end. The epoll mechanism adopted here is an IO multiplexing mechanism, which can monitor multiple descriptors at the same time. When a descriptor is ready (read or write ready), it immediately notifies the corresponding program to carry out read or write operations, which is essentially synchronous I/O, that is, read and write is blocked. Therefore, the main thread is dormant most of the time and does not consume a lot of CPU resources. Gityuan – Handler (Native)
What is the message loop mechanism of the main thread?
In fact, a new binder thread is created before it goes into an infinite loop, in code activityThread.main () :
public static void main(String[] args) {
....
// Create Looper and MessageQueue objects to handle messages for the main thread
Looper.prepareMainLooper();
// Create an ActivityThread object
ActivityThread thread = new ActivityThread();
// Create a new thread.
thread.attach(false);
Looper.loop(); // The message loop runs
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code
The Activity’s life cycle depends on the main thread’s looper. loop, which takes action when different messages are received: Once you exit the Message loop, your program can exit. Fetching a message from a message queue may block and will be processed accordingly. If a message takes too long to process, it can affect the refresh rate of the UI thread and cause a lag.
The Attach (false) method creates a Binder thread (ApplicationThread, the server side of the Binder that receives events from AMS). The Binder thread sends messages to the main thread through a Handler. “Activity Start Process”
Such as received MSG = H.L AUNCH_ACTIVITY, call the ActivityThread. HandleLaunchActivity () method, which will eventually through reflection mechanism, create the Activity instance, and then execute the Activity. The onCreate () method;
Such as MSG = have received H.P AUSE_ACTIVITY, call the ActivityThread. HandlePauseActivity () method, which will eventually perform the Activity. The onPause () method.
Where does the main thread come from? Of course, other threads in the App process send messages to the main thread via Handler
System_server process
The system_server process is the system process, the core carrier of the Java Framework framework, which runs a large number of system services, such as ApplicationThreadProxy (ABBREVIATED as ATP), ActivityManagerService (AMS for short). Both services run in different threads of the System_server process. Since ATP and AMS are based on IBinder interfaces, they are both binder threads. The creation and destruction of binder threads are determined by binder drivers.
App process
App processes are commonly referred to as applications. The main thread is mainly responsible for the life cycle of activities/Services and other components and UI-related operations run in this thread. Binder Threads ApplicationThread(AT) and ActivityManagerProxy (AMP) are AT least two binder threads in each App process
Binder
A Binder is used to communicate between processes. A Binder client of one process sends a transaction to a server of another process. For example, thread 2 sends a transaction to thread 4. Handlers are used to communicate between different threads in the same process, such as thread 4 in the figure sending messages to the main thread.
The lifecycle of an Activity, such as suspending an Activity, is shown as follows:
- AMS of thread 1 calls ATP of thread 2; (Because the threads of the same process share resources, they can call each other directly, but need to pay attention to the problem of multi-threaded concurrency.)
- Thread 2 is transmitted to thread 4 of App process through binder;
- Thread 4 sends a message suspending the Activity to the main thread through the handler message mechanism.
- The main thread in the stars. The loop () to iterate over the message, when I received the news of suspension of the Activity, will be distributed to ActivityThread message. H.h andleMessage () method, through the method of calls, Finally, activity.onpause () is called, and when onPause() is finished, the loop continues.
Supplement:
The main method of ActivityThread is basically a message loop, and once you exit the message loop, your application can exit.
Fetching a message from a message queue may block and will be processed accordingly. If a message takes too long to process, it can affect the refresh rate of the UI thread and cause a lag.
Finally, through a paragraph of “Android Development art Exploration” summary:
ActivityThread communicates with AMS through the ApplicationThread. The Binder method is called back by AMS when the ApplicationThread requests the ActivityThread. The ApplicationThread then sends a message to H, which then switches the logic in the ApplicationThread to the ActivityThread for execution. Message loop model for the main thread
In addition, ActivityThreads are not actually threads, and unlike the HandlerThread class, activityThreads do not really inherit from the Thread class
If an ActivityThread is not a Thread, which Thread does Looper bind to? (Dig into the source code to parse the “power” described in the Handler end diagram.)
3. What motivates ActivityThread?
Process A process is created before each APP runs. This process is produced by Zygote fork and is used to host various activities/services running on the app. Processes are completely transparent to the top applications, which is a deliberate move by Google to make apps run in the Android Runtime. In most cases an App runs in a process, unless the Android: Process attribute is configured in androidmanifest.xml, or the process is forked through native code. Threads Threads are common in applications, such as creating a new Thread every time a new Thread().start is created. From the perspective of Linux, there is no essential difference between a process and a thread except whether they share resources. They are both a task_struct structure. In the view of CPU, a process or thread is nothing more than a piece of executable code. Ensure that each task has as fair a slice of CPU time as possible.
The main thread hosting ActivityThread is the process created by Zygote fork.
4. How does Handler enable thread switching
In fact, we can see from the above, threads share resources. So Handler handles different threads just by looking at the asynchronous case. Here are a few things about Handler. The Handler is created using the current thread’s Looper to construct the message loop. The Looper is bound to the thread in which it was created, and the Handler processes the message in the thread in which it is associated with the Looper. (Knocks on the blackboard)
So how does the Handler internally get the Looper of the current thread — ThreadLocal? ThreadLocal can store and provide data in different threads without interfering with each other. ThreadLocal can easily fetch Looper for each thread. If you want to use a Handler, you must create a Looper for the thread. The main thread, also known as the UI thread, is an ActivityThread. The Looper is initialized when an ActivityThread is created, which is why we can use handlers in the main thread by default.
Why does the system not allow access to the UI in child threads? This is because Android UI controls are not thread-safe. If concurrent access in multiple threads can cause the UI controls to be in an unexpected state, why not lock access to the UI controls? There are two disadvantages: (1) adding locking mechanism will complicate the UI access logic and (2) locking mechanism will reduce the EFFICIENCY of UI access, because locking mechanism will block the execution of some threads. So the simplest and most efficient way to handle UI operations is to adopt a single-threaded model.
So again, the child thread must not update the UI, right? Hongyang said he loved brother: Why did hundreds of female donkeys scream so loudly at night? Why do condoms get robbed in grocery stores? Why are underwear stolen from girls’ dormitories? Serial sow rapes. Who’s responsible? Was it a man or a ghost when the old nun’s door was knocked at night? What lies behind the accidental deaths of hundreds of bitches? Behind all this, is the distortion of human nature or the collapse of morality? Is it sexual explosion or desperate helplessness? Stay tuned for tonight’s spotlight interview: all the stories I have to tell.
This leaves us with two more things to look at in the next part: the drawing mechanism of a View and the internal mechanism of Android Window.
5. What methods does the child thread have to update the UI?
- The main thread defines the Handler, the child thread sends the message through the mHandler, the main thread Handler
handleMessage
Update the UI. - Use the Activity object’s runOnUiThread method.
- Create Handler, pass in
getMainLooper
. - The post (Runnable r).
runOnUiThread
We’re not going to analyze the first one, but we’re going to look at the second one that’s more commonly used.
Let’s go over what we said above
The Looper is bound to the thread in which it is created, and the Handler processes the message in the thread in which it is associated with the Looper. (Knocks on the blackboard)
new Thread(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
//DO UI method}}); } }).start();Copy the code
//Activity
final Handler mHandler = new Handler();
public final void runOnUiThread(Runnable action) {
if(Thread.currentThread() ! = mUiThread) { mHandler.post(action);// Child thread (non-UI thread)
} else{ action.run(); }}Copy the code
If you enter the Activity class, you can see the update UI message sent by mHandler if it is in a child thread. This Handler is created in the Activity, that is, in the main thread, so it is no different from the main thread where we use the Handler to update the UI. Because this Looper is the Looper created in ActivityThread (looper.prepareMainLooper ()).
Create Handler, pass ingetMainLooper
In the same way, can we create a Handler in the child thread and get the MainLooper to update the UI in the child thread? First we see that in the Looper class we have the static object sMainLooper, and this sMainLooper is the MainLooper created in ActivityThread.
private static Looper sMainLooper; // guarded by Looper.class
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if(sMainLooper ! =null) {
throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code
So needless to say, we can use this sMainLooper to update the UI.
new Thread(new Runnable() {
@Override
public void run() {
Log.e("qdx"."step 1 "+Thread.currentThread().getName());
Handler handler=new Handler(getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
//Do Ui method
Log.e("qdx"."step 2 "+Thread.currentThread().getName()); }}); } }).start();Copy the code
View.post(Runnable r)
As usual, we click on the source code
//View
/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
*/
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if(attachInfo ! =null) {
return attachInfo.mHandler.post(action); // In general, go here
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
/** * A Handler supplied by a view's {@link android.view.ViewRootImpl}. This * handler can be used to pump events in the UI events queue. */
final Handler mHandler;
Copy the code
This Handler can handle UI events, so its Looper is also the main thread’s sMainLooper. This means that most UI updates are done through handlers.
In addition, UI updates can also be implemented via AsyncTask. Yes, also via Handler…
Handler
It is
6. Toast, showDialog, method in child thread.
Some people might look at this and think: child threads can’t update the UI anyway
It also says how to update the UI
Hold on, brother, and let me finish my story
new Thread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this."run on thread", Toast.LENGTH_SHORT).show();// No doubt crash
}
}).start();Copy the code
If you look at this crash log, it’s a little confusing, because normally if the child thread can’t update the UI control, it will report the following error:
Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler To meet you.
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(MainActivity.this."run on thread", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();Copy the code
This gives the child thread Toast. ? As usual, let’s get to the bottom of Toast’s internal implementation.
//Toast
/** * Show the view for the specified duration. */
public void show() {
``````
INotificationManager service = getService();// Get a service named Notification from SMgr
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);//enqueue? Does it have to do with the queue of the Handler?
} catch (RemoteException e) {
// Empty}}Copy the code
In the show method, we see that Toast’s show method is different from normal UI controls and also performs Toast drawing using the Binder interprocess communication method. It would not be in the process of the much discussed, interested in NotificationManagerService class analysis.
Binder’s native TN class is a Binder’s native TN class. Binder’s native TN class is Binder’s native TN class. In the show method of Toast, will pass this TN object NotificationManagerService to communication! And we also found its show method in TN.
private static class TN extends ITransientNotification.Stub {//Binder server implementation class
/** * schedule handleShow into the right thread */
@Override
public void show(IBinder windowToken) {
mHandler.obtainMessage(0, windowToken).sendToTarget();
}
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) { IBinder token = (IBinder) msg.obj; handleShow(token); }}; }Copy the code
Watching the code above, know the child thread Toast in the cause of the error, because in TN use Handler, so you need to create stars. Now that the Handler is used to send the message, you can find the method to update the Toast in the handleMessage. See handleMessage handled by handleShow.
Public void handleShow(IBinder windowToken) {`````` mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
mParams.token = windowToken;
if (mView.getParent() != null) {
mWM.removeView(mView);
}
mWM.addView(mView, mParams); // Use the addView method of WindowManager
trySendAccessibilityEvent();}}Copy the code
See here can be summed up:
Toast is essentially displayed and drawn by the Window, and the main thread cannot update the UI due to the behavior of ViewRootImpl’s checkThread method in the View tree maintained by the Activity. The TN class in Toast uses Handler to queue and time-control the Toast display, so looper.prepare () is used in the child thread to prevent an exception from being thrown when TN is created. And the stars. The loop (); (This is not recommended, however, because it prevents the thread from executing and causes a memory leak.)
So does Dialog. At the same time, we have another knowledge point to study: what is Windows in Android, what is its internal mechanism?
7. How to handle memory leaks caused by improper use of Handlers?
First create a Looper in the child thread above for program effect using the following method
Looper.prepare();
` `` `` `
Looper.loop(a);Copy the code
In fact, this is a very dangerous approach
In a child thread, if you manually create a Looper for it, you should call quit to terminate the message loop after everything is done. Otherwise, the child thread will remain in a waiting state. If you exit the Looper, the thread will terminate immediately. (【 stars. MyLooper (). The quit ();. )
If a message is processed in the Handler’s handleMessage (or run) method, it will remain in the message queue of the main thread and will affect the system’s ability to reclaim the Activity, causing a memory leak.
For details, see Handler Memory Leak analysis and troubleshooting
To summarize, there are two main points to address Handler memory leaks
- There are delayed messages that need to be removed when the Activity is destroyed
Messages
- Leaks caused by anonymous inner classes are changed to anonymous static inner classes, and weak references are made to the context or Activity.
conclusion
Handler was able to free up so much spray, and at the same time thanks to the previous fumble.
We also need to explore Java GC mechanisms and memory leaks. What is Windows in Android, and what is its internal mechanism? Android View drawing mechanism
Handler Gityuan – Message mechanism Handler