The purpose of the Handler
Handler is mainly used for asynchronous message processing, mainly to solve the problem that the child thread cannot access the UI.
Initialization of the Handler
There are two common ways to use it: first, initialize a handler directly and override its handleMessage method, or pass a Handler.Callback object and override the handleMessage method in the handler. Callback class.
private Handler handler1 = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg); }};private Handler handler2 = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false; }});Copy the code
In the Handler method:
public void dispatchMessage(@NonNull Message msg) {
if(msg.callback ! =null) {
handleCallback(msg);
} else {
if(mCallback ! =null) {
if (mCallback.handleMessage(msg)) {
return; } } handleMessage(msg); }}Copy the code
Send a message
handler.sendMessage(Message msg)
handler.post(Runnable r)
The runnable object is eventually wrapped in a Message.
Create Handler in child thread
Calling new Handler() directly from a child thread will result in an error:
2021-05-31 16:15:52.447 28913-29688/com.fred.app E/AndroidRuntime: FATAL EXCEPTION: Thread3 Process: com.fred.app, PID: 28913 java.lang.RuntimeException: Can't create handler inside thread Thread[thread-3,5,main] that has not called Looper. Prepare () at android.os.Handler.<init>(Handler.java:207) at android.os.Handler.<init>(Handler.java:119) at com.fred.app.MainActivity.lambda$sendMsg$3$MainActivity(MainActivity.java:95) at com.fred.app.-$$Lambda$MainActivity$wS6JjKtF31xh-2WedxDQY0yoNJA.run(Unknown Source:2) at java.lang.Thread.run(Thread.java:919)Copy the code
The error message is also clear: Handler could not be created in the child thread because the looper.prepare () method was not called. The exception message is thrown in the Handler constructor. So, if we call looper.prepare () once in the child thread, new Handler() should not be a problem.
Handler, Looper, MessageQueue
Binding of the main thread to Looper
sequenceDiagram
ActivityThread->>Looper: Looper.prepareLooper()
Looper-->>Looper: prepare()
Looper-->> ThreadLocal: set(new Looper(quitAllowed);
Looper.preparemainlooper () is called in the main method of ActivityThread; This method calls prepare() in Looper, which calls set(new Looper(quitAllowed) in ThreadLocal. Bind the current thread to the Looper to be created. When new Looper(quitAllowed) is created, a MessageQueue is initialized, and the corresponding Looper and MessageQueue in the main thread are created.
Looper and MessageQueue
Looper will have an instance of MessageQueue,
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
The Handler and stars
Let’s look at the Handler constructor:
public Handler(@Nullable Callback callback, boolean async) {... mLooper = Looper.myLooper();if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Copy the code
In the stars
public static @Nullable Looper myLooper(a) {
return sThreadLocal.get();
}
Copy the code
We can see from the Handler constructor that:
Set Looper in Handler
When Handler initializes, it calls looper.mylooper () to fetch a Looper object from the ThreadLocal. From the previous analysis, if this method is called from the main thread, the Looper must already be initialized. So the return value must not be null. MyLooper () must be null, and an exception will be thrown. The solution is to call looper.prepareLooper () once in the child thread.
Handler initialization cannot directly new a Looper, because if you new a Looper, you cannot guarantee the uniqueness of the Looper. In the prepare method for Looper, a Looper is fetched from ThreadLocal and the resulting Looper is bound to the current thread.
Set MessageQueue in Handler
mQueue = mLooper.mQueue;
Sending of messages
Calling sendMessage or a similar method in the Handler will eventually put the message on the message queue
Consumption of messages
Using the main thread as an example, looper.prepareMainLooper () is called in the main method of the main thread. Looper.loop() is called after the method; Method, which has an infinite loop, constantly to messages from the message queue, take to the message after it performs MSG. Target. DispatchMessage (MSG); We know that msg.target corresponds to a Handler object, so this method ends up calling a Handler’s dispatchMessage method. The dispatchMessage method eventually calls the Handler’s handleMessage method to complete the message distribution.
The Handler and Message
When sendMessage is in Handler, the enqueueMessage method is called, which binds Message to Handler.
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// The MSG target attribute points to the current handler
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
Handler memory leaks. Procedure
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
startActivity(new Intent(MainActivity.this, OrderActivity.class));
System.out.println("[App Log]-------------------------------->handleMessage"); }};private void initView(a) {
findViewById(R.id.tv_id).setOnClickListener(v -> sendMsg());
}
private void sendMsg(a) {
new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
}).start();
}
@Override
protected void onDestroy(a) {
super.onDestroy();
handler.removeMessages(1); }}Copy the code
As shown in the code above, clicking on the button triggers the sendMsg method, which will sleep(5000) and then send a message. The handler will jump to another activity after receiving the message. We removed the message that was sent in onDestory. If we press the system’s back key after pressing the button, the activity’s onDestory will execute, and handler.removemessage (1) will execute, since the handler message in sendMsg has not been sent. So the removeMessage method doesn’t actually delete the message. Eventually the methods in the handleMessage will execute anyway, causing a memory leak.
The solution is simple, as long as we’re hereonDestory
I’m going to set handler tonull
“And then make a non-null judgment when sending a message.
Or we could do it this way, if we write it in sendMsg
private void sendMsg() {
new Thread(() -> {
Message msg = new Message();
msg.what = 1;
handler.sendMessageDelayed(msg, 5000);
}).start();
}
Copy the code
The MSG will be pushed to the messgeQueue corresponding to the handler, but will be sent after 5 seconds, but since the onDestory has removed the message from the message queue, it can be avoided in this way.
Three message types
A synchronous message
We usually use, sent messages are synchronous messages.
Asynchronous messaging
A message set to asynchronous via msg.setasynchronous (true) was introduced in API Level 22(after Android 5.0).
Barrier message
Also known as synchronous barrier messages, which have no corresponding target, i.e. no corresponding handler, and need to use reflection to call, belong to the system’s hidden API. Android has become increasingly restrictive in this regard, disallowing reflection calls on older versions.
Insert barrier message
MessageQueue queue = mHandler.getLooper().getQueue();
Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
method.setAccessible(true);
token = (int)method.invoke(queue);
Copy the code
Remove barrier messages
MessageQueue queue = mHandler.getLooper().getQueue();
Method method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier".int.class);
method.setAccessible(true);
method.invoke(queue, token);
Copy the code
When a postSyncBarrier is called to issue a barrier message, the synchronous messages in the message queue are suspended, the asynchronous messages execute normally, and the synchronous messages continue after removeSyncBarrier is called. It is important to note that after we insert the barrier message, we must remember to remove it, otherwise the page will be in a hanging state. For example, click events on a page cannot be listened to.
HandlerThread
Thread is a Thread that can use a Handler. It has a Looper inside, and its run method starts the Looper message loop. Its use:
Thread t = new Thread(new Runnable() {
@Override
public void run(a) {
Log.i(TAG, "thread start"); }}); HandlerThread handlerThread =new HandlerThread("Handler Thread");
Handler handler = new Handler(handlerThread.getLooper());
handler.post(t);
Copy the code
IntentService
IntentService inherits from Service and is an abstract class. IntentService can be used to execute time-consuming tasks in the background and will stop automatically. Since it is a service, its priority is much higher than that of a simple thread, which means it is not easy to be killed by the system. IntentService encapsulates the Handler and HandlerThread
- IntentService: Most services don’t need to process multiple requests at the same time. In this case, we can use IntentService.
- Working mechanism:
- Create a default worker thread and put all
intents
Passed to theonStartCommand
- Create a queue, one at a time
intent
Passed to theonHandleIntent
So we don’t have to worry about multithreading - If all requests are executed, they will be executed
stopSelf
methods
- Create a default worker thread and put all
- IntentService encapsulates
HandlerThread
andHandler
. In itsonCreate
One is created in theHandlerThread
Object, and then use that object’sLooper
To construct aHandler
By thisHandler
To process messages because of thisHandler
Is made up ofHandlerThread
theLooper
Structural, whileHandlerThread
IntentService is essentially a child thread, so it can perform background tasks. - Usage: Inherit from IntentService and implement it
onHandleIntent
methods
A Demo
button2.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action"."ACTION_1"));
startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action"."ACTION_2"));
startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action"."ACTION_3"));
startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action"."ACTION_4")); }}});Copy the code
public class MyIntentService extends IntentService {
private final String TAG = "MyIntentService";
public MyIntentService(a) {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
String action = intent.getStringExtra("action");
Log.i(TAG, "task:" + action + " Thread:" + Thread.currentThread());
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "task:" + action + " handled");
}
@Override
public void onDestroy(a) {
super.onDestroy();
Log.i(TAG, "service destory"); }}Copy the code
The execution result
10 to 22 09:51:10. 540, 15243-15470 / com. Fred I/MyIntentService: Task :ACTION_1 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:11.542 15243-15470/com.fred task:ACTION_1 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:11.542 15243-15470/com.fred I/MyIntentService: task:ACTION_1 handled 10-22 09:51:11.544 15243-15470/com. I/MyIntentService: task:ACTION_1 handled 10-22 09:51:11.544 Task :ACTION_2 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:12.544 15243-15470/com.fred task:ACTION_2 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:12.544 15243-15470/com.fred I/MyIntentService: task:ACTION_2 handled 10-22 09:51:12.545 15243-1547 /com. I/MyIntentService: task:ACTION_2 handled 10-22 09:51:12.545 Task :ACTION_3 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:13.545 15243-15470/com.fred task:ACTION_3 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:13.545 15243-15470/com.fred I/MyIntentService: task:ACTION_3 handled 10-22 09:51:13.546 15243-15470/com. I/MyIntentService: task:ACTION_3 handled 10-22 09:51:13.546 Task :ACTION_4 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:14.546 15243-15470/com.fred task:ACTION_4 Thread:Thread[IntentService[MyIntentService],5,main] 10-22 09:51:14.546 15243-15470/com.fred I/MyIntentService: task:ACTION_4 handled 10-22 09:51:14.546 15243-15243/com. Fred I/MyIntentService: service destoryCopy the code
You can see that the tasks in IntentService are queued.
Frequently asked Questions
Child thread can’t manipulate UI?
Since the ViewRootImpl contains the checkThread operation, it is possible to operate the UI if the corresponding UI operation is completed before the checkThread, for example, in the activity’s onCreate method.
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
whylooper.loop()
Will not cause the main thread to freeze?
- Called in the main thread
looper.loop()
Method, which has an infinite loop in it to ensure that the main thread will always execute and not exit. - Block in the loop when there is no message
queue.next()
In thenativePollOnce()
Method, the thread frees CPU resources and goes to sleep until the next message arrives or a transaction occurs. So an infinite loop is not a huge drain on CPU resources. - The only operations that really get stuck are those that take too long to process a message, resulting in frame drops, ANR, and not the loop method itself.
reference
- www.jianshu.com/p/4a5856866…