Handler、Looper尊浪、messagequeue源碼分析及使用(1)

Handler匣屡、Looper、messagequeue源碼分析及使用(1)
Handler拇涤、Looper捣作、messagequeue源碼分析及使用(2)

一、什么是handler


handler通過發(fā)送和處理Message/Runnable對象來關聯(lián)相應線程的MessageQueue鹅士。

  • 可以讓對應的Message/Runnable在未來的某個時間點進行相應處理券躁。
  • 讓自己想要處理的耗時操作放在子線程,讓更新ui的操作放在主線程。

簡單直白的來說也拜,因為android中不能在子線程中更新ui以舒,同時主線程又不能做耗時操作(會堵塞ui),而handler可以很方便的幫助我們在主線程和子線程中切換慢哈,達到在主線程中更新ui蔓钟,在子線程中做耗時操作,handler在這里充當?shù)木褪且粋€消息的發(fā)送者和處理者的身份岸军。

二奋刽、handler的使用


1、如何創(chuàng)建主線程的handler艰赞,實現(xiàn)在主線程更新UI佣谐,在子線程做耗時操作?
代碼 2-1
public class UiHandlerActivcity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ui_handler_activcity);
        mUiTextView = findViewById(R.id.ui_textview);
        startThread();
    }

    private static final int MSG_WHAT = 0x100;
    private final String TAG = this.getClass().getSimpleName();
    private TextView mUiTextView;
    private Thread mThread = null;
    private Handler mUiHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "UI線程 ID=" + Looper.getMainLooper().getThread().getId());
            Log.d(TAG, "Handler's handleMessage() 運行的線程 ID=" + Thread.currentThread().getId());
            if (msg.what == MSG_WHAT) {
                mUiTextView.setText(String.valueOf(msg.arg1));
            }
        }
    };
    private void startThread() {
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "Thread's run() 運行的線程 ID=" + Thread.currentThread().getId());
                try { // 模擬耗時操作
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message message = Message.obtain();
                message.what = MSG_WHAT;
                message.arg1 = 500;
                mUiHandler.sendMessage(message);
            }
        });
        mThread.start();
    }
}

打印結(jié)果:

02-02 12:27:59.384 25774-25791/com.ktln.must D/UiHandlerActivcity: Thread's run() 運行的線程 ID=2232
02-02 12:28:04.384 25774-25774/com.ktln.must D/UiHandlerActivcity: UI線程 ID=1
02-02 12:28:04.384 25774-25774/com.ktln.must D/UiHandlerActivcity: Handler's handleMessage() 運行的線程 ID=1

從運行結(jié)果來看方妖,mUiHandler.handleMessage()的運行線程和UI線程是同一個狭魂,而mThread.run()則運行在非主線程中。

2党觅、如何創(chuàng)建非UI線程的handler雌澄,實現(xiàn)UI線程發(fā)送消息通知非UI線程做耗時操作?
代碼 2-2
public class NoUiHandlerActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_no_ui_handler);
        startThread();
    }

    private static final String TAG = NoUiHandlerActivity.class.getSimpleName();
    private void startThread() {
        MyThread myThread = new MyThread();
        myThread.start();
        myThread.getHandler().post(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "UI線程 ID=" + Looper.getMainLooper().getThread().getId());
                Log.d(TAG, "Runnable's run() 運行的線程 ID=" + Thread.currentThread().getId());
            }
        });
    }
    private static class MyThread extends Thread {
        private Handler mHandler;
        public MyThread() { super(); }
        @Override
        public void run() {
            Looper.prepare();
            synchronized (this) {
                mHandler = new Handler(); // 或者 mHandler = new Handler(Looper.myLooper());
                notifyAll();
            }
            Looper.loop();
        }
        public Handler getHandler() {
            synchronized (this) {
                // 因為異步執(zhí)行杯瞻,在其他線程調(diào)用時候handler可能還沒有創(chuàng)建好镐牺,這是使用會報空指針錯誤,
                // 所以通過代碼塊加鎖魁莉,wait和notifyAll睬涧,來解決這一問題
                while (mHandler == null) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mHandler;
        }
    }
}

打印結(jié)果:

02-02 14:11:21.054 31781-31804/com.ktln.must D/NoUiHandlerActivity: UI線程 ID=1
02-02 14:11:21.054 31781-31804/com.ktln.must D/NoUiHandlerActivity: Runnable's run() 運行的線程 ID=2320

從運行結(jié)果來看,UI線程的ID=1旗唁,而Runnable.run()的運行線程ID=2320畦浓,所以可以斷定這個handler是運行在非UI線程中的。

三检疫、創(chuàng)建handler的重要步驟

  • Looper.prepare();讶请,給目標線程創(chuàng)建一個Looper對象,如果目標線程已經(jīng)存在Looper對象屎媳,則不需要再創(chuàng)建夺溢;
  • Looper.loop();,讓目標線程的Looper對象烛谊,循環(huán)讀取消息隊列MessageQueue风响;
  • new Handler(Looper.myLooper());,使用目標Looper對象創(chuàng)建Handler對象晒来,再使用這個handler對象發(fā)送消息(Message/Runnable)钞诡;

你可能會問上面代碼2-1中沒有構(gòu)造Looper啊,為什么也能正常執(zhí)行?
通過Android應用程序的啟動荧降,我們知道android應用程序的入口接箫,即ActivityThread的main方法,來看下源碼

    public static void main(String[] args) {
        ..........
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        ...........
        Looper.loop();
    }
  • 其中調(diào)用了Looper.prepareMainLooper();創(chuàng)建Looper朵诫,Looper.loop();開啟輪詢辛友,由此可見所有的Handler創(chuàng)建都是一個流程;
  • 并且通過ActivityThread入口的main()能夠看出剪返,只要Looper.loop();停止輪詢废累,我們的android應用程序也就退出了,即Looper.quit();就會結(jié)束應用脱盲,退出程序邑滨。

四、使用handler中的內(nèi)存泄漏問題

對handler熟悉的同學一定會發(fā)現(xiàn)钱反,我上面寫的測試代碼都存在一個問題掖看,那就是有可能發(fā)生內(nèi)存泄漏!C娓纭哎壳!
1、為什么會發(fā)生內(nèi)存泄漏那尚卫?
我們拿代碼2-1來說归榕,private Handler mUiHandler = new Handler() ;由于mUiHandler不是一個靜態(tài)內(nèi)部類,所以mUiHandler隱匿的持有UiHandlerActivcity的引用吱涉,當UiHandlerActivcity釋放時刹泄,mUiHandler如果內(nèi)部正在做一些耗時操作,這時會導致UiHandlerActivcity無法及時回收邑飒,而導致UiHandlerActivcity停留在堆內(nèi)存中循签,繼而導致內(nèi)存泄漏级乐。

2疙咸、怎么解決內(nèi)存泄漏問題?
其實百度一下风科,你就會發(fā)現(xiàn)很多相關的文章撒轮,我這里總結(jié)下。

  • 聲明一個靜態(tài)內(nèi)部類的handler對象
  • ActivityonDestroy()方法中調(diào)用Handler.removeCallbacksAndMessages(null);
  • 如果你在靜態(tài)內(nèi)類的handler中使用外部類贼穆,需要使用弱引用题山,即WeakReference<>

3、修改代碼2-1故痊,防止內(nèi)存泄漏

public class UiHandlerActivcity extends AppCompatActivity {
    private static final int MSG_WHAT = 0x100;
    private static final String TAG = UiHandlerActivcity.class.getSimpleName();
    private TextView mUiTextView;
    private Thread mThread = null;
    private static Handler mUiHandler = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ui_handler_activcity);
        mUiTextView = findViewById(R.id.ui_textview);
        mUiHandler = new UiHandler(this);
        startThread();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mUiHandler != null)
            mUiHandler.removeCallbacksAndMessages(null);
    }

    private void startThread() {
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "Thread's run() 運行的線程 ID=" + Thread.currentThread().getId());
                try {
                    // 模擬耗時操作
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (mUiHandler != null) {
                    Message message = Message.obtain();
                    message.what = MSG_WHAT;
                    message.arg1 = 500;
                    mUiHandler.sendMessage(message);
                    mUiHandler.removeCallbacksAndMessages(null);
                }
            }
        });
        mThread.start();
    }

    private static final class UiHandler extends Handler {
        private WeakReference<UiHandlerActivcity> reference = null;
        public UiHandler(UiHandlerActivcity activcity) {
            super();
            this.reference = new WeakReference<>(activcity);
        }
        @Override
        public void handleMessage(Message msg) {
            if (reference == null || reference.get() == null) { return; }
            UiHandlerActivcity activcity = reference.get();
            Log.d(TAG, "UI線程 ID=" + Looper.getMainLooper().getThread().getId());
            Log.d(TAG, "Handler's handleMessage() 運行的線程 ID=" + Thread.currentThread().getId());
            if (msg.what == MSG_WHAT) {
                activcity.mUiTextView.setText(String.valueOf(msg.arg1));
                activcity.startActivity(new Intent(activcity, NoUiHandlerActivity.class));
            }
        }
    }

}


Handler顶瞳、Looper、messagequeue源碼分析及使用(1)
Handler、Looper慨菱、messagequeue源碼分析及使用(2)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末焰络,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子符喝,更是在濱河造成了極大的恐慌闪彼,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件协饲,死亡現(xiàn)場離奇詭異畏腕,居然都是意外死亡,警方通過查閱死者的電腦和手機茉稠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門描馅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人而线,你說我怎么就攤上這事流昏。” “怎么了吞获?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵况凉,是天一觀的道長。 經(jīng)常有香客問我各拷,道長刁绒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任烤黍,我火速辦了婚禮知市,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘速蕊。我一直安慰自己嫂丙,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布规哲。 她就那樣靜靜地躺著跟啤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪唉锌。 梳的紋絲不亂的頭發(fā)上隅肥,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音袄简,去河邊找鬼腥放。 笑死,一個胖子當著我的面吹牛绿语,可吹牛的內(nèi)容都是我干的秃症。 我是一名探鬼主播候址,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼种柑!你這毒婦竟也來了宗雇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤莹规,失蹤者是張志新(化名)和其女友劉穎赔蒲,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體良漱,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡舞虱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了母市。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矾兜。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖患久,靈堂內(nèi)的尸體忽然破棺而出椅寺,到底是詐尸還是另有隱情,我是刑警寧澤蒋失,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布返帕,位于F島的核電站,受9級特大地震影響篙挽,放射性物質(zhì)發(fā)生泄漏荆萤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一铣卡、第九天 我趴在偏房一處隱蔽的房頂上張望链韭。 院中可真熱鬧,春花似錦煮落、人聲如沸敞峭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旋讹。三九已至,卻和暖如春量淌,著一層夾襖步出監(jiān)牢的瞬間骗村,已是汗流浹背嫌褪。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工呀枢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人笼痛。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓裙秋,卻偏偏與公主長得像琅拌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子摘刑,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359