記一次Handler的優(yōu)化

為什么要使用Handler


Handler是Android提供的一套消息機制搓劫。由于Android的開發(fā)規(guī)范限制(UI控件非線程安全),更新UI的操作必須在主線程完成琢感。所以大部分人對Handler的理解主要是用來更新UI的碗暗,包括我一直以來也都是這樣理解滴。但這僅僅是Handler的一個特殊使用場景昌阿。
和Handler一起為大家所知的還有它的兩兄弟LooperMessageQueue饥脑,這三駕馬車一起構成了Android的消息機制,但是本文只討論Handler懦冰。

Handler是如何實現消息機制的


Handler重載了很多的構造方法灶轰,但是內部原理都一樣。在創(chuàng)建時會使用當前線程的Looper來構建內部的消息循環(huán)系統(tǒng)刷钢。

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();// 引用當前線程的Looper
        if (mLooper == null) {// mLooper什么時候會為空笋颤?只有當在子線程中創(chuàng)建Handler,又未提前調用Looper.prepare()
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;// 引用Looper中的消息隊列
        mCallback = callback;// 這個是Handler.Callback對象内地,等會我們會詳細講解
        mAsynchronous = async;
    }

Handler創(chuàng)建完畢后伴澄,就可以通過這個handler對象發(fā)送一個消息赋除,這個消息會進入消息隊列,因為Looper的消息隊列一直在循環(huán)秉版,一旦消息到來贤重,就會通過Handler的dispatchMessage()方法來進行分發(fā)處理茬祷。因為Looper是運行在創(chuàng)建Handler所在的線程清焕,為什么這么說,看如下源碼:

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {// prepare在一個線程中只能調用一次
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));// 實例化的Looper被放入線程變量祭犯,接下來我們看看Looper的構造器做了什么工作
    }

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);// 初始化了消息隊列
        mThread = Thread.currentThread();// 關聯(lián)了當前的線程
    }

所以這樣一來Handler中的業(yè)務邏輯就被切換到創(chuàng)建Handler所在的線程中去執(zhí)行了秸妥。
依據上述,糾正很多初學者的一點疑慮:Handler也可以在子線程中創(chuàng)建沃粗,只要姿勢正確粥惧,先調用一下Looper.prepare()就可以。這樣創(chuàng)建的子線程也能擁有消息機制最盅,但這個Handler是不能做UI更新的突雪。

Handler使用過程中問題


通常大家看到很多Handler實例化的過程是這樣的:

Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            // 根據消息類別做處理
        }
};

這時候會看到編輯器友好善意的Warning: * This Handler class should be static or leaks might occur (anonymous android.os.Handler)*
這個問題是說Handler應該被聲明稱靜態(tài)內部類,否則就可能會導致內存泄露涡贱。what the fu*k r u saying? 因為java中匿名類默認持有外部類對象的引用咏删,不然你也不可能直接在內部類里面直接使用外部類的屬性。如果外部類正欲銷毀问词,而在handleMessage里面恰好有新的消息到達需要處理督函,匿名類持有外部類對象就不會被釋放。另外激挪,注意非靜態(tài)內部類也默認持有外部類對象的引用辰狡。

解決方案


方案一:

只要將匿名類修改成靜態(tài)的內部類,并將外部類改為弱引用垄分,例如:

private static final class MyHandler extends Handler{
        private WeakReference<? extends Activity> mReference;
        public MyHandler(Activity activity){
            mReference = new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            // 消息處理
            Activity activity = mReference.get();
            switch(msg.what){
                case XX:
                    if(activity != null){
                        // 做你的UI更新去吧
                    }
                    break;
            }
        }
    }

并在外部類銷毀的時候調用Handler的removeCallbacksAndMessages(null)去釋放當前handler發(fā)送到消息隊列的消息宛篇。咦,這里為什么還有CallBack呢薄湿?因為handler還可以post一個Runnable對象些己,而這個對象會被包裝成Message對象,這個是在消息分發(fā)的時候優(yōu)先執(zhí)行的嘿般。不信段标,你看

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {// post出去的Runnable
            handleCallback(msg);
        } else {
            if (mCallback != null) {// 這里我們可以實現Handler.Callback接口來處理消息
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);// 通常重新的handleMessage消息處理方法
        }
    }

方案二:

如果認真看以上Handler的消息分發(fā)dispatchMessage()的執(zhí)行流程,不難發(fā)現炉奴,我們還可以這樣安全的使用Handler逼庞。

private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // 根據消息類別做處理
            return false;
        }
    });

以上觀點純屬個人見解,若有出入瞻赶,歡迎各位書友一起探討赛糟。

上一篇:一招搞定Android Activity的管理

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末鲤看,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子笼平,更是在濱河造成了極大的恐慌立镶,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件司倚,死亡現場離奇詭異豆混,居然都是意外死亡,警方通過查閱死者的電腦和手機动知,發(fā)現死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門皿伺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盒粮,你說我怎么就攤上這事鸵鸥。” “怎么了丹皱?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵妒穴,是天一觀的道長。 經常有香客問我摊崭,道長讼油,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任爽室,我火速辦了婚禮汁讼,結果婚禮上,老公的妹妹穿的比我還像新娘阔墩。我一直安慰自己嘿架,他們只是感情好,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布啸箫。 她就那樣靜靜地躺著耸彪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忘苛。 梳的紋絲不亂的頭發(fā)上蝉娜,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機與錄音扎唾,去河邊找鬼召川。 笑死,一個胖子當著我的面吹牛胸遇,可吹牛的內容都是我干的荧呐。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼倍阐!你這毒婦竟也來了概疆?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤峰搪,失蹤者是張志新(化名)和其女友劉穎岔冀,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體概耻,經...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡使套,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了咐蚯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片童漩。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡弄贿,死狀恐怖春锋,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情差凹,我是刑警寧澤期奔,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站危尿,受9級特大地震影響呐萌,放射性物質發(fā)生泄漏。R本人自食惡果不足惜谊娇,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一肺孤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧济欢,春花似錦赠堵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至半等,卻和暖如春揍愁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杀饵。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工莽囤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人切距。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓朽缎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子饵沧,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內容