Want to implement the Android queue function? Handler inner strength mental method, you deserve it! (1)-Handler source code and answers to common questions

Handler is a message processing mechanism in Android. It is a solution for communication between threads. At the same time, you can also understand that it naturally creates a queue for us in the main thread. The order of messages in the queue is the delay time we set. If you want to implement a queue function in Android, you might as well consider it for the first time. This article is divided into three parts:

Handler source code and answers to common questions

  1. How many Handler, Looper, MessageQueue are there at most in a thread?
  2. Why doesn't Looper's infinite loop cause the application to freeze? Will it consume a lot of resources?
  3. How to update the UI of the child thread, such as Dialog, Toast, etc.? Why does the system not recommend updating the UI in the child thread?
  4. How does the main thread access the network?
  5. How to deal with memory leaks caused by improper use of Handler?
  6. What are the application scenarios of Handler's message priority?
  7. When does the Looper of the main thread exit? Can I log out manually?
  8. How to determine if the current thread is the Android main thread?
  9. The correct way to create a Message instance?

Handler in-depth question answering

  1. ThreadLocal
  2. epoll mechanism
  3. Handle synchronization barrier mechanism
  4. Handler lock related issues
  5. Synchronous method in Handler

Some applications of Handler in the system and third-party frameworks

  1. HandlerThread
  2. IntentService
  3. How to build an app that does not crash
  4. Application in Glide

Handler source code and answers to common questions

Let's take a look at 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.

The general idea is that Handler allows you to send Message/Runnable to the message queue of the thread (MessageQueue), and each Handler instance is associated with a thread and the message queue of that thread. When you create a Handler, you should bind to a Looper (the main thread has already created Looper by default, and the child threads need to create Looper by themselves). It sends Message/Runnable to the corresponding message queue of Looper and processes the corresponding thread in that Looper. Message/Runnable. The following picture is the workflow of Handler.

Handler work flow chart

It can be seen that in Thread, Looper's conveyor belt is actually an infinite loop. It continuously fetches messages from the message queue MessageQueue, and finally hands it to Handler.dispatchMessage for message distribution, and Handler.sendXXX, Handler.postXXX these The method sends the message to the MessageQueue in the message queue. The whole mode is actually a producer-consumer mode, which continuously produces messages, processes messages, and sleeps when there are no messages. MessageQueue is a priority queue composed of a singly linked list (all the heads are taken, so it is a queue).

As mentioned earlier, when you create a Handler, you should bind a Looper (binding can also be understood as creation, the main thread has already created the Looper by default, and the child threads need to create the Looper by themselves), so let's take a look at the main thread first How is it handled in:

//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");
}

You can see that in the main method in ActivityThread, we first call Looper.prepareMainLooper() method, then get the Handler of the current thread, and finally call Looper.loop(). First 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() {
     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));
}

You can see that the Looper of the current thread is created in the Looper.prepareMainLooper() method, and the Looper instance is stored in the thread local variable sThreadLocal (ThreadLocal), that is, each thread has its own Looper. When creating the Looper, the message queue of the thread is also created. You can see that prepareMainLooper will determine whether sMainLooper has a value. If it is called multiple times, an exception will be thrown, so that means there will only be one Looper and MessageQueue of the main thread. . Similarly, when Looper.prepare() is called in a child thread, the prepare(true) method will be called. If it is called multiple times, it will also throw an exception that each thread can only have one Looper. In summary, there is only one Looper in each thread. And MessageQueue.

//Looper.java
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

Let's take a look at the main thread sMainThreadHandler = thread.getHandler(). What getHandler gets is actually the mH Handler.

//ActivityThread.java
final H mH = new H(); 
@UnsupportedAppUsage
    final Handler getHandler() {
      return mH;
}

The mH Handler is an internal class of ActivityThread. By viewing the handMessage method, you can see that this Handler handles some messages of the four major components, Application, etc., such as creating a Service and binding some messages of 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));
    }
}

Finally, we look at the Looper.loop() method:

//Looper.java
public static void loop() {
      //获取ThreadLocal中的Looper
    final Looper me = myLooper();
        ···
    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;
        }
                ···
        msg.target.dispatchMessage(msg);
                ···
        //回收复用  
        msg.recycleUnchecked();
    }
}

The loop method is an infinite loop. Here, the message queue.next() is continuously obtained from the message queue, and then the message is distributed through the Handler (msg.target). In fact, there is no specific binding, because the Handler is in There is only one Looper and MessageQueue corresponding to each thread, and it is natural to rely on it for processing, that is, calling the Looper.loop() method. In the endless loop of Looper.loop(), messages are continuously fetched, and finally recycled and reused.

Here we must emphasize the parameter target (Handler) in Message. It is this variable that each Message can find the corresponding Handler for message distribution, allowing multiple Handlers to work at the same time.

Let's take a look at how the child thread is processed. First, create a Handler in the child thread and send Runnable.

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        new Thread(new Runnable() {
            @Override
            public void run() {
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
                    }
                });
            }
        }).start();

    }

After running, you can see the error log, and you can see that we need to call the Looper.prepare() method in the child thread, which is actually to create a Looper to "associate" with your Handler.

   --------- beginning of crash
2020-11-09 15:51:03.938 21122-21181/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.jackie.testdialog, PID: 21122
    java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,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.jackie.testdialog.HandlerActivity$1.run(HandlerActivity.java:31)
        at java.lang.Thread.run(Thread.java:919)

Add Looper.prepare() to create Looper, and call Looper.loop() method to start processing messages.

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //创建Looper,MessageQueue
                Looper.prepare();
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
                    }
                });
                //开始处理消息
                Looper.loop();
            }
        }).start();
    }

It should be noted here that the quit method should be called to terminate the message loop after all things are processed, otherwise the child thread will always be in a loop waiting state, so when it is not needed to terminate the Looper, call Looper.myLooper().quit().

After reading the above code, you may have a question. Isn’t it a problem to update the UI (toast) in the child thread? Isn’t our Android not allowed to update the UI in the child thread? In fact, this is not the case. In ViewRootImpl The checkThread method will verify mThread!= Thread.currentThread(). The initialization of mThread is in the constructor of ViewRootImpl, which means that a thread that creates ViewRootImpl must be consistent with the thread where checkThread is called. UI updates are not limited to The main thread can proceed.

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

Some concepts need to be introduced here. Window is a window in Android. Each Activity and Dialog and Toast correspond to a specific Window. Window is an abstract concept. Each Window corresponds to a View and a ViewRootImpl. Window and View pass ViewRootImpl to establish a connection, therefore, it exists in the form of View. Let's take a look at the creation process of ViewRootImpl in Toast. Calling toast's show method 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); //进行ViewRootImpl的创建
            trySendAccessibilityEvent();
        } catch (WindowManager.BadTokenException e) {
            /* ignore */
        }
    }
}

The final implementer of this mWM (WindowManager) is WindowManagerGlobal. In its addView method, ViewRootImpl is created, and then root.setView(view, wparams, panelParentView) is performed, and the interface is updated through ViewRootImpl and the process of adding Window is completed.

//WindowManagerGlobal.java
root = new ViewRootImpl(view.getContext(), display); //创建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);
        }
        throw e;
    }
}

SetView will internally complete the asynchronous refresh request through requestLayout, and will also call the checkThread method to verify the legitimacy of the thread.

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

Therefore, the creation of our ViewRootImpl is in the sub-thread, so the value of mThread is also the sub-thread, and our update is also in the sub-thread, so no exceptions will be generated. You can also refer to this article for analysis and write very detailed. Similarly, the following code can also verify this situation.

//子线程中调用    
public void showDialog(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                //创建Looper,MessageQueue
                Looper.prepare();
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        builder = new AlertDialog.Builder(HandlerActivity.this);
                        builder.setTitle("jackie");
                        alertDialog = builder.create();
                        alertDialog.show();
                        alertDialog.hide();
                    }
                });
                //开始处理消息
                Looper.loop();
            }
        }).start();
    }

Call the showDialog method in the child thread, first call the alertDialog.show() method, and then call the alertDialog.hide() method. The hide method only hides the Dialog without doing any other operations (without removing the Window), and then in the main The thread calls alertDialog.show(); it will throw Only the original thread that created a view hierarchy can touch its views exception.

2020-11-09 18: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)

Therefore, the focus of updating the UI in the thread is whether the thread where the ViewRootImpl and checkThread are the same.

How to access the network in the main thread

Add the following code before the network request:

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
StrictMode.setThreadPolicy(policy);

StrictMode (strict mode) introduced in Android 2.3, used to detect two major problems: ThreadPolicy (thread strategy) and VmPolicy (VM strategy), here the strict mode network detection is turned off, you can perform network operations in the main thread However, this is generally not recommended.

Why does the system not recommend to access the UI in a child thread?

This is because Android's UI controls are not thread-safe. If concurrent access in multiple threads may cause the UI controls to be in an unexpected state, then why doesn't the system add a lock mechanism to the UI controls? There are two disadvantages:

  1. First, adding a lock mechanism will complicate the logic of UI access
  2. The lock mechanism will reduce the efficiency of UI access, because the lock mechanism will block the execution of some threads.

So the simplest and most efficient method is to use a single-threaded model to handle UI operations. (Explore Android Development Art)

How does the child thread notify the main thread to update the UI (all through the Handle to send messages to the main thread to operate the UI)

  1. The Handler is defined in the main thread, the child threads send messages through mHandler, and the handleMessage of the main thread Handler updates the UI.
  2. Use the runOnUiThread method of the Activity object.
  3. Create a Handler and pass in getMainLooper.
  4. View.post(Runnable r) 。

Why doesn't Looper's infinite loop cause the application to freeze? Will it consume a lot of resources?

From the previous analysis of the main thread and sub-threads, it can be seen that Looper will continuously retrieve messages in the thread. If it is an infinite loop of the sub-thread, once the task is completed, the user should manually exit instead of letting it sleep and wait. (Quoted from Gityuan) A thread is actually a piece of executable code. When the executable code is executed, the life cycle of the thread should be terminated and the thread exits. As for the main thread, we never hope that it will be run for a period of time and then exit by ourselves, so how to ensure that it can always survive? The simple method is that the executable code can be executed continuously, and the endless loop can guarantee that it will not be exited. For example, the binder thread also adopts the endless loop method, and read and write operations with the Binder driver through a different loop method. Of course, it is not simply Endless loop, sleep when there is no message. Android is based on the message processing mechanism, and the user's behavior is in this Looper loop. When we tap the screen during sleep, we wake up the main thread to continue working.

Does the infinite loop of the main thread running all the time consume CPU resources? In fact, this is not the case, here is the Linux pipe/epoll mechanism. Simply put, when there is no message in the main thread's MessageQueue, it will be blocked in the nativePollOnce() method in the loop's queue.next(). At this time, the main thread will release the CPU The resource enters the dormant state until the next message arrives or a transaction occurs, and the main thread is woken up by writing data to the write end of the pipe. The epoll mechanism used here is an IO multiplexing mechanism that can monitor multiple descriptors at the same time. When a descriptor is ready (read or write ready), it will immediately notify the corresponding program to read or write operations, essentially synchronous I/O, that is, reading and writing are blocked. Therefore, the main thread is dormant most of the time and does not consume a lot of CPU resources.

When does the Looper of the main thread exit

When the App exits, the mH (Handler) in ActivityThread executes the exit after receiving the message.

//ActivityThread.java
case EXIT_APPLICATION:
    if (mInitialApplication != null) {
        mInitialApplication.onTerminate();
    }
    Looper.myLooper().quit();
    break;

If you try to manually exit the main thread Looper, the following exception will be 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)

Why is it not allowed to exit, because the main thread is not allowed to exit, once it exits, it means the program is hung up, and exit should not be used this way.

Handler message processing sequence

When Looper executes the message loop loop(), the following line of code will be executed, and msg.targe is the Handler object.

msg.target.dispatchMessage(msg);

Let's take a look at the source code of dispatchMessage:

  public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

1. If the Message object has a CallBack callback, the CallBack is actually a Runnable, and only this callback is executed, and then it ends. The CallBack code for creating the Message is as follows:

Message msgCallBack = Message.obtain(handler, new Runnable() {
    @Override
    public void run() {
    }
});

The handleCallback method calls Runnable's run method.

private static void handleCallback(Message message) {
    message.callback.run();
}

2. If the Message object does not have a CallBack callback, enter the else branch to determine whether the Handler's CallBack is empty. If it is not empty, execute the CallBack's handleMessage method, and then return. The CallBack code for constructing the Handler is as follows:

Handler.Callback callback = new Handler.Callback() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
          //retrun true,就不执行下面的逻辑了,可以用于做优先级的处理
        return false;
    }
};

3. Finally, the handleMessage() function of the Handler is called, which is the function we often rewrite, and the message is processed in this method.

scenes to be used

It can be seen that Handler.Callback has the priority to process messages. When a message is processed and intercepted by Callback (returns true), then the handleMessage(msg) method of Handler will not be called; if Callback processes the message, but not Intercept, then it means that a message can be processed by Callback and Handler at the same time. We can use the CallBack interception to intercept Handler messages.

Scenario: Hook ActivityThread.mH, there is a member variable mH in ActivityThread, which is a Handler and an extremely important class. Almost all plug-in frameworks use this method.

Handler.post (Runnable r) method execution logic

We need to analyze how the commonly used Handler.post(Runnable r) method is executed, whether a new thread is created, in fact it is not, this Runnable object is just called its run method, and it does not start one at all Thread, the source code is as follows:

//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;
    }

Finally, the Runnable object is packaged into a Message object, that is, the Runnable object is the CallBack object of the Message, and has the priority of execution.

How Handler performs thread switching

The principle is very simple. Resources are shared between threads. The child threads send messages through handler.sendXXX, handler.postXXX and other methods, and then use Looper.loop() to continuously retrieve the messages in the message queue, and finally hand it over to the handle.dispatchMessage method. Perform message distribution processing.

How to deal with memory leaks caused by improper use of Handler?

  1. There is a delayed message, remove Message/Runnable in time after the interface is closed, call handler.removeCallbacksAndMessages(null)
  2. Memory leaks caused by internal classes are changed to static internal classes, and weak references to the context or Activity/Fragment are used.

At the same time, there is a very critical point. If there is a delayed message, when the interface is closed, the message in the Handler has not been processed yet, then how is the message processed in the end? After testing, for example, after opening the interface, I delay sending a message for 10 seconds, and then close the interface. Eventually, I still receive the message (print log) in the handMessage method of the Handler (created by the anonymous inner class). Because there will be a reference chain of MessageQueue -> Message -> Handler -> Activity, Handler will not be destroyed, and Activity will not be destroyed.

Create Message instance correctly

  1. Obtained by Message.obtain() static method of Message;
  2. Through the Handler public method handler.obtainMessage()

All messages will be recycled and put into sPool, using Flyweight design pattern.

Interview review route

I won’t talk about the extra words. Next, I will share a review route for the interview. If you are also preparing for an interview but do not know how to review efficiently, you can refer to my review route. If you have any questions, please feel free to communicate with each other. Come on!

Here is a direction for everyone to learn systematically:

1. Watch the video for systematic learning

The experience of Crud in the past few years has made me realize that I am really a fighter in the rookie. It is also because of Crud that my technology is relatively fragmented and not deep enough to be systematic, so it is necessary to study again. What I lack is system knowledge, poor structural framework and ideas, so learning through videos is better and more comprehensive. Regarding video learning, individuals can recommend to study at station B. There are many learning videos on station B. The only drawback is that they are free and easily outdated.

In addition, I have collected several sets of videos myself, and I can share them with you if necessary.

2. To systematically sort out knowledge and improve reserves

There are so many knowledge points in client development, and there are still so little things in the interview. Therefore, there are no other tricks for the interview, just to see how well you prepare for these knowledge points. So, when you go out for an interview, it is good to see which stage you have reached in your review.

System learning direction:

  • Essential skills for architects: in-depth Java generics + annotations in simple language + concurrent programming + data transmission and serialization + Java virtual machine principle + reflection and class loading + dynamic proxy + efficient IO

  • Android advanced UI and FrameWork source code: advanced UI promotion + Framework kernel analysis + Android component kernel + data persistence

  • 360° overall performance tuning: design ideas and code quality optimization + program performance optimization + development efficiency optimization

  • Interpretation of open source framework design ideas: hot repair design + plug-in framework interpretation + component framework design + image loading framework + network access framework design + RXJava responsive programming framework design + IOC architecture design + Android architecture component Jetpack

  • NDK module development: NDK basic knowledge system + underlying image processing + audio and video development

  • WeChat Mini Program: Mini Program Introduction + UI Development + API Operation + WeChat Docking

  • Hybrid development and Flutter: Html5 project combat + Flutter advanced

After the knowledge is sorted out, it is necessary to check and fill in the vacancies. Therefore, for these knowledge points, I have prepared a lot of e-books and notes on hand. These notes provide a perfect summary of each knowledge point.

3. Read the source code, read the actual combat notes, and learn the ideas of God

"Programming language is the way the programmer expresses, and the architecture is the programmer's perception of the world." Therefore, if programmers want to quickly understand and learn the architecture, reading the source code is essential. Reading the source code is to solve problems + understand things, and more importantly: see the ideas behind the source code; programmers say: read thousands of lines of source code, and practice thousands of ways.

Mainly include WeChat MMKV source code, AsyncTask source code, Volley source code, Retrofit source code, OkHttp source code, etc.

4. On the eve of the interview, sprint questions

Within a week before the interview, you can start sprinting. Please keep in mind that when brushing the questions, the technology is the first priority, and the algorithm is basic, such as sorting, etc., and the intellectual questions are generally not asked unless they are school recruits.

Regarding the interview questions, I personally prepared a set of systematic interview questions to help you learn from one another:

The above content is free to share with everyone, friends who need the full version, click here to see all the content .

Guess you like

Origin blog.csdn.net/weixin_44339238/article/details/111870459