A, definitions,
- An Android messaging/asynchronous communication mechanism.
Second, the role of
- In multi-threaded scenarios, sub-threads need to pass UI update operation information to the main thread to realize asynchronous message processing.
Three, use,
There are two ways to use it:
- One is through
sendMessage()
To achieve asynchronous communication. - One is through
mHandler.post()
To achieve asynchronous communication.
3.1 sendMessage()
way
1. CreateHandler
Object, listed below3
Kind of way.
- Anonymous inner class
// Create a Handler class object from an anonymous inner class in the main thread.
private Handler mHandler = new Handler(){
// Update the UI by overriding the handlerMessage method
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
/ /... Perform the UI update operation}};Copy the code
- implementation
Callback
interface
// The main thread implements the Callback interface to create a handler class object.
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false; }});Copy the code
- Created in a child thread
Handler
Class object (rare, rarely used)
new Thread(new Runnable() {
@Override
public void run(a) {
Looper.prepare();
Handler mHandler = new Handler();
Looper.loop();
}
}).start();
Copy the code
2. To createMessage
Object, at the same timehandler
Send a message.
- create
Message
object
// Method 1:
Message message1 = new Message();
// Method 2:
Message message2 = Message.obtain();
// Method 3:
Message message3 = mHandler.obtainMessage();
Copy the code
Message
Object carrying data
Message message = new Message();
message.what = 1; // Identifies the message
Arg1, arg2
message.obj = "La la la la"; // object
message.arg1 = 1; / / int type
message.arg2 = 2; / / int type
// Method 2: Transfer by Bundle
Bundle bundle = new Bundle();
bundle.putInt("1".1);
message.setData(bundle);
Copy the code
handler
Send a message
// Method 1: Send a common message
mHandler.sendMessage(message);
// Method 2: Send an empty message and pass in the what value for easy judgment in the received message.
mHandler.sendEmptyMessage(0);
// Mode 3: Delay sending messages
mHandler.sendEmptyMessageDelayed(0.1000);
Copy the code
3. handler
Receive the message and process it.
private Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
// Determine different messages according to the what identifier
switch (msg.what) {
case 0:
System.out.println("Received message is empty message");
break;
case 1:
System.out.println(msg.obj.toString());
break;
default:
break; }}};Copy the code
3.2 mHandler.post()
way
- 1. Create
Handler
Object. - 2. In the child thread by calling the instance object
post()
Method, passed inrunnable
Object, overriderun()
Method in rewriterun()
Update in methodUI
.
private Handler mHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run(a) {
mHandler.post(new Runnable() {
@Override
public void run(a) {
// Update UI operations}}); }});Copy the code
Ok, about the basic use of Handler is said, the following through a problem and the corresponding problem of the source code to deepen our understanding of Handler, do know what it is.
Four, common questions and source solutions
1. Create the main threadHandler
Create with child threadHandler
What’s the difference?
Create Handler for main thread:
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false; }});Copy the code
Child thread create Handler:
new Thread(new Runnable() {
@Override
public void run(a) {
Looper.prepare();
Handler mHandler = new Handler();
Looper.loop();
}
}).start();
Copy the code
By comparison, we can see that there are two more lines of code to create the Handler on the child thread, so why not on the main thread? Let’s use the source code to explain:
At startup, the application calls the ActivityThread class. In the main() method of this class, looper.prepareMainLooper () is called:
// The main method in ActivityThread
public static void main(String[] args) {
/ /...
// Create Looper for the main thread
Looper.prepareMainLooper();
/ /...
Looper.loop();
}
Copy the code
Looper.prepareMainLooper();
- The implication of this line of code is to initialize one for the current thread
Looper
, so it’s not that the main thread doesn’t need to be calledLooper.prepare()
Method, but the system has already done something.
Looper.loop();
- What this line of code means is that it turns on polling for the main thread, which is an infinite loop that keeps polling.
At this point, some of you might be wondering, what if the system automatically added these two lines of code to the main thread when it created the Handler? What if we didn’t add these two lines to the child thread when it created the Handler? The answer is no, no, exceptions will be reported:
throw new RuntimeException("Only one Looper may be created per thread");
Copy the code
And that’s when we think, why does the system throw an exception? We still need to find the answer from the source code. We need to look at the source code for new Handler().
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()");
}
//....
}
Copy the code
We can see from the source that the member variable mLooper is assigned. If it does not get the value, it will throw the exception we mentioned earlier, so we need to see what looper.mylooper () does.
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/ /...
public static @Nullable Looper myLooper(a) {
return sThreadLocal.get();
}
Copy the code
The return value of this method is the Looper object, and the return value of this method is the Looper object.
public T get(a) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
returnresult; }}return setInitialValue();
}
Copy the code
ThreadLocalMap (); ThreadLocalMap (); ThreadLocalMap (); Return to the main() method of ActivityThread and click prepareMainLooper() of Looper.prepareMainLooper() :
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(); }}Copy the code
Click the prepare() method in this method again:
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
Here is the sthreadlocal.set () method:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
}
Copy the code
If we look at these two methods side by side, we can see that when the application starts, a unique piece of data is created in the ThreadLocalMap. The key value is the main thread, and the value is the main thread’s Looper object. When we create the Handler in the child thread, The value of the key passed in is the child thread. Of course, we can’t get the Looper object from ThreadLocalMap because the value of the key is different. So the Looper object is null, which throws the above exception.
From this question, the following conclusions can be made:
- Child threads do not exist by default
Looper
If neededHandler
Must be created for the threadLooper
. - There must be one in a thread
Looper
That is, in a thread, if you want to useHandler
You have to have oneLooper
. Want to create properly in child threadshandler
Object as created abovehandler
The first object3
Kind of method. Handler
The current thread will be used for creationLooper
To construct the message loop system, because when the application starts,ActivityThread
The class is initializedLooper
, so it can be used by default in the main threadhandler
, but you cannot say that the main thread is not createdLooper
Object.
2. Does updating control content really only run in the main thread?
First give the conclusion, this sentence is too absolute, not necessarily. Textview.settext () : textView.settext () : textView.settext () : textView.settext () :
Click through the setText() method to find the checkForRelayout() method in the last setText method. Click on this method to get to the bottom of the code. You can see that either the if code or the else code will end up with the requestLayout() and invalidate() lines. Then let’s see what’s going on in requestLayout().
public void requestLayout(a) {
if(mMeasureCache ! =null) mMeasureCache.clear();
if(mAttachInfo ! =null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
------ ViewRootImpl implements the mParent interface --------
ViewRootImpl viewRoot = getViewRootImpl();
if(viewRoot ! =null && viewRoot.isInLayout()) {
if(! viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if(mParent ! =null && !mParent.isLayoutRequested()) {
// ---------mParent is an interface ---------
mParent.requestLayout();
}
if(mAttachInfo ! =null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null; }}Copy the code
ViewRootImpl implements the mParent interface, which calls requestLayout() in ViewRootImpl.
@Override
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code
There is a method to check the thread: checkThread()
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
This code checks if the current thread is the main thread and raises an exception if it is not.
If we update the UI in a child thread, we will not raise an exception if the requestLayout() checking thread is slower than the invalidate() drawing interface.
To verify this, delay the child thread by one second to update the UI.
Process: com.example.handlerdemo, PID: 10569
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:360)
at android.view.View.requestLayout(View.java:23093)
at android.widget.TextView.checkForRelayout(TextView.java:8908)
at android.widget.TextView.setText(TextView.java:5730)
at android.widget.TextView.setText(TextView.java:5571)
at android.widget.TextView.setText(TextView.java:5528)
at com.example.handlerdemo.MainActivity$3.run(MainActivity.java:75)
at java.lang.Thread.run(Thread.java:764)
Copy the code
Can see out pointing error is reading the source code we see ViewRootImpl. RequestLayout this check threads.
UI updates can only be performed on the main thread.
3. Create the main threadHandler
What’s the difference between the two?
A:
private Handler mHandler1 = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false; }});Copy the code
Method 2:
private Handler mHandler2 = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg); mTextView.setText(msg.obj.toString()); }};Copy the code
Those of you who have used it know that method two has a yellow warning, which is Google’s backup API and not recommended.
From the previous analysis, we know that the main() method of AcitivityThread creates a Looper object and calls looper.loop () to loop through the message queue.
/ / which class
for (;;) {
//....
try {
msg.target.dispatchMessage(msg);
if(observer ! =null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; }}/ /...
Copy the code
In this loop, the dispatchMessage() method of MSG. Target is called to distribute the message. What is MSG. MSG is a Message object. If we go to the source code of the Message class, we can see the following code:
/ / the Message class
@UnsupportedAppUsage
/*package*/ Handler target;
Copy the code
MSG. Target is the Handler object, so Handler’s dispatchMessage() method is called. So let’s look at the dispatchMessage() method in the Handler class:
/ / Handler class
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
If (mhandler.post ()) {if (mCallback) {if (mCallback) {if (mCallback) {if (mCallback) {if (mCallback) {if (mCallback) {if (mCallback) {if (mCallback) {if (mCallback) {if (mCallback) {if (mCallback);
/ / Handler class
final Callback mCallback;
public interface Callback {
/ * * *@param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
Copy the code
A callback object is passed in to a new Handler, so McAllback.handlemessage () is not null. Method, which is the handleMessage() method we implement in the callBack interface. In the method we override, there is no difference between return true or return false.
If we return true, the source code will simply return. If we return false, the source code will go to the following handleMessage(), which is a public method exposed in the Handler class that can be overridden, but whose method name is the same as the one in the interface. We can’t override the handleMessage() method in the Handler class of method 1, so it makes no difference whether we write return true or return false.
If we had written in method 2, we would have followed the handleMessage() method below. That is, the rewritable public method handleMessage() exposed in the Handler class we overwrote in Approach 2.
Ok, so that’s the difference between the two methods. Now let’s talk about the if in the dispatchMessage() method that we ignored earlier. Why is the code in the if for mhandler.post ()?
Let’s look at our own mhandler.post ():
new Thread(new Runnable() {
@Override
public void run(a) {
mHandler1.post(new Runnable() {
@Override
public void run(a) {
/ /...}}); }});Copy the code
Handler: post();
/ / Handler class
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
Copy the code
As you can see from the Runnable object we passed in, we call getPostMessage(r) to wrap the Runnable object we passed in. So what’s going on in this method?
/ / Handler class
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Copy the code
Message.callback is a callback attribute in the Message class, which is a Runnable object:
// in the Message class
@UnsupportedAppUsage
/*package*/ Runnable callback;
Copy the code
The Message object returned by the above method and its callback property value is the Runnable object passed in by the POST () method.
So let’s go back to the if in the dispatchMessage() method of the Handler class:
/ / Handler class
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
Mhandler.post () executes the code in if when receiving the message, since msg.callback is the Runnable object passed in and is not null, handleCallback() is executed:
/ / Handler class
private static void handleCallback(Message message) {
message.callback.run();
}
Copy the code
You can see that the handleCallback() method executes message.callback’s run() method, which is overridden by the Runnable interface we passed in.
4. CreateMessage
What is the difference between the two methods?
A:
Message message = Message.obtain();
Copy the code
Method 2:
Message message = mHandler.obtainMessage();
Copy the code
Let’s take a look at the source code for the Obtain () method in the Message class in approach 1:
public static Message obtain(a) {
synchronized (sPoolSync) {
if(sPool ! =null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
returnm; }}return new Message();
}
Copy the code
From the source code, Android provides a cache pool for Message, which can cache used messages for future use. When we obtain a Message using the message.obtain () method, we obtain the Message from the cache pool first, and create the Message only if it is not available. Doing so optimizes memory. The cache pool sPool is also pointed to the Message next.
Now look at the source code for obtainMessage() in method 2:
public final Message obtainMessage(a)
{
return Message.obtain(this);
}
Copy the code
The Messageobtain() method is actually called, passing the current handler object as an argument.
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
Copy the code
The obtain() method on the first line is the same as the message.obtain () method on the first line, with a second line assigning handler to message.target. SendToTarget () = message.sendtotarget () = message.sendtotarget () = message.sendtotarget () = message.sendtotarget () = message.sendtotarget () = message.sendtotarget ()
public void sendToTarget(a) {
target.sendMessage(this);
}
Copy the code
Target = handler; this = message; this = handler. SendMessage (message);
5. Why does improper use of Handler cause memory leakage?
Remember that when we used the second method to create a Handler, there was a yellow warning to the effect that this Handler was not set to static and that the resulting memory leak would occur on the external class that holds the Handler class, such as MainActivity.
The second way to create a Handler is not set to static, so it is possible to leak memory.
Two things to know first:
Handler
We get the main thread when we create itLooper
Object, and thisLooper
The life cycle of an object is the same as the life cycle of an application.- Java Basics
Non-static inner class
orAnonymous inner class
It’s defaultHolds a reference to an external class
.
Based on the above two theoretical knowledge, we imagine a scenario in which there are unprocessed messages waiting to be processed in the MessageQueue in the Handler. By default, message in the message queue holds a reference to Handler, which in turn holds a reference to an external class such as MainActivity by default. Assuming Handler is created in MainActivity, So if we destroy the MainActivity, the garbage collector will not be able to reclaim the MainActivity because of the above reference, resulting in a memory leak.
Now that we know why memory leaks occur, what can be done to fix them? From the cause, of course:
- Static inner class + weak reference.
- Empty the external class when it is about to be destroyed
Handler
The message queue of theHandler
Set tonull
.
The principle of the first approach is that static inner classes do not hold references to external classes by default, and weakly referenced objects have a short lifetime. When the garbage collector thread scans, once it finds an object with only weak references, it reclaims its memory regardless of whether the current memory space is sufficient or not.
The second method works by emptying the message queue so that the reference relationship does not exist and synchronizes the lifecycles of MainActivity and Looper objects.
The first way to do:
public class HandlerActivity extends AppCompatActivity {
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable mRunnable = new Runnable() {
@Override
public void run(a) {
/ / operation}};@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fourth);
mHandler.postDelayed(mRunnable, 1000*10);
finish();
}
private static class MyHandler extends Handler {
WeakReference<HandlerActivity> mWeakActivity;
public MyHandler(HandlerActivity activity) {
this.mWeakActivity = new WeakReference<HandlerActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
final HandlerActivity mActivity = mWeakActivity.get();
if(mActivity ! =null) {
// Process the message}}}}Copy the code
The second way is:
@Override
protected void onDestroy(a) {
super.onDestroy();
Log.e("TAG >>>>"."onDestroy");
// Handle memory leaks correctly
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
Copy the code
Five,Handler
Mechanism source code explanation
Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler
First to aWorth ten million
The graph:
For Hanlder’s asynchronous messaging mechanism, there are four steps:
Step 1:
The looper.PrepareMainLooper () method is called in the main() method of the ActivityThread class when the application starts. Prepare () is called in prepareMainLooper(), which creates a globally unique main thread Looper object.
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
The sthreadlocal.set () method inserts the new Looper into the ThreadLocalMap. The key value is the current thread (the main thread) and the value of the Looper is the main thread’s Looper.
/ / private
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Copy the code
So the Looper object can only be created using the static prepare() method exposed by the Looper class. This creates a globally unique main thread Looper object. The MessageQueue object, which is the globally unique MessageQueue for the main thread, is also created.
Step 2: When we create the Handler object on the main thread, i.e., new Handler, look at the source code:
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
MLooper and Queue are initialized. The Looper object created by the main thread is assigned to the member variable mLooper in the Handler class. The MessageQueue MessageQueue in the main thread Looper is assigned to the member variable mQueue in the Handler class.
Step 3:
whenHandler
When we send the message, we knowHandler
There are many ways to send messages:But no matter how many ways you can send a message, by looking at the source code, you will eventually call itenqueueMessage()
Methods:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
MSG. Target = this; MSG. Target = this; This line of code, which is the Handler object that calls this method, assigns the Handler object to the target property of the passed Meessage object.
EnqueueMessage: MessageQueue enqueueMessage: MessageQueue enqueueMessage
boolean enqueueMessage(Message msg, long when) {
/ /...
synchronized (this) {
/ /...
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
/ /...
}
//....
}
return true;
}
Copy the code
We see this line of code in there: mMessages = MSG; MessageQueue (MessageQueue) MessageQueue (MessageQueue) MessageQueue (MessageQueue) The value of MSG is also assigned to the mMessages attribute in the main thread MessageQueue (MessageQueue class).
Step 4:
How does the Handler receive and process the message? Looper.loop() is called to loop messages in ActivityThread after the application starts.
public static void loop(a) {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
//....
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
/ /...
try {
msg.target.dispatchMessage(msg);
if(observer ! =null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if(observer ! =null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if(traceTag ! =0) { Trace.traceEnd(traceTag); }}/ /...}}Copy the code
We see one line of code that MSG. Target. DispatchMessage (MSG); The Looper message pump is always fetching messages from the MessageQueue. Once a message is fetched from the MessageQueue, it calls the dispatchMessage () method to distribute the message. So what is msg.target? Remember when the Handler used to call enqueueMessage() no matter what method it called to send a message? In that method, the caller Handler is assigned to the message.target property. So the way to distribute messages here is by calling the Handler’s own dispatchMessage() method. The dispatchMessage() method of the Handler itself has been examined before:
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
If you are sending a message by calling mHandler.post(), then the message will be processed by following the code in the if above and executing the Runnable interface passed in by mHandler.post(Runnable r) to implement the Runnable class’s re-abstract run() method.
If you created the Handler object by implementing the callback interface, then use the if code in the else section to call the handleMessage() method that implements the callback interface.
If you create a Handler object from an anonymous object class and override the handleMessage() method exposed in the Handler class, go to the last handleMessage() method.
At the end of the article
The paper come zhongjue shallow, and must know this to practice. Winter Night reading shows the child Yu — Lu You
Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler