当前位置:网站首页>Further exploration of handler (I) (the most complete analysis of the core principle of handler)

Further exploration of handler (I) (the most complete analysis of the core principle of handler)

2022-06-27 03:26:00 AD calcium milk Lalalala

I wrote it a long time ago Handler Related articles of , Now look back , The understanding is still relatively shallow . So , I decided to do another in-depth study Handler.

Let's start with a question : What are the communication methods from the sub thread to the main thread ? What is the principle of sub thread to main thread communication ?

You may answer :RxJava,Handler,EventBus, radio broadcast . But the essence behind these appearances is a set of mechanisms , Namely Handler. You can say that ,Android The core of inter thread communication is Handler.

First let's look at Handler Use , I won't say anything specific , Everyone must be very clear :

Sub thread :handler.sendMessage()

The main thread :handler.handleMessage()

It is probably through this way that inter thread communication is realized , Let's start with the source code , Thinking principle .Handler The source location of is frameworks/base/core/java/android/os below , I'm looking at android9.0 Of . Because I usually study with Mac, So I use the source code tool Sublime Text. We must put down the system source code , Directly from AS Many source codes in the tool are actually hidden .

stay Handler Here we see this line of code :

public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
}

Continue to look at :

public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, 
        SystemClock.uptimeMillis() + delayMillis);
}

Go on and watch :

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
}

The key is coming. , We are sendMessageAtTime There is a data structure :MessageQueue. Message queue . Let's take a look at the data structure along the source code :

Message mMessages;

We are Message I saw the maintenance Message, Go in and have a look Messaage Data structure of :

/*package*/ int flags;

/*package*/ long when;

/*package*/ Bundle data;

/*package*/ Handler target;

/*package*/ Runnable callback;

// sometimes we store linked lists of these things
/*package*/ Message next;

Mesage There is another attribute in it :Message next. Obviously the ,Message Data structure of a linked list . We're going back to sendMessageAtTime Method , It turns out that the final call is enqueueMessage Method :

private boolean enqueueMessage(
                MessageQueue queue, 
                Message msg, 
                long uptimeMillis){
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

The final call is MessageQueue Of enqueueMessage Method :

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

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            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 {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

Let's focus on this code :msg.target Namely Handler Self object , Obviously, it cannot be empty .msg.isInUse The corresponding method is msg.markInUse, If this message is marked , that msg.isInUse It will return true.mQuitting Marks whether the sending message thread exits , If you quit, there is no need to continue . Of course, the front is not the point , Let's see if-else Inner logic :

  • p==null Obviously not , The last two lines of code Message p = mMessage so , Generally, it will not be the first message ( If it is the first message , go if Logic , take msg Assign to mMessages);
  • when==0 Set up ? Someone will say :sendMessage It's called in sendMessageDealyed No, it's not 0 Do you ? Take a closer look ,sendMessageDelayed What's in it ,SystemClock.uptimeMillis() + delayMillis, Do you see this code . Then pass it on layer by layer , therefore when A more accurate understanding is the specific time , Not the delay time ;
  • when<p.when Set up ? In general, it is not tenable .

  Then we just need to pay attention to else Just the code inside ,if Inside, we'll look back . Next, let's look at the linked list msg operation , Let's take out the code separately :

                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;

That's the code , I guess many people can't understand it , however , Is the core of the core ! We will first p Assign to prev, Obviously this p It's original msg Linked list , And then p.next Assign to p, This time for null 了 , Out of the loop .

Again msg.next assignment p, It's assignment null, And then prev Linked list next Point to msg. This completes the whole process of sending messages , so : The essential logic of message sending is to store the message in MessageQueue Held Message Inside the list .

Let's take a look at Handler Construction :

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

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

so , Initializing Handler When ,Handler Just like Looper It establishes a relationship . Take a look myLooper Method :

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}

We can't help thinking , We can get To a Looper object , So when is this object set Come in ?

Let's look at a class :ActivityThead. This class is in the frameworks/base/core/java/android/app Next .

Be careful Main Method :

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();//1

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        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();//2

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

Lots of code , We just need to focus on the tag 1 And tags 2 It's about . Mark 1 Click the code at to have a look :

(Looper The method inside )

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
}

Continue to look at :

(Looper The method inside )

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

Come here , We already know that ThreadLocal Inside Looper When is the object set It's coming in .

Let's look at the mark 2 It's about , Called Looper Of loop Method .

public static void loop() {
        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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

Let's analyze this method :

  • adopt myLooper Get the main thread Looper Objects and Looper Object maintained MessageQueue;
  • Polling the message queue continuously through an endless loop (queue.next());
  • msg.target.dispatchMessage(msg).

Focus on the next step 3 Well , We know msg.target Namely Handler Object itself , So let's look again Handler Of dispatchMessage Method :

    public void handleMessage(Message msg) {
    }
    
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

diapatchMsg The final call is handleMessage. In this way, the process from sending messages to receiving messages forms a closed loop . Come back to , Look again. :

What are the communication methods from the sub thread to the main thread ? What is the principle of sub thread to main thread communication ?

Give a brief answer : The child thread passes through handler Send a message to the message queue , And through msg.target Flag bit save handler Object reference , Main thread pass Looper.loop Keep polling message queues , And finally call the handler Of handleMessage Method . In fact, the essential problem lies in MessageQueue In the message queue Message Linked list data structure , This one is in the heap memory , Belongs to the thread share .

We often hear Handler Memory leaks , So here comes the question :Handler What is the cause of the memory leak ?

We have analyzed such a long source code , Let's look at the reference chain :

MainActivity <- Handler <-Message(msg.target) <- MessageQueue <- Looper.

We know that as long as the application is live ,Looper All the objects will be there , and Handler yes Acticity The inner class of , Internal classes hold external class references by default , thus , The chain of references will not break . according to GC Reachability algorithm ,MainActivity It won't be recycled , Cause memory leaks . Be careful : A memory leak occurs when a child thread sends a message , The main thread exits before receiving the message Acticity, For example, send a delay news . If the main thread receives a message , Will execute msg Of recycleUnchecked() Method :

void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
}

Will target Set up null, In this way, the reference chain will also be in Handler <- Message It's broken here .

原网站

版权声明
本文为[AD calcium milk Lalalala]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/178/202206270313052320.html