Handler的removeCallbacksAndMessages怎么不好使了呢?

前言

此篇文章記錄日常遇到的一個小坑:Handler的removeCallbacksAndMessages沒生效。

正文

需求:

需求:有1-5個超時任務(wù),如果某個任務(wù)在規(guī)定時間內(nèi)完成,需要取消對應(yīng)的超時任務(wù);

這個需求并不復(fù)雜,如果是比較簡單的延時任務(wù),可以使用Handler.postDelayed添加延時任務(wù),如果任務(wù)在預(yù)期內(nèi)完成,可以通過Handler.removeCallbacksAndMessages刪除掉對應(yīng)的任務(wù):

Handler handler = new Handler();
// 添加5個超時任務(wù),每一個任務(wù)的Tag通過generateToken()生成
for (int i = 0; i < 5; i++) {
    Log.e("lzp", "start post delay task " + i);
    int finalI = i;
    handler.postDelayed(() -> Log.e("lzp", "timeout " + finalI), generateToken(i), 3000);
}

// 1s后取消所有的延時任務(wù)
handler.postDelayed(() -> {
    for (int i = 0; i < 5; i++) {
        Log.e("lzp", "removeCallbacksAndMessages " + i);
        handler.removeCallbacksAndMessages(generateToken(i));
    }
}, 1000);

// 生成任務(wù)的Token
private String generateToken(int i) {
    return “Task” + i;  
}

如果上面的代碼正常運(yùn)行,按照我的期望這5個超時任務(wù)應(yīng)該會被取消掉,但是運(yùn)行的結(jié)果卻是:


image.png

分析

看來Handler.removeCallbacksAndMessages并沒有成功的取消對應(yīng)的任務(wù),只能去看源碼了:

public final void removeCallbacksAndMessages(Object token) {
    mQueue.removeCallbacksAndMessages(this, token);
}

// MessageQueue.java
void removeCallbacksAndMessages(Handler h, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        // 請注意這里使用的是 ==,而不是equals
        while (p != null && p.target == h
                && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                // 請注意這里使用的是 ==,而不是equals
                if (n.target == h && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

源碼也非常的簡單,很快就發(fā)現(xiàn)了問題可能發(fā)生的地方,這里判斷Token使用的“==”而不是equals,我使用的是字符串作為Token,確實(shí)可能這里判斷是不相等的,之后通過斷點(diǎn)確認(rèn)此處判斷的確是false。那么為什么我的字符串內(nèi)存地址不同呢,字符串常量池沒有復(fù)用嗎?為了解決這個疑問,只能去查看編譯后的字節(jié)碼,看看generateTag到底是如何工作的。

首先找到生成的apk文件,直接雙擊打開,此時可能看到多個dex文件,所以要找到類具體被打包到了那個dex文件中,然后找到對應(yīng)的方法右鍵點(diǎn)擊Show ByteCode:


image.png
.method private generateTag(I)Ljava/lang/String;
    .registers 4
    .param p1, "i"    # I

    .line 56
    // 創(chuàng)建StringBuilder
    new-instance v0, Ljava/lang/StringBuilder;
    // 執(zhí)行StringBuilder構(gòu)造方法
    invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
    // 創(chuàng)建常量Task
    const-string v1, "Task"
    // StringBuilder拼接Task字符串
    invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    
    move-result-object v0
    // 拼接參數(shù)I
    invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

    move-result-object v0
    // 調(diào)用StringBuilder的toString方法
    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
    // 把toString的結(jié)果賦值給v0
    move-result-object v0
    // 返回v0
    return-object v0
.end method

字節(jié)碼的語法跟Java差不多,通過分析字節(jié)碼發(fā)現(xiàn),JVM會把我們的字符串的“+”在編譯的時候替換為StringBuilder的拼接操作,所以再去查看一下StringBuilder的toString方法:

@Override
public String toString() {
    if (count == 0) {
        return "";
    }
    return StringFactory.newStringFromChars(0, count, value);
}
    
@FastNative
static native String newStringFromChars(int offset, int charCount, char[] data);

StringBuilder的toString調(diào)用native方法newStringFromChars,這個newStringFromChars方法實(shí)際上就是String內(nèi)部用來創(chuàng)建字符串的類,例如:

public static String valueOf(char c) {
    // Android-changed: Replace constructor call with call to new StringFactory class.
    // char data[] = {c};
    // return new String(data, true);
    return StringFactory.newStringFromChars(0, 1, new char[] { c });
}

結(jié)論

所以通過以上的分析,得出結(jié)論:字符串使用“+”或StringBuilder進(jìn)行拼接,返回的是相同內(nèi)容的不同對象,而Handler.removeCallbacksAndMessages使用“==”判斷Token的內(nèi)存地址是否相等,所以導(dǎo)致移除任務(wù)失敗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 所謂,君子性非異也,善假于物也!~ 那么,本文意在給大家提供快速、全面、高效的面試解決方案; 為大家節(jié)約尋找面試、...
    騎小豬看流星閱讀 4,201評論 13 100
  • 一 布局優(yōu)化 核心思想:減少布局文件層級布局層級減少 -> 繪制工作量減少 ->性能提高 刪除布局中無用控件和層級...
    _Sisyphus閱讀 373評論 0 0
  • 一. 布局優(yōu)化 核心思想:減少布局文件層級布局層級減少 -> 繪制工作量減少 ->性能提高 刪除布局中無用控件和層...
    _Sisyphus閱讀 335評論 0 0
  • 預(yù)防OOM的幾點(diǎn)建議 Android開發(fā)過程中,在 Activity的生命周期里協(xié)調(diào)耗時任務(wù)可能會很困難,你一不小...
    MrWang915閱讀 401評論 0 0
  • final關(guān)鍵字 可以用來修飾類、方法和變量(包括成員變量和局部變量) 1.修飾類 當(dāng)用final修飾一個類時,表...
    快感的感知閱讀 1,437評論 0 2

友情鏈接更多精彩內(nèi)容