主頁 > 移動端開發 > Android Handler 從使用到進階

Android Handler 從使用到進階

2021-05-03 07:48:49 移動端開發

文章目錄

    • 1.Handler
    • 2.Handler簡單使用
      • 2.1 發送訊息
      • 2.2 使用
      • 2.3 view.post()
      • 2.4 runOnUiThread
    • 3.子執行緒中使用
      • 3.1 子執行緒直接創建Handler錯誤
      • 3.2 主執行緒默認初始化Looper
      • 3.3 Handler構造方法查看
      • 3.4 子執行緒正確的創建
    • 4.Message
      • 4.1 基本引數
      • 4.2 享元模式obtain()
      • 4.3 回收recycle()
    • 5.MessageQueue
      • 5.1 MessageQueue每個執行緒只有一個
      • 5.2 訊息入隊
      • 5.3 訊息出隊
      • 5.4 退出
    • 6.Looper
      • 6.1 ThreadLocal
      • 6.2 初始化prepare(),為何只能呼叫一次
      • 6.3 系結當前執行緒,創建訊息對列
      • 6.4 拿到當前執行緒的looper
      • 6.5 loop()
      • 6.6 退出
    • 7.Handler
      • 7.1 消發送息
      • 7.2 處理訊息
    • 8.記憶體泄漏
    • 9.HandlerThread
    • 參考檔案

1.Handler

  • 只要是開發Android的同學,Handler這個經常都會看到,也會使用到,本文章就做個筆記,
  • Android規定了只能在主執行緒更新UI,那子執行緒想更新UI,在操作完成后,就可用通過Handler發訊息,然后在主執行緒更新UI了,其實可以理解為生產者-消費者模式,發送訊息,取出訊息并處理,
  • Android系統原始碼中,Android的訊息機制中,大量使用Handler,所以了解Handler非常的有必要,
  • 下圖是一個訊息發送的簡易流程,一各個步驟分析,
    Handler

2.Handler簡單使用

2.1 發送訊息

  • 最基本的使用就是各種sendMessage,帶不帶引數,是否延遲等,
  • 發送訊息方法非常多,根據自己需求選擇,所有發送訊息最后都是
  • enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis)

發送訊息

2.2 使用

  • 這樣直接使用會有記憶體泄漏風險,后面說,
  • Handler創建有兩種,一個是在構造方法傳CallBack,一個是重寫類的 handleMessage() 方法,
	private static final int MESSAGA_TEST_1 = 1;

    /**
     * 主執行緒有初始化好Looper,所以在主執行緒處理訊息
     */
    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case MESSAGA_TEST_1:
                    if (tvTest != null) {
                        //傳遞訊息obj
                        tvTest.setText((String) msg.obj);
                    }
                    break;
            }
            return false;
        }
    });

 
    private Handler mHandler2 = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case MESSAGA_TEST_1:
                    if (tvTest != null) {
                        //傳遞訊息obj
                        tvTest.setText((String) msg.obj);
                    }
                    break;
            }
        }
    };

    private void onClick() {
        //當點擊事件執行,就會在子執行緒發送訊息更新textview
        new Thread(new Runnable() {
            @Override
            public void run() {
                clickEndMessage();
            }
        }).start();
    }
  

    /**
     * 可以在子執行緒發送
     */
    private void clickEndMessage() {
        //obtain享元模式
        Message message = Message.obtain(mHandler);
        //what相當于標記
        message.what = MESSAGA_TEST_1;
        //obj傳資料
        message.obj = new String("baozi");
        //普通發送message
        mHandler.sendMessage(message);
 		.
 		.
 		.
        //發送標記,不帶其他內容,內部會封裝成只有標記的Message物件
        mHandler.sendEmptyMessage();
        //尾部帶有Delayed,發送延遲訊息,單位毫秒
        mHandler.sendMessageDelayed(message, 1000);
        //尾部帶有AtTime,發送訊息的時間跟Delayed差別就是Delayed是執行的當前時間+傳進去的時間,AtTime就傳進去的絕對時間
        mHandler.sendMessageAtTime();
        //在佇列頭插入訊息
        mHandler.sendMessageAtFrontOfQueue(message);
    }


    @Override
    protected void onDestroy() {
        if(mHandler!=null){
        	//關閉activity時,移除訊息
            mHandler.removeMessages(MESSAGA_TEST_1);
            mHandler = null;
        }
        super.onDestroy();
    }

