Handler is a message processing mechanism in Android, it is a solution for communication between threads, and you can also understand that it is natural for us to create a queue in the main thread, the order of messages in the queue is the time we set the delay. If you want to implement a queue in Android, Think about it first. This paper is divided into three parts:
- Handler source code and FAQ answers
- What is the maximum number of handlers in a thread, Looper, MessageQueue?
- Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources?
- How does the child thread update the UI, such as Dialog, Toast, etc.? Why does the system not recommend updating the UI in child threads?
- How does the main thread access the network?
- How do I handle memory leaks caused by improper use of handlers?
- Handler message priority, what are the application scenarios?
- When does the main thread Looper exit? Can I log out manually?
- How to determine whether the current thread is the main thread of Android?
- The correct way to create a Message instance?
- Handler In-depth problem solving
- ThreadLocal
- Epoll mechanism
- Handle Synchronization barrier mechanism
- Handler lockrelated problems
- A synchronization method in Handler
- Some applications of Handler in systems and third-party frameworks
- HandlerThread
- IntentService
- How to build an APP that doesn’t crash
- Glide application
Handler source code and FAQ answers
Here’s the official definition:
A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.Copy the code
Handler allows you to send a Message/Runnable to a thread’s MessageQueue. Each Handler instance is associated with a thread and the Message queue for that thread. When you create a Handler, it should be bound to a Looper (the main thread already creates a Looper by default, and the child thread needs to create its own Looper). It sends a Message/Runnable to the corresponding Message queue of Looper and processes the corresponding Message/Runnable in that thread of Looper. The following diagram shows how this Handler works
Workflow flow chart of Handler
It can be seen that in Thread, the conveyor belt of Looper is actually a dead loop, which continuously takes messages from MessageQueue MessageQueue and finally delivers them to handler. dispatchMessage for message distribution. The Handler sendXXX, Handler. PostXXX these methods the MessageQueue messages sent to the queue, the entire model is actually a producers, consumers, a steady stream of news production, processing messages, no news for dormancy. MessageQueue is a priority queue consisting of a single linked list (the headers are taken, so it is a queue).
As mentioned earlier, when you create a Handler you should bind it to a Looper (binding can also be interpreted as creating a Looper, the main thread already creates a Looper by default, and the child thread needs to create a Looper by itself), so let’s first look at how this is handled in the main thread:
//ActivityThread.java
public static void main(String[] args) {
···
Looper.prepareMainLooper();
···
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Copy the code
As you can see in the main method of ActivityThread, we call looper.prepareMainLooper (), then get the Handler of the current thread, and finally call looper.loop (). Let’s take a look at the looper.prepareMainLooper () method
//Looper.java
/**
* 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(a) {
prepare(false);
synchronized (Looper.class) {
if(sMainLooper ! =null) {
throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}//prepare
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));
}
Copy the code
PrepareMainLooper () creates a Looper for the current thread and stores the Looper instance in the thread-local variable sThreadLocal(ThreadLocal), i.e. each thread has its own Looper. PrepareMainLooper creates a MessageQueue for that thread when Looper is created. PrepareMainLooper determines if sMainLooper has a value. If it is called multiple times, it will throw an exception, so the main thread will have only one Looper and MessageQueue. The prepare(true) method will be called when looper.prepare () is called in a child thread. The prepare(true) method will be called when looper.prepare () is called in a child thread.
//Looper.java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
SMainThreadHandler = thread.gethandler (). The getHandler actually gets the mH Handler.
//ActivityThread.java
final H mH = new H();
@UnsupportedAppUsage
final Handler getHandler(a) {
return mH;
}
Copy the code
MH this Handler is an inner class of ActivityThread. If you look at the handMessage method, you can see that this Handler handles messages for the four components, applications, etc., such as creating a Service and binding messages to the Service.
//ActivityThread.java
class H extends Handler {...public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXIT_APPLICATION:
if(mInitialApplication ! =null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case BIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
handleBindService((BindServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case UNBIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
handleUnbindService((BindServiceData)msg.obj);
schedulePurgeIdler();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SERVICE_ARGS:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
handleServiceArgs((ServiceArgsData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
handleStopService((IBinder)msg.obj);
schedulePurgeIdler();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break; ...case APPLICATION_INFO_CHANGED:
mUpdatingSystemConfig = true;
try {
handleApplicationInfoChanged((ApplicationInfo) msg.obj);
} finally {
mUpdatingSystemConfig = false;
}
break;
case RUN_ISOLATED_ENTRY_POINT:
handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
(String[]) ((SomeArgs) msg.obj).arg2);
break;
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
// Client transactions inside system process are recycled on the client side
// instead of ClientLifecycleManager to avoid being cleared before this
// message is handled.
transaction.recycle();
}
// TODO(lifecycler): Recycle locally scheduled transactions.
break;
case RELAUNCH_ACTIVITY:
handleRelaunchActivityLocally((IBinder) msg.obj);
break;
case PURGE_RESOURCES:
schedulePurgeIdler();
break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: "+ codeToString(msg.what)); }}Copy the code
Finally, we look at the looper.loop () method
//Looper.java
public static void loop(a) {
// Get Looper from ThreadLocal
finalLooper me = myLooper(); ...finalMessageQueue queue = me.mQueue; ...for (;;) { / / death cycle
// Get the message
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return; }, MSG. Target. DispatchMessage (MSG); ...// recyclemsg.recycleUnchecked(); }}Copy the code
The loop method is an infinite loop that fetches queue.next() from the message queue and dispatches the message to the Handler(msg.target) without any binding. Since the Handler has only one Looper and one MessageQueue per thread, it is the Handler that calls the looper.loop () method. Looper.loop() continues to fetch messages in an endless loop that is eventually recycled.
The target(Handler) parameter in Message is highlighted here. It is this variable that allows each Message to find a corresponding Handler for Message distribution, allowing multiple handlers to work simultaneously.
What do we do in the child thread? First create a Handler in the child thread and send a Runnable
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
new Thread(new Runnable() {
@Override
public void run(a) {
new Handler().post(new Runnable() {
@Override
public void run(a) {
Toast.makeText(HandlerActivity.this."toast",Toast.LENGTH_LONG).show(); }}); } }).start(); }Copy the code
The error log tells you that you need to call looper.prepare () in the child thread, essentially creating a Looper to “associate” with your Handler.
--------- beginning of crash
2020-11- 0915:51:03.938 21122-21181/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.jackie.testdialog, PID: 21122
java.lang.RuntimeException: CanT create handler inside thread Thread thread [thread-2,5,main] that has not called Looper. Prepare () at android.os.Handler.
(Handler.java:207) at android.os.Handler.
(Handler.java:119) at com.jackie.testdialog.HandlerActivity$1.run(HandlerActivity.java:31) at java.lang.Thread.run(Thread.java:919)
Copy the code
Add looper.prepare () to create Looper while calling looper.loop () to begin processing the message.
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
new Thread(new Runnable() {
@Override
public void run(a) {
// create Looper, MessageQueue
Looper.prepare();
new Handler().post(new Runnable() {
@Override
public void run(a) {
Toast.makeText(HandlerActivity.this."toast",Toast.LENGTH_LONG).show(); }});// Start processing messages
Looper.loop();
}
}).start();
}
Copy the code
It is important to note that the quit method should be called to terminate the message loop after everything is done, otherwise the child thread will remain in the loop waiting state, so terminate Looper when it is not needed, calling looper.mylooper ().quit().
Android does not allow you to update the UI on child threads. This is not the case. In ViewRootImpl, the checkThread method checks the mThread. CurrentThread (),mThread is initialized in the ViewRootImpl constructor, which means that a ViewRootImpl Thread must be created on the same Thread that called the checkThread. UI updates are not limited to the main thread.
void checkThread(a) {
if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views."); }}Copy the code
Here we need to introduce some concepts. Window is the Window in Android. Every Activity and Dialog and Toast corresponds to a specific Window respectively. A Window and a View are linked by the View View pl, so it exists as a View. If we look at the creation of the ViewRootImpl in Toast, the show method calling Toast will eventually call its handleShow method
//Toast.java
public void handleShow(IBinder windowToken) {...if(mView ! = mNextView) {// Since the notification manager service cancels the token right
// after it notifies us to cancel the toast there is an inherent
// race and we may attempt to add a window after the token has been
// invalidated. Let us hedge against that.
try {
mWM.addView(mView, mParams); // Create the ViewRootImpl
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */}}}Copy the code
The final implementer of this mWM (WindowManager) is Windows ManagerGlobal, whose addView method creates the View wrootimpl, Then go to root.setView(View, wparams, panelParentView), update the interface through ViewRootImpl and complete the adding process of Window.
//WindowManagerGlobal.java
root = new ViewRootImpl(view.getContext(), display); / / create ViewRootImpl
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//ViewRootImpl
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throwe; }}Copy the code
The setView uses requestLayout to complete the asynchronous refresh request, and also calls the checkThread method to check the validity of the thread.
@Override
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code
Therefore, our ViewRootImpl is created in a child thread, so the value of mThread is also a child thread. At the same time, our update is also in a child thread, so there is no exception. At the same time, we can refer to this article for analysis, write very detailed. Similarly, the following code verifies this case
// called in the child thread
public void showDialog(a){
new Thread(new Runnable() {
@Override
public void run(a) {
// create Looper, MessageQueue
Looper.prepare();
new Handler().post(new Runnable() {
@Override
public void run(a) {
builder = new AlertDialog.Builder(HandlerActivity.this);
builder.setTitle("jackie"); alertDialog = builder.create(); alertDialog.show(); alertDialog.hide(); }});// Start processing messages
Looper.loop();
}
}).start();
}
Copy the code
Call the showDialog method in the child thread, calling alertdialog.show () and alertdialog.hide (). The hide method hides the Dialog and does nothing else (it does not remove the Window). Then call alertdialog.show () on the main thread; Only the original thread that created a view Hierarchy can touch its views.
2020-11- 0918:35:39.874 24819-24819/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.jackie.testdialog, PID: 24819
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)
at android.view.View.requestLayout(View.java:24454)
at android.view.View.setFlags(View.java:15187)
at android.view.View.setVisibility(View.java:10836)
at android.app.Dialog.show(Dialog.java:307)
at com.jackie.testdialog.HandlerActivity$2.onClick(HandlerActivity.java:41)
at android.view.View.performClick(View.java:7125)
at android.view.View.performClickInternal(View.java:7102)
Copy the code
So the key to updating the UI in a thread is whether the ViewRootImpl that created it is the same thread as the checkThread.
How do I access the network from the main thread
Add the following code before the network request
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
StrictMode.setThreadPolicy(policy);
Copy the code
StrictMode has been introduced with Android2.3 to check for two major problems: ThreadPolicy and VmPolicy. StrictMode can be used to perform network operations on the main thread, which is not recommended. You can check out the rigor mode here.
Why does the system not recommend accessing 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, then why does the system not lock access to the UI controls? There are two disadvantages:
-
Adding locks in the first place complicates the logic of UI access
-
Locking reduces the efficiency of UI access because it blocks the execution of certain threads.
So the simplest and most efficient way to handle UI operations is to adopt a single-threaded model. (Android development art exploration)
How a child thread notifies the main thread to update the UI (Handle sends a message to the main thread to manipulate the UI)
- The main thread defines a Handler, the child thread sends a message through the mHandler, and the main thread Handler’s handleMessage updates the UI.
- Use the Activity object’s runOnUiThread method.
- Create a Handler and pass in getMainLooper.
- The post (Runnable r).
Why doesn’t the Looper loop cause applications to freeze and consume a lot of resources?
As can be seen from the previous analysis of the main thread and child thread, the Looper will continuously retrieve messages in the thread. If it is a child thread’s Looper loop, once the task is completed, the user should manually exit, rather than let it sleep and wait. (Quote from Gityuan) A thread is simply a piece of executable code, and when the executable code completes, the thread’s 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. Android is based on message processing, and the user’s actions are all in this Looper loop, where we click on the screen during sleep to wake up the main thread and continue working.
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.
When the main thread Looper exits
When the App exits, the mH (Handler) in the ActivityThread receives the message and exits.
//ActivityThread.java
case EXIT_APPLICATION:
if(mInitialApplication ! =null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
Copy the code
If you try to exit the main thread Looper manually, the following exception is thrown
Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
at android.os.MessageQueue.quit(MessageQueue.java:428)
at android.os.Looper.quit(Looper.java:354)
at com.jackie.testdialog.Test2Activity.onCreate(Test2Activity.java:29)
at android.app.Activity.performCreate(Activity.java:7802)
at android.app.Activity.performCreate(Activity.java:7791)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Copy the code
Why is it not allowed to exit? Because the main thread is not allowed to exit, and exiting means the program is dead, and exiting shouldn’t be done that way.
Handler Specifies the order in which messages are processed
The following line of code is executed when Looper executes the message loop(), MSG. Targe is the Handler object
msg.target.dispatchMessage(msg);
Copy the code
Let’s look at the dispatchMessage source code
public void dispatchMessage(@NonNull Message msg) {
if(msg.callback ! =null) {
handleCallback(msg);
} else {
// If callback processes the MSG and returns true, handleMessage is not called again
if(mCallback ! =null) {
if (mCallback.handleMessage(msg)) {
return; } } handleMessage(msg); }}Copy the code
-
If the Message object has a CallBack, the CallBack is actually a Runnable, and the CallBack is executed.
Message msgCallBack = Message.obtain(handler, new Runnable() { @Override public void run(a) {}});Copy the code
The handleCallback method calls the Runnable run method.
private static void handleCallback(Message message) { message.callback.run(); } Copy the code
-
If the Message object does not have a CallBack, the else branch checks whether the Handler’s CallBack is null, executes the handleMessage method of the CallBack, and returns the Handler’s CallBack as follows:
-
Handler.Callback callback = new Handler.Callback() { @Override public boolean handleMessage(@NonNull Message msg) { //retrun true does not perform the following logic, can be used to do priority processing return false; }};Copy the code
-
Finally, we call Handler’s handleMessage() function, which we often override, to handle the message.
Usage scenarios
If a message is processed by Callback and then intercepted (returns true), then Handler’s handleMessage(MSG) method is not called. If a Callback processes a message but does not intercept it, it means that a message can be processed by both Callback and Handler. We can use the CallBack interception to intercept Handler messages.
Scenario: Hook ActivityThread.mh. In ActivityThread, there is a member variable, mH, which is a Handler and an extremely important class. Almost all plug-in frameworks use this method.
Handler.post(Runnable r) method execution logic
Post (Runnable r) {Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r: Runnable r
//Handler.java
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Copy the code
The Runnable object is wrapped as a Message object, and the Runnable object is the Message’s CallBack object, which has precedence over execution.
How does Handler do thread switching
SendXXX, handler.postXXX and other methods to send messages, and then looper.loop () to retrieve messages in the message queue. Finally, the handle.dispatchMessage method is used for message distribution.
How do I handle memory leaks caused by improper use of handlers?
-
Messages have delay in timely remove Message/Runnable interface was off, call handler. RemoveCallbacksAndMessages (null)
-
Replace memory leaks caused by inner classes with static inner classes and use weak references to context or Activity/Fragment.
Specific analysis and resolution of memory leaks can be found in this article. There is also a key point, if there is a delay message, when the interface closed, the message in the Handler has not finished processing, then what is the final processing of the message? After testing, for example, I delayed sending a message 10 seconds after opening the interface, closed the interface, and eventually received the message in the handMessage method of Handler (created by the anonymous inner class) (printing the log). Because there is a MessageQueue -> Message -> Handler -> Activity reference chain, the Handler is not destroyed and the Activity is not destroyed.
Create the Message instance correctly
- Static methods via Message
Message.obtain()
To obtain; - Public method via Handler
handler.obtainMessage()
All messages are recycled and put into sPool using the meta-design pattern.
Handler In-depth problem solving
ThreadLocal
ThreadLocal provides each thread with a copy of a variable so that each thread is not accessing the same object at any one time, thus isolating multiple threads from sharing data.
If you’re designing a ThreadLocal, and the goal of a ThreadLocal is to have different variables V for different threads, the most straightforward way to do this is to create a Map where the Key is the thread and the Value is the variable V owned by each thread. A ThreadLocal holds such a Map internally. You might design it like this
The Java implementation also has a Map called ThreadLocalMap, but instead of ThreadLocal, ThreadLocalMap is held by Thread. The class Thread has a private property called threadLocals, which is of type ThreadLocalMap and whose Key is ThreadLocal.
The simplified code looks like this
class Thread {
// Internal holds ThreadLocalMap
ThreadLocal.ThreadLocalMap
threadLocals;
}
class ThreadLocal<T>{
public T get(a) {
// Get the thread held first
//ThreadLocalMap
ThreadLocalMap map =
Thread.currentThread()
.threadLocals;
/ / in ThreadLocalMap
// Find variables
Entry e =
map.getEntry(this);
return e.value;
}
static class ThreadLocalMap{
// Inside is an array instead of a Map
Entry[] table;
// Find Entry based on ThreadLocal
Entry getEntry(ThreadLocal key){
// omit the lookup logic
}
/ / Entry definition
static class Entry extends
WeakReference<ThreadLocal>{ Object value; }}}Copy the code
In the Java implementation, ThreadLocal is only a proxy tool class that does not hold any thread-related data. All thread-related data is stored in the Thread. In this design, ThreadLocalMap belongs to the Thread from the perspective of data kinship. So the get method for ThreadLocal is to get a unique ThreadLocalMap for each thread.
Another reason is that memory leaks are less likely. In our design, the Map held by ThreadLocal holds references to Thread objects, which means that Thread objects in the Map are never reclaimed as long as ThreadLocal objects exist. ThreadLocal lives tend to be longer than threads, so this design can easily lead to memory leaks.
In Java, threads hold ThreadLocalMap, and ThreadLocalMap references are weak references to ThreadLocal, so ThreadLocalMap can be reclaimed as long as Thread objects can be reclaimed. The Java implementation, while more complex, is more secure.
ThreadLocal and memory leaks
However, it’s not always perfect. Using ThreadLocal in a Thread pool can lead to memory leaks. Threads in a Thread pool live too long and often live with the program, which means that threadLocalMaps held by threads are never recycled. In addition, entries in a ThreadLocalMap are weak references to ThreadLocal, so ThreadLocal can be reclaimed as soon as it ends its life cycle. However, the Value in an Entry is strongly referenced by the Entry. Therefore, the Value cannot be reclaimed even after its life cycle ends, resulting in memory leakage.
So we can manually release resources through the try{}finally{} scheme
ExecutorService es;
ThreadLocal tl;
es.execute(()->{
//ThreadLocal adds variables
tl.set(obj);
try {
// Omit the business logic code
}finally {
// Manually clean ThreadLocaltl.remove(); }});Copy the code
The ThreadLocal content above is mainly referenced here.
Epoll mechanism
When the main thread MessageQueue has no message, it blocks in the nativePollOnce() method of the loop queue.next(), and finally calls epoll_wait() to block and wait. 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.
Select,poll, and epoll for IO multiplexing
-
On the surface, epoll performs best, but select and poll may perform better than epoll in the case of a small number of connections and very active connections, since epoll’s notification mechanism requires many function callbacks.
-
Select is inefficient because it requires polling every time. But inefficiencies are relative and can be improved by good design, depending on the situation.
The reason why WE chose epoll mechanism at the bottom of Handler is that Epoll is more efficient. In select/poll, the kernel scans all monitored file descriptors only after a certain method is called. Epoll registers a file descriptor through epoll_ctl(). Once a file descriptor is ready, the kernel uses a callback mechanism similar to callback. Activate this file descriptor quickly to be notified when the process calls epoll_wait(). The traversal file descriptor is removed here, and instead a mechanism that listens for callbacks. That’s the beauty of epoll.)
Handler synchronization barrier mechanism
What if there is an urgent Message that needs to be processed first? This actually involves the architectural aspects of the design, the design of general scenarios and special scenarios. You might think of sendMessageAtFrontOfQueue () this method, the actual well not only so, add a Handler synchronization barrier this mechanism, to implement the asynchronous message priority executive function.
PostSyncBarrier () sends synchronization barriers, and removeSyncBarrier() removes synchronization barriers
The synchronization barrier can be understood as intercepting synchronous Message execution. The main thread Looper will loop through MessageQueue’s next() to fetch the Message execution of the queue head, and then fetch the next Message when the Message execution is finished. When the next() method fetches a Message whose header is a synchronization barrier, it traverses the queue, looking only for messages with the asynchronous flag set. If it finds an asynchronous Message, it fetches the asynchronous Message and executes it, otherwise it blocks the next() method. If the next() method is blocked, the main thread is idle, that is, not doing anything. So, if the queue header is a synchronization barrier message, all synchronization messages behind it are blocked until the synchronization barrier message is removed from the queue, otherwise the main thread will not process the synchronization messages behind the synchronization screen.
All messages are synchronous by default, and are asynchronous only if the asynchronous flag is manually set. In addition, synchronization barrier messages can only be sent internally, and this interface is not exposed to us.
All the message-related code in Choreographer, as you will see, is manually set up to flag asynchronous messages, so these operations are not affected by synchronization barriers. The reason for doing this may be to ensure that the upper app can perform the work of traversing and drawing the View tree in the first time when it receives the screen refresh signal.
Actions during Choreographer are also asynchronous messages to ensure that Choreographer runs smoothly, It also ensures that doTraversal → performTraversals traversals will be executed at the first time. If there are other synchronization messages in this process, they will not be processed. After doTraversal.
For thread if there are too many messages to be performed, and the message is according to the time stamp for sorting, if do not add a synchronization barrier, then traverse map View tree might be forced to delay the work, because it also need to line up, then it is likely when the end of a frame is to screen data calculation, Even if the calculation is less than 16.6ms, it will also cause frame loss.
So, the synchronization barrier message control will ensure that every time the screen refresh signal is received the first time to traverse the View tree work?
Let’s just say that synchronization barriers are done as much as possible, but there is no guarantee that they will be handled first. Because the synchronization barrier is only sent to the message queue when scheduleTraversals() is called, that is, only when a View initiates a refresh request will any synchronization messages after that time be blocked. If the work is sent to the message queue before scheduleTraversals(), it will still be taken out and executed in sequence.
Here’s a partial breakdown:
WindowManager maintains the DecorView and ViewRootImpl for all activities. As we explained earlier, Windows ManagerGlobal initializes ViewRootImpl in its addView method and then calls its setView method, passing in the DecorView as an argument. So let’s see what viewrotimpl does, right
//ViewRootImpl.java
/ / the view is DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) { mView = view; ...// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout(); // Initiate a layout request... the assignParent (this); // Call DecorView assignParent with the current ViewRootImpl object this as an argument...}}}Copy the code
DecorView assignParent is called in the setView() method
//View.java
/* * Caller is responsible for calling requestLayout if necessary. * (This allows addViewInLayout to not request a new layout.) */
@UnsupportedAppUsage
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent"); }}Copy the code
The argument is ViewParent, and ViewRootImpl implements the ViewParent interface, so here we bind the DecorView to ViewRootImpl. The root layout of each Activity is a DecorView, and the parent of a DecorView is a ViewRootImpl, so do something like invalidate() in the child View, loop through the parent and end up in the ViewRootImpl. So the View refresh is actually controlled by the View wrootimPL.
Even when a little View on the interface initiates a redraw request, you have to go all the way to ViewRootImpl, and it initiates the redraw request, and then it starts traversing the View tree until you get to the View that needs to be redrawn and then calls its onDraw() method to draw.
The invalidate () request to redraw operations is the last call to ViewRootImpl. ScheduleTraversals (), and ViewRootImpl. SetView () method call requestLayout methods
@Override
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code
Finally, the scheduleTraversals() method is called, which is the key to the screen refresh.
Start an Activity and bind its DecoView to a new ViewRootImpl object when its onCreate- onResume life cycle is complete. At the same time, start scheduling a traversal of the View (drawing the View tree) and wait to execute, then set the DecoView parent to the ViewRootImpl object. So we don’t get the View width and height in onCreate~onResume, and the interface is drawn after onResume. You can refer to my previous article analysis.
ViewRootImpl. ScheduleTraversals () a series of analysis and screen refresh mechanism can refer to this article, the content most refer to it, the analysis of the related content synchronization barrier also in it.
Choreographer is primarily about coordinating the timing of animation, input, and drawing. It receives timing pulses from the display subsystem (for example, vSYNC) and then schedules part of the rendering of the next frame.
By Choreographer. GetInstance (). PostFrameCallback () to listen on frame rate;
public class FPSFrameCallback implements Choreographer.FrameCallback {
private static final String TAG = "FPS_TEST";
private long mLastFrameTimeNanos;
private long mFrameIntervalNanos;
public FPSFrameCallback(long lastFrameTimeNanos) {
mLastFrameTimeNanos = lastFrameTimeNanos;
// Render time per frame is nanoseconds
mFrameIntervalNanos = (long) (1000000000 / 60.0);
}
@Override
public void doFrame(long frameTimeNanos) { //Vsync signal arrival time frameTimeNanos
// Initialization time
if (mLastFrameTimeNanos == 0) {
// Render time of last frame
mLastFrameTimeNanos = frameTimeNanos;
}
final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames > 5) {
Log.d(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
}
mLastFrameTimeNanos = frameTimeNanos;
// Register the next frame callback
Choreographer.getInstance().postFrameCallback(this); }}Copy the code
The invocation method is registered with Application
Choreographer.getInstance().postFrameCallback(FPSFrameCallback(System.nanoTime()))
Copy the code
Causes of frame loss: There are generally two kinds of reasons for frame loss. One is that it takes more than 16.6ms to traverse the drawing View tree and calculate screen data. Second, the main thread has been processing other time-consuming messages, so that the work of traversing and drawing the View tree cannot start late, which exceeds the time of switching to the next frame at the bottom level of 16.6ms.
Handler lock related problems
Since there can be multiple handlers to add data to a MessageQueue (each Handler may be on a different thread when the message is sent), how is it internally thread-safe?
Handler.sendXXX, handler. postXXX will eventually be called to MessageQueue’s enqueueMessage method
The source code is as follows:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// Lock to ensure safety
synchronized (this) {···}}Copy the code
Its internal synchronization keyword to ensure thread safety. At the same time, messagequeue.next() is also locked internally by synchronized to ensure thread safety at fetch time, and inserts are also locked. This problem is not difficult, just see if you know the source code.
A synchronization method in Handler
How do I get the handler.post message to execute after it has been executed and then proceed further, synchronizing the method runWithScissors
public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
if (r == null) {
throw new IllegalArgumentException("runnable must not be null");
}
if (timeout < 0) {
throw new IllegalArgumentException("timeout must be non-negative");
}
if (Looper.myLooper() == mLooper) {
r.run();
return true;
}
BlockingRunnable br = new BlockingRunnable(r);
return br.postAndWait(this, timeout);
}
Copy the code
Some applications of Handler in systems and third-party frameworks
HandlerThread
HandlerThread inherits Thread. As the name implies, HandlerThread is actually a encapsulation of Handler and Thread, which has been encapsulated well and safely for us. Internal synchronization also ensures Thread safety, such as getLooper method
public Looper getLooper(a) {
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;
}
Copy the code
In the thread’s run method, Looper can be created and assigned to mLooper only after the thread is started, and the block is waiting for the Looper to be created successfully. The Looper method was created successfully for external use. The Looper method was created successfully for external use.
IntentService
A quick look at the source code shows the Handler in action. The Handler’s handMessage eventually calls back to the onHandleIntent method.
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
@UnsupportedAppUsage
private volatile ServiceHandler mServiceHandler;
Copy the code
How do you build a program that doesn’t crash
To create a program that doesn’t crash, refer to this article
Glide application
You can control the life cycle of a LiveData by adding an empty Fragment to an Activity or Fragment. The Fragment lifecycle is then managed through FragmentMannager to achieve lifecycle control. The following is an excerpt of the code to add Fragment Glide:
private RequestManagerFragment getRequestManagerFragment(
@NonNull final android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
Obtain a RequestManagerFragment from FragmentManager. If it has been added to FragmentManager, return the instance. Otherwise, empty
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
//2. If FM does not exist, fetch it from map cache
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
//3. If steps 1 and 2 are not displayed, the system is not created
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
if (isParentVisible) {
current.getGlideLifecycle().onStart();
}
//4. Save the newly created fragment to the Map container
pendingRequestManagerFragments.put(fm, current);
//5. Send the event to add a Fragment transaction
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
//6. Send remove local cache eventhandler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current;
}
// The only difference is that this is the FragmentManager and this is the Acitivity FragmentManager
private SupportRequestManagerFragment getSupportRequestManagerFragment(
@NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
SupportRequestManagerFragment current =
(SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingSupportRequestManagerFragments.get(fm);
if (current == null) {
current = new SupportRequestManagerFragment();
current.setParentFragmentHint(parentHint);
if(isParentVisible) { current.getGlideLifecycle().onStart(); } pendingSupportRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget(); }}return current;
}
@Override
public boolean handleMessage(Message message) {
boolean handled = true;
Object removed = null;
Object key = null;
switch (message.what) {
case ID_REMOVE_FRAGMENT_MANAGER:
// remove the cache
android.app.FragmentManager fm = (android.app.FragmentManager) message.obj;
key = fm;
removed = pendingRequestManagerFragments.remove(fm);
break;
// omit code...
}
// omit code...
return handled;
}
Copy the code
If you look at the code above, you might be confused.
- Why are fragments added to the FragmentManager stored in the Map container (step 4)?
- Check whether the Fragment has been added from the map (step 2). The FragmentManager will find the Fragment.
The answer is simple: after step 5, you don’t add the Fragment to the FragmentManager (event queue). Instead, you send the event that adds the Fragment. Now let’s look at the code
//FragmentManagerImpl.java
void scheduleCommit(a) {
synchronized (this) {
booleanpostponeReady = mPostponedTransactions ! =null && !mPostponedTransactions.isEmpty();
booleanpendingReady = mPendingActions ! =null && mPendingActions.size() == 1;
if(postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); updateOnBackPressedCallbackEnabled(); }}}Copy the code
Adding a Fragment ends up in the scheduleCommit method of FragmentManagerImpl, which we can see sends events through a Handler.
This explains why the Fragment is not added to the FragmentManager immediately after step 5, so the Map cache Fragment is needed to mark if any fragments are added. Step 6 then sends the message to remove the Map cache, because the Handler processes the messages in order.
conclusion
In fact, the analysis of the source code is not very careful, but from the whole system of various aspects of the use of different extensions, as well as some third-party framework use, I hope this article is helpful to you, like the point like it ~
Have you mastered the basic, intermediate and advanced methods of Activity?
Reference article:
Zhuanlan.51cto.com/art/202007/…
Blog.csdn.net/meegomeego/…
Juejin. Cn/post / 684490…
www.jianshu.com/p/dfd940e7f…
Time.geekbang.org/column/arti…
Xiaozhuanlan.com/topic/65243…
Juejin. Cn/post / 686972…