主頁 >  其他 > 花了幾天研究Handler訊息機制的成果

花了幾天研究Handler訊息機制的成果

2021-04-27 12:11:04 其他

最近花了幾天研究了一下Android的Handler機制,也閱讀了網上很多的資料,于是在此寫篇文章整理一下,想形成自己的體系,有一些別人總結的很好的干貨我這邊就不再總結了(但是會給出推薦鏈接),我只是把自己認為特別重要的寫在這里,

一.Handler機制是干啥的

我認為可以從兩方面來理解

1 從俠義上來說,handler(Android訊息機制的主要成員)是用來解決子執行緒無法訪問更改UI的問題的
2 從廣義上來說,所有的代碼都是在handler基礎上運行的,handler是Android的app運行的整個框架,Android系統框架內,Activity生命周期的通知等功能也是通過Handler訊息機制來實作的

1好理解,因為這是handler與我們距離最近的使用,但是為什么我認為有2這么一點呢?下面我來解釋下:

關于 2 的解釋

我們知道,安卓應用程式作為一個控制類程式,跟Java程式類似,都是有一個入口的,而這個入口就是ActivityThread,ActiviyThread也有一個main方法,這個main方法其實是安卓應用程式真正的入口,所以

①我們進入ActivityThreadmain方法看一下
public static void main(String[] args) {
		...
        Looper.prepareMainLooper();
        ...
        Looper.loop();
		
        throw new RuntimeException("Main thread loop unexpectedly exited");
}

省略無關代碼,我們不難發現,在main函式這里進行了兩個關于Looper的至關重要的操作,第一個是
Looper.prepareMainLooper();

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

發現這里是對sMainLooper 進行賦值,就是給主執行緒的looper賦值(looperhandler機制里面一個非常重要的角色)也就是創建了主執行緒的Looper

③然后Looper.loop(),我們追蹤進入loop方法
public static void loop() {
        ...

        for (;;) {
           ...
        }
    }

發現在loop方法里面,除了開頭幾行的一些賦值等等,后面就會進入一個死回圈,我們整個app的啟動,可以說都是在這個死回圈里面進行的,可以這么理解,就是可以把我們的app比喻成人,這個死回圈比喻成自然環境,只有自然環境能夠一直運行下去,正常運轉,那么人才能有生活的物資,才不至于滅亡,所以app就依賴這個死回圈才能在你不退出的情況下一直運行,所以我說2 不過分吧?

二.Handler機制的幾個角色及其相互關系

上面我們解決了兩個問題,其一是Handler機制是啥(主執行緒中更新UI),其二是Handler機制的重要性(Android的framework層的極端重要的部分),那么下面就來詳細地介紹下Handler機制涉及的幾個角色Handler,Looper,MessageQueue,Message及其相互關系

1.基本介紹

  • Handler: 發送訊息和處理訊息

  • Looper: 從MessageQueue中獲取Message,并將其交給 HandlerdispatchMessage(Message) 方法

  • MessageQueue: 訊息佇列,存放Handler發送過來的訊息

  • Message: 訊息的載體

2.他們之間的關系

他們之間的關系,我從兩個方面來分析

①其一,從基本功能的角度

首先,看這么一張圖

(1)MessageQueue就像履帶,它上面裝載著一個個Message
(2)Thread就像傳送帶的動力,對應到程式就是我們通信都是基于執行緒而來的,
(3)傳送帶的滾動需要一個開關給電機通電,那么就相當于我們的Looperloop函式,而這個loop里面的for回圈就會帶著傳送帶不斷的滾動,去輪詢messageQueue上面裝載的每一個Message
(4)Message就是 我們的貨物了,

②其二,從設計思想的角度


其實構成了執行緒模型中的經典問題 生產者-消費者模型

生產者-消費者模型:生產者和消費者在同一時間段內共用同一個存盤空間,生產者往存盤空間中添加資料,消費者從存盤空間中取走資料,

好處:能夠保證資料生產消費的順序(通過MessageQueue,先進先出) 不管是生產者(子執行緒)還是消費者(主執行緒)都只依賴緩沖區(handler),生產者消費者之間不會相互持有,使他們之間沒有任何耦合

③再總結下,就是下面這張圖

三.關于Handler機制的一些必要知識點

1.Handler有哪些發送訊息的方法

sendMessage(Message msg)
sendMessageDelayed(Message msg, long uptimeMillis)
post(Runnable r)
postDelayed(Runnable r, long uptimeMillis)
sendMessageAtTime(Message msg,long when)

//下面這些不怎么重要
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long uptimeMillis)
sendEmptyMessageAtTime(int what, long when)

2.從Handler發送訊息到訊息入隊,其內部是如何實作的?

①首先在子執行緒呼叫sendMessage方法發送訊息
public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}
②追蹤sendMessageDelayed
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
③發現呼叫了sendMessageAtTime,繼續追蹤
public boolean sendMessageAtTime(@NonNull 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);
}
④發現呼叫了HandlerenqueueMessage,繼續追蹤
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
     //將當前的Handler賦值給Message的target屬性
     msg.target = this;
     msg.workSourceUid = ThreadLocalWorkSource.getUid();

     if (mAsynchronous) {
         msg.setAsynchronous(true);
     }
     return queue.enqueueMessage(msg, uptimeMillis);
}
⑤發現最終呼叫了MessageQueueenqueueMessage方法,將訊息入隊

2.MessageQueue的必要知識

(1)基本介紹

MessageQueue維護了一個優先級佇列(以時間為順序進行排列),而且是以鏈表的形式實作的,其他執行緒若想發送訊息到此執行緒,則就要把訊息發送到此執行緒的MessageQueue

(2)排序依據when的介紹

MessageQueue不僅滿足先進先出,而且還有順序,其順序就是根據每一個訊息的when屬性來排序的,還記得一開始列出來的提交資訊的那些方法嘛,不管用哪個方法去提交訊息,最終一般都會走到sendMessageDelayed方法,而這個方法就可以設定when,我們進去看一下

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

可以看到這個when值是 SystemClock.uptimeMillis()delayMillis 之和,代表從系統啟動開始再加上你給它設定的延遲時間數,很好理解的,when就是用于表示 Message 期望被分發的時間,就是啥時候執行,

(3)在哪根據when來排序的
boolean enqueueMessage(Message msg, long when) {
		...
        synchronized (this) {
          	...
            msg.when = when;
            ...
            if (p == null || when == 0 || when < p.when) {
               ...
            } else {
                ...
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                    	//***************************
                    	//***************************
                    	//看這里看這里
						//***************************
						//***************************
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        ...
                    }
                }
               ...
            }

           ...
        }
      ...
    }

我們把相對無關的代碼都省去了,在代碼標注的位置,我們可以看到這個for死回圈跳出的條件有兩個,其一是一直遍歷走到MessageQueue的隊尾,才退出回圈,其二是發現有符合要求的地方可以插入message了,這個符合要求是啥意思,比如待插入的messagewhen是2,而MessageQueue佇列中的message分別是1,3,4,5.那么待插入的message就會插入到when13message之間,新佇列的when就變成了1,2,3,4,5,很好理解吧,

特殊情況,如果when相同,則按代碼執行時間先后順序插入,

(4)MessageQueue核心演算法enqueueMessage的完整分析

上面我們簡簡單單地分析了下enqueueMessage,下面我們就較為完整地分析下這個演算法

boolean enqueueMessage(Message msg, long when) {
    // Hanlder為空則拋例外
    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.");
    }

    // 對MessageQueue進行加鎖
    synchronized (this) {
        // 判斷目標thread是否已經死亡
        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;
        }
        // 標記Message正在被執行,以及需要被執行的時間
        msg.markInUse();
        msg.when = when;
        // p是MessageQueue的鏈表頭
        Message p = mMessages;
        // 判斷是否需要喚醒MessageQueue
        boolean needWake;
        // 如果有新的隊頭,同時MessageQueue處于阻塞狀態則需要喚醒佇列
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            //...
            // 根據時間找到插入的位置
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                //...
            }
            msg.next = p; 
            prev.next = msg;
        }

        // 如果需要則喚醒佇列
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

需要注意的是:當新插入的Message在鏈表頭時,如果messageQueue是空的或者正在等待下個延遲訊息,換句話說就是處于阻塞狀態的時候,則需要喚醒MessageQueue

3.Looper的必要知識

(1)簡單介紹

Looper是個什么角色呢?從一個簡單的例子來說吧,我們在子執行緒創建Handler實體的時候,必須要先創建一個Lopper,并開啟回圈讀取訊息,在主執行緒中我們ActivityThread已經幫我們創建了(在本文最開始的地方就有介紹),所以我們不需要再次創建,但是在子執行緒中新建Handler的時候就需要創建咯!比如這樣

public static class MyThread extends Thread {
    public Handler mHandler;
 
        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                public void handleMessage(@NonNull Message msg) {
                                        //處理接收的訊息
                }
            };
            Looper.loop();
        }
}

一個執行緒對應一個Looper,但是可以有多個HandlerLooper是訊息輪詢的基礎,比如本文最開始的傳送帶圖片,沒有Looper那么整個Handler機制就無法運轉,而且Looper還不能多,否則傳送帶就會崩掉,違背單線操作的概念,造成不合適的并發操作,

  • 值得注意的是,我們看下Looper的構造方法,發現我們在創建Looper的時候,也會捎帶著創建MessageQueue
 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
(2)Looper的作業介紹(訊息是怎么被讀取的)

剛剛說了用Loop來輪詢訊息,那么具體是怎么個詢法呢?沒錯,就是Looperloop方法,我們要想搞懂Looper的訊息輪詢機制,就要從loop方法開始,(本文開始也有一丟丟對loop方法的分析,不過這里才是更全面的)

public static void loop() {
      	...
        for (;;) {
            Message msg = queue.next(); // might block
            ...
        }
    }

我們可以看到,在開啟死回圈后,獲取每一個訊息的方式是呼叫queuenext方法,而且值得注意的是,在原始碼的注釋中給了兩個單詞:might block,即有可能是阻塞的,好,下面追蹤queuenext方法看看

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // 原始碼中的注釋表示:如果looper已經退出了,這里就回傳null
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    //...
    // 定義阻塞時間賦值為0
    int nextPollTimeoutMillis = 0;
    //死回圈
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 阻塞對應時間 這個方法最侄訓呼叫到linux的epoll機制
        nativePollOnce(ptr, nextPollTimeoutMillis);
        // 對MessageQueue進行加鎖,保證執行緒安全
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //...
            if (msg != null) {
                if (now < msg.when) {
                    // 將要獲取的下一個訊息(也就是隊頭)還沒開始,計算等待時間
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 獲得訊息且現在要執行,標記MessageQueue為非阻塞
                    mBlocked = false;
                    // 鏈表操作
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 沒有訊息,進入阻塞狀態
                nextPollTimeoutMillis = -1;
            }
            //退出
            if (mQuitting) {
                 dispose();
                 return null;
             }
            
    }
}

總結下:先看下Looper是否已經退出(怎么退出后面講),然后進入死回圈,首先判斷是否要進行阻塞(判斷是依據是上一次回圈得到的某個值:nextPollTimeoutMillis ),阻塞最侄訓呼叫到linuxepollpipe/epoll)機制,阻塞結束后會進入中,得到隊首的Message物件,然后進入if判斷,如果有訊息,即把隊頭訊息取出,然后進行判斷,決定是當下立即回傳還是delay一下再回傳,如果沒有訊息,則無限期進入阻塞狀態,直到有訊息進入,

  • nextPollTimeoutMillis表示阻塞的時間,其中,-1表示無限時間,直到有訊息傳入為止,0表示不阻塞,
(3)Looper的退出

剛剛說了Looper的作業原理,那么作業完了怎么退出呢?
Looper提供了quitquitSafely兩個方法來退出一個Looper,二者的區別是: quit會直接退出Looper,而quitSafely只是設定一個標記,然后把訊息佇列中的已有訊息處理完畢后才安全退出,這個好理解,

public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
    mQueue.quit(true);
}

其實兩個方法最終都是呼叫mQueuequit方法,讓我們來分析一下這個方法

// 最終都是呼叫到了這個方法
void quit(boolean safe) {
    // 如果!mQuitAllowed為true即不能退出則拋出例外,
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;
        // 執行不同的退出邏輯方法
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }
        // 喚醒MessageQueue
        nativeWake(mPtr);
    }
}

這個方法首先根據mQuitAllowed引數判斷是否能退出,如果可以退出則進入中執行退出邏輯,如果mQuitting==true,那么這里會直接return掉,而且mQuitting這個變數只有在這里被執行了賦值,所以一旦looper退出,則無法再次運行了,也就是說Looper只能創建一次,一旦quit之后此執行緒就無法再復活了,

  • 而且同一個執行緒,只能創建一個Looper,因為Looper的創建是通過Looper.prepare方法實作的,而在prepare方法中就判斷了,當前執行緒是否存在Looper物件,如果已經存在則拋出例外,所以同一個執行緒,只能創建一個Looper,多次創建會報錯,
    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));
    }

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

之后執行不同的退出邏輯,然后喚醒MessageQueue,之后MessageQueuenext方法會退出,Looperloop方法也會跟著退出,那么執行緒也就停止了,

(4)關于Looper的幾個騷操作
①如何獲取當前執行緒的looper
Looper.myLooper()

內部原理就是通過 ThreadLocal<Looper>sThreadLocal物件的get方法來獲取 Looper
sThreadLocal物件在整個app中只有一個,因為它是由static final修飾的


public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
②如果在任意執行緒獲取主執行緒的 Looper
Looper.getMainLooper()
③如何判斷當前執行緒是不是主執行緒

方法一:

Looper.myLooper() == Looper.getMainLooper()

方法二:

Looper.getMainLooper().getThread() == Thread.currentThread()

方法三: 方法二的簡化版

Looper.getMainLooper().isCurrentThread()

4.關于ThreadLocal

ThreadLocalJava中一個用于執行緒內部存盤資料的工具類,并不是Android所特有的,
值得注意的是,在不同的執行緒中訪問同一個threadLocal物件,但是它們獲取的值卻是不一樣的,這就是ThreadLocal的奇妙之處,那么問題來了,原理是什么?
我們進入其set方法看一下

public void set(T value) {
		//得到當前執行緒
        Thread t = Thread.currentThread();
        //得到當前執行緒的ThreadLocalMap 
        ThreadLocalMap map = getMap(t);
        if (map != null)
        	//將當前的ThreadLocal變數作為key,傳進來的泛型作為value進行存盤
            map.set(this, value);
        else
        	//沒有map則創建,并且將當前的ThreadLocal變數作為key,傳進來的泛型作為value進行存盤
            createMap(t, value);
    }

我們可以發現,每一個執行緒都維護著一個Map集合,其名字為ThreadLocalMap ,我們可以通過
getMap方法來獲得,此集合是以鍵值對的方式來存盤資料的,鍵為ThreadLocal物件,也就是呼叫set方法的那個ThreadLocal物件,這個值就是set()里面傳入的引數了,每一個執行緒都有一個ThreadLocalMap ,雖然鍵相同,但是取出來的值是不相同的,

  • 我們可以這樣類比,就是ThreadLocal是一個人,不同的執行緒即不同的Thread和不同的ThreadLocalMap,是不同的環境,相同的一個人(key),在學校(學校的Thread,里面有學校特有的ThreadLocalMap)里面他的身份(value)就是學生,在家庭里面他的身份(value)可能是父親,在社會上他的身份(value)就是中國公民,即同一個人在不同的環境下有不同的身份,同一個ThreadLocal在不同的執行緒下可能映射著不同的值,就是這個道理,
  • 前面在Looper中提到的的 ThreadLocal<Looper>sThreadLocal,它的get方法就是回傳每個執行緒所特有的Looper,其也是從ThreadLocalMap中取值,雖然整個App中sThreadLocal是唯一的,但是每個執行緒ThreadLocalMap是不同的,所以不同的執行緒能得到不同的Looper

5.同步屏障

Handler機制中,有三種訊息型別:

①同步訊息,也就是普通的訊息,
②異步訊息,通過setAsynchronous(true)設定的訊息,
③同步屏障訊息,通過postSyncBarrier方法添加的訊息,特點是target為空,也就是沒有對應的handler

同步屏障是什么意思?可以這么理解,比如救護車,在正常情況下,沒有拉病人的情況下是可以和其他車一樣在公路上正常行駛的,但是如果拉上病人就不一樣了,不僅需要加急,不用等紅燈,而且前面還有一輛警車幫忙帶路,其實這個救護車就可以理解成異步訊息,警車就可以理解成同步屏障訊息,在后方有承載著病人的救護車的時候,其他車輛都會被警車“屏障”,救護車優先通行,這就是同步屏障機制存在的意義,當有要緊的異步訊息需要立馬被執行的時候,同步屏障訊息就來了,具體程序我們可以看一下原始碼

Message next() {
        ...
        for (;;) {				
			...
            synchronized (this) {
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
				...
            }
			...
        }
    }

我們可以看到,在next方法,每次取出訊息的時候,會先進行一個if判斷,即if (msg != null && msg.target == null) ,就是如果某個msg不為null而且它的targetnull,那么它就是一個同步屏障訊息,那我們就要進入這個if,此時會往后逐個遍歷MessageQueue中的訊息,當發現下一個異步訊息的時候,就退出回圈,得到這個異步訊息然后加急進行處理,

  • 值得注意的是:同步屏障訊息只是一個標志,就像我一開始說的警車一樣,它上面并沒有承載病人,真正承載病人的是后面的救護車,也就是有重要資訊的異步訊息,
  • 而且并不是所有的異步訊息都是需要加急處理的,正常情況下異步訊息和同步訊息都是可以根據when來決定處理順序的,當然只是正常情況下,畢竟異步訊息設計出來就是要被加急處理的,就像救護車的職能就是緊急救援病人的,

用張圖表示就是這樣

那么有什么加急訊息呢?比如UI的更新,這就是一個加急訊息,相信不用我解釋大家都能理解咯,

6.HandlerThread

HandlerThread是啥?看名字就知道,其實它繼承了Thread,本質上也是一個Thread,但是它的run函式中已經幫我們完成了Looper的創建,不信你看

public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
}

所以它第一個好處就是方便我們使用,也就是我們使用子執行緒創建Handler的時候就不用再顯示地創建Looper了,
但是,光知道這個還是不夠的,萬一在HandlerThread還沒有創建好Looper的時候就呼叫getLooper方法怎么辦呢?谷歌工程師早就想到了這個執行緒安全問題,所以我們看getLooper方法

public Looper getLooper() {
        ...
        // If the thread has been started, wait until the looper has been created.
        //意思是如果執行緒已經啟動了,那么等looper創建了之后再回傳
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

從這個方法可以看出,人家getLooper方法并不是無腦給你回傳looper,因為有可能還沒有創建好,所以先wait,等前面run方法創建了looper之后notifyAll,這里getLooper才給你回傳looper,這就是第二個好處,執行緒安全

7.IntentService

首先看原始碼

public abstract class IntentService extends Service {
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
}

首先它是一個服務,可以借助子執行緒HandlerThread ,將訊息一個接一個地執行,而且訊息佇列的特點是前面的訊息執行完后面的訊息才能執行,所以就可以讓子任務在子執行緒中有條不紊地一個接一個地執行,而且訊息執行完畢之后服務自動關閉stopSelf,實作了記憶體釋放,
我們的耗時任務的邏輯就是重寫onHandleIntent方法,在執行耗時任務的時候就會回呼我們重寫的onHandleIntent方法,
簡單來說,這就是一個可以在子執行緒進行耗時任務,并且在任務有序執行后自動停止的Service

8.Handler記憶體泄漏

小題大做 | 記憶體泄漏簡單問,你能答對嗎(強烈建議大家學習此文)

參考文章

溫故而知新 | 打破Handler問到底(這個強烈推薦大家學習)
你真的懂Handler嗎?Handler問答
"一篇就夠"系列: Handler訊息機制完全決議

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/280556.html

標籤:其他

上一篇:開源的關于智能車競賽光電起始點的開源方案

下一篇:基于正點原子STM32F1精英版秒表(庫函式版)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more