2.3 view.post()

  • 比如view,post()、postDelayed() 方法,可以延遲五秒后更新UI,實際就是使用Handler,
tvTest.postDelayed(new Runnable() {
    @Override
    public void run() {
        tvTest.setText("5s");
    }
},5*1000);

/**
 * View 原始碼
 */
public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().postDelayed(action, delayMillis);
    return true;
}

2.4 runOnUiThread

  • 經常用的 runOnUiThread() 方法也是用Handler,
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        //更新UI
    }
});  

/**
 * Activity 原始碼
 */
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

3.子執行緒中使用

3.1 子執行緒直接創建Handler錯誤

  • 子執行緒不能直接創建Handler,會報例外,因為Looper還沒創建,而主執行緒默認就初始化好Looper
  • 應該先Looper.
    private Handler handler2;

    /**
     * 子執行緒
     */
    private void thread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler2 = new Handler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(@NonNull Message msg) {
                        return false;
                    }
                });
            }
        }).start();
    }
  • 提示的錯誤,
    子執行緒創建Handler

3.2 主執行緒默認初始化Looper

  • ActivityThread 類就能主執行緒找到Looper初始化,Looper.prepareMainLooper();
    public static void main(String[] args) {
        .
        .
        Looper.prepareMainLooper();
		.
		.
	      
        ActivityThread thread = new ActivityThread();
        .
        .
    }

3.3 Handler構造方法查看

  • 構造方法中可以看到 mLooper = Looper.myLooper(); 獲取的 mLooper 為null,就報上面的那個例外了,
    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()");
        }
        .
        .
    }

3.4 子執行緒正確的創建

  • 先執行,Looper.prepare(); 初始化Looper,然后呼叫loop()方法,再創建Handler,
  • 每個執行緒**Looper.prepare();**只能呼叫一次,否則會報錯,
    private Handler handler2;

    /**
     * 子執行緒
     */
    private void thread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Looper looper = Looper.myLooper();
                looper.loop();

                handler2 = new Handler(looper, new Handler.Callback() {
                    @Override
                    public boolean handleMessage(@NonNull Message msg) {
                        return false;
                    }
                });
            }
        }).start();
    }

4.Message

  • Message就是一個存放訊息的類,是一個鏈表結構,

4.1 基本引數

public final class Message implements Parcelable {
   	//用于handler標記處理的
    public int what;
    //可以傳遞的int引數1
    public int arg1;
    //可以傳遞的int引數2
    public int arg2;
	//可以傳遞的obj引數
    public Object obj;

    //執行時間
    public long when;
	//傳遞的bundle
    Bundle data;
	//Message系結的Handler
    Handler target;
	//Handler.post()時傳的callback
    Runnable callback;
	//鏈表結構
    Message next;

4.2 享元模式obtain()

  • obtain() 可以重用Message,減少開銷提高性能,
  • 菜鳥教程-享元模式
    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

4.3 回收recycle()

  • 如果發送的延遲訊息,或者訊息在執行,就會報錯,一般我們不用呼叫recycle方法,
    /**
     * Return a Message instance to the global pool.
     * <p>
     * You MUST NOT touch the Message after calling this function because it has
     * effectively been freed.  It is an error to recycle a message that is currently
     * enqueued or that is in the process of being delivered to a Handler.
     * </p>
     */
    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }
  • Message用完并不是記憶體回收,只是把里面的內容清空,等下次復用,
  • 這個鏈表也不是無限的,最多就50個節點
    private static final int MAX_POOL_SIZE = 50;

    @UnsupportedAppUsage
    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 = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
        	//最多50個
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

5.MessageQueue

  • 這是一個阻塞佇列,
  • 佇列是在不停的for回圈的,是一個死回圈,那不就一直占用著cpu?所以就有了native的方法,處理休眠喚醒,

5.1 MessageQueue每個執行緒只有一個

  • 訊息佇列每個執行緒只有一個,跟Looper系結在一起,在分析Looper時會一起分析,

5.2 訊息入隊

  • 前面我們知道Handler所有訊息入隊最后都是呼叫 enqueueMessage(Message msg, long when)
  • 判斷插入訊息的位置,還判斷是否喚醒操作,
    boolean enqueueMessage(Message msg, long when) {
        //handler為空就報例外
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //加鎖
        synchronized (this) {
        	//訊息正在使用會報錯
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
			//判斷執行緒是否存活
            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) {
            	//如果佇列為空,或者訊息延遲時間為0,或者延遲時間小于mMessage的,就插入在頭部
                // 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;
    }

5.3 訊息出隊

  • 訊息出隊其實應該放在 Looper.loop() 里面分析更合適,這里先寫,后面結合 loop() 一起看會更好,
  • 前面 return null; 看注釋的意思是,如果looper已經退出和釋放,就回傳null,
  • 這里無限回圈,就是一定要取到訊息,有訊息,阻塞時間為訊息的執行時間減去當前時間,如果沒訊息就阻塞, nativePollOnce(ptr, nextPollTimeoutMillis)
  • 這 next() 方法只有一個地方回傳msg,關注這里就行了,
    @UnsupportedAppUsage
    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
			//沒訊息取就阻塞
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                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());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        //阻塞時間為訊息的執行時間減去當前時間
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        //只有這里回傳msg
                        return msg;
                    }
                } else {
                	//沒有更多訊息,設定為-1,阻塞
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

				.
				.
        }
    }

5.4 退出

  • 主執行緒是不能退出,
  • 傳入的 safe 處理,分別是移除訊息未執行的訊息,和移除全部訊息,
    void quit(boolean safe) {
    	//主執行緒是不能退出的
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;
			
			//移除訊息
            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
	/**
	 * 移除全部訊息
	 */
    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

	/**
	 * 移除未執行的訊息,正在運行的等待完成再回收
	 */
    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
        	
            if (p.when > now) {
            	//如果執行時間還未到,即未執行的訊息,移除回收
                removeAllMessagesLocked();
            } else {
            	//等待在執行的訊息執行完再回收移除
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

6.Looper

  • Handler要負責發送訊息,MessageQueue訊息佇列存放訊息,Looper就是負責訊息回圈,
  • 商場的扶手電梯大家應該都知道吧,可以把扶手電梯的電機看成Looper,一梯一梯看成訊息佇列MessageQueue,坐電梯的人看成Message,就這樣不停的回圈,把訊息送去處理,

6.1 ThreadLocal

  • Android 開發也要掌握的Java知識 -ThreadLocal 可以看下ThreadLocal原理,
  • Looper就是用到了ThreadLocal,Looper內部直接就有一個,還定義成static,final了,也就意味著Android里面獲取的ThreadLocal只有這一個,
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

6.2 初始化prepare(),為何只能呼叫一次

  • 在prepare()方法可以看到,如果初始化過,就呼叫就會報錯了,所以每個執行緒最多只有一個Looper,但Handler可以有很多個,
    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
    	//每個執行緒只能有一個looper
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //初始化后設定給sThreadLocal
        sThreadLocal.set(new Looper(quitAllowed));
    }

6.3 系結當前執行緒,創建訊息對列

  • 創建訊息對列,Looper系結到當前Thread,
  • quitAllowed訊息佇列是否可銷毀,主執行緒的是不可銷毀的,子執行緒默認是可銷毀,
    private Looper(boolean quitAllowed) {
    	//創建訊息佇列
        mQueue = new MessageQueue(quitAllowed);
        //系結當前執行緒
        mThread = Thread.currentThread();
    }

6.4 拿到當前執行緒的looper

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

6.5 loop()

  • 首先拿到當前執行緒的looper,如果沒有prepare()那就是空,報例外,
  • 然后就是不停的回圈,取出訊息,再處理訊息,
  • Android中為什么主執行緒不會因為Looper.loop()里的死回圈卡死?
  /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
      	//拿到當前執行緒的looper,如果沒有prepare()那就是空,報例外
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }
      .
      .
      .

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

            .
            .
            try {
            	//處理訊息,msg.target就是系結的handler
                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);
                }
            }
            .
            .
			//回收訊息
            msg.recycleUnchecked();
        }
    }

6.6 退出

  • 退出 Looper 其實就是呼叫 MessageQueue 的退出,傳的 safe 不同而已,那這兩段注釋說啥,
  • 傳 false ,就是說后面發送訊息都不會再處理了,發送訊息全部都失敗,而且該方法不安全,建議使用 quitSafely(),
  • 傳 true,跟上面效果差不多,就是MessageQueue的邏輯,移除未執行的訊息,正在運行的等待完成,
    /**
     * Quits the looper.
     * <p>
     * Causes the {@link #loop} method to terminate without processing any
     * more messages in the message queue.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p class="note">
     * Using this method may be unsafe because some messages may not be delivered
     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
     * that all pending work is completed in an orderly manner.
     * </p>
     *
     * @see #quitSafely
     */
    public void quit() {
        mQueue.quit(false);
    }

    /**
     * Quits the looper safely.
     * <p>
     * Causes the {@link #loop} method to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * However pending delayed messages with due times in the future will not be
     * delivered before the loop terminates.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p>
     */
    public void quitSafely() {
        mQueue.quit(true);
    }

7.Handler

7.1 消發送息

  • Handler 發送訊息最后都是執行 enqueueMessage()
    enqueueMessage
  • 這就做了 Message 的 target 指向 handler 自己,
  • 訊息入隊,
  • mAsynchronous 默認就是false , 構造方法我們基本也不會去動這引數,默認handler 是同步的,
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //訊息的target就是handler
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
		//設定異步
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //訊息入隊
        return queue.enqueueMessage(msg, uptimeMillis);
    }

7.2 處理消息

  • Looper.loop() 里面 msg.target.dispatchMessage(msg); 就是讓 Handler 處理訊息,
  • 這里可以看到三種,第一種是 Handler 發送訊息用的 post , 就會把 Callback 封裝到 Message ,走 Message 自己的 Callback ,
  • 第二種是Handler 創建時,重寫了 Callback,這是有回傳值得,如果為 true ,就會再執行下面的 handleMessage 方法,
  • 第三種就是沒傳 Callback ,就執行 Handler 自己的 handleMessage ,但要重寫該方法,
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
    	//Message 內部的callback,就是Handler,post()方法封裝在Message內部的
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
        	//如果Handler創建時傳Callback就執行這里
            if (mCallback != null) {
            	//如果回傳值為false 就結束
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //如果mCallback為null,或者上面回傳值為true,就執行這里
            handleMessage(msg);
        }
    }
    
     /**
     * Message 內部的callback,就是Handler,post()方法封裝在Message內部的
     */
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    
    /**
     * 創建時如果傳Callback,就執行
     */
    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);
    }
    
    /**
     * 創建時如果沒傳Callback,而是重寫了該方法
     */
    public void handleMessage(@NonNull Message msg) {
    }

8.記憶體泄漏

  • 面試的時候賊喜歡問這個問題!!!
  • 轉 小題大做 | 記憶體泄漏簡單問,你能答對嗎

9.HandlerThread

  • HandlerThread 是谷歌封裝好 Looper 的 Thread ,子執行緒如果需要Handler推薦使用
  • 這里就是保證在使用Handler之前,Looper 一定準備好,
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
 	@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        //獲得鎖創建looper
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
     
    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason isAlive() returns false, this method will return null. If this thread
     * has been started, this method will block until the looper has been initialized.
     * @return The looper.
     */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
 
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
           //如果執行緒存活而且looper為空,就等待讓出鎖,直到looper創建
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
}

參考檔案

文章寫完了,發現別人有更好的,想了想還是把自己寫的發出去吧,,,
Android訊息機制1-Handler(Java層)
小題大做 | 記憶體泄漏簡單問,你能答對嗎
又又又又攢了一個月的Android面試題

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

標籤:其他

上一篇:ZYNQ7000 (九) 撰寫LINUX下C程式的步驟在線運行并最終固化到RAMDISK里

下一篇:仿QQ6.0主頁面側滑效果(第二種實作方法)

標籤雲
其他(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)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more