Android Handler、Looper消息傳遞機(jī)制(1)

1.為什么要了解學(xué)習(xí)Handler?

1.為了以后面試做準(zhǔn)備:國(guó)內(nèi)一些大廠或者專業(yè)的互聯(lián)網(wǎng)技術(shù)型公司,在面試的時(shí)候都會(huì)或多或少提到Android的消息傳遞機(jī)制,如果你能深入了解并掌握這一方面的知識(shí)洞辣,會(huì)讓面試官對(duì)你的印象提升數(shù)倍。
2.為了完成從初級(jí)開(kāi)發(fā)到中高級(jí)開(kāi)發(fā)的轉(zhuǎn)變:初級(jí)開(kāi)發(fā)就是會(huì)簡(jiǎn)單調(diào)用代碼昙衅,想更進(jìn)一步扬霜,你就要理解你調(diào)用的代碼底層是怎么實(shí)現(xiàn)的,現(xiàn)在越來(lái)越多的框架極大的方便了我們的編程而涉,但是我們不能僅僅知其然著瓶,更要知其所以然。很多優(yōu)秀的框架都少不了對(duì)Handler的調(diào)用啼县,這時(shí)你如果掌握了handler的原理材原,會(huì)極大的幫助你理解那些復(fù)雜難懂的框架余蟹。
3.為了更好的調(diào)試程序:我們看日志的時(shí)候經(jīng)常會(huì)出現(xiàn)很多關(guān)于Handler這個(gè)類的各種調(diào)用威酒,如果你理解了這個(gè)機(jī)制挺峡,你會(huì)個(gè)更好的找到錯(cuò)誤點(diǎn)并結(jié)局它們沙郭。

2.為什么要引入Handler消息傳遞機(jī)制病线?

在Android開(kāi)發(fā)中鲤嫡,子線程沒(méi)有辦法對(duì)UI界面上的內(nèi)容進(jìn)行操作暖眼,如果操作诫肠,將拋出異常:CalledFromWrongThreadException
那么我們?yōu)榱藢?shí)現(xiàn)子線程中操作UI界面栋豫,Goodle在Android引入了Handler消息傳遞機(jī)制丧鸯,目的是打破對(duì)主線程操作UI的依賴性丛肢。

3.什么是Handler?

handler通俗一點(diǎn)講就是用來(lái)在各個(gè)線程之間發(fā)送數(shù)據(jù)的處理對(duì)象蜂怎。在任何線程中杠步,只要獲得了另一個(gè)線程的handler篮愉,則可以通過(guò) handler.sendMessage(message)方法向那個(gè)線程發(fā)送數(shù)據(jù)差导∩韬郑基于這個(gè)機(jī)制助析,我們?cè)谔幚矶嗑€程的時(shí)候可以新建一個(gè)thread外冀,這個(gè)thread擁有UI線程中的一個(gè)handler雪隧。當(dāng)thread處理完一些耗時(shí)的操作后通過(guò)傳遞過(guò)來(lái)的handler向UI線程發(fā)送數(shù)據(jù)脑沿,由UI線程去更新界面。

4.常用類

4.1簡(jiǎn)單介紹:
  1. Message:消息注服,被傳遞和處理的數(shù)據(jù)溶弟。其中包含了消息ID辜御,消息處理對(duì)象以及處理的數(shù)據(jù)等我抠,由MessageQueue統(tǒng)一列隊(duì)袜茧,終由Handler處理
  2. Handler:處理者纳鼎,負(fù)責(zé)Message的發(fā)送及處理贱鄙。使用Handler時(shí)逗宁,需要實(shí)現(xiàn)handleMessage(Message msg)方法來(lái)對(duì)特定的Message進(jìn)行處理瞎颗,例如更新UI等哼拔。Handler類的主要作用:(有兩個(gè)主要作用)1)倦逐、在工作線程中發(fā)送消息檬姥;2)穿铆、在主線程中獲取荞雏、并處理消息凤优。
  3. MessageQueue:消息隊(duì)列筑辨,本質(zhì)是一個(gè)數(shù)據(jù)結(jié)構(gòu)暮现,用來(lái)存放Handler發(fā)送過(guò)來(lái)的消息栖袋,并按照FIFO(First Input First Outputl,先入先出隊(duì)列)規(guī)則執(zhí)行塘幅。當(dāng)然电媳,存放Message并非實(shí)際意義的保存匾乓,而是將Message串聯(lián)起來(lái)钝尸,等待Looper的抽取。
  4. Looper:消息泵或循環(huán)器剩愧,不斷從MessageQueue中抽取Message仁卷。因此锦积,一個(gè)MessageQueue需要一個(gè)Looper丰介。
  5. Thread:線程带膀,負(fù)責(zé)調(diào)度整個(gè)消息循環(huán)垛叨,即消息循環(huán)的執(zhí)行場(chǎng)所柜某。
4.2Handler剂癌、Looper珍手、Message辞做、MessageQueue之間的關(guān)系:
image.png

1.** Looper和MessageQueue一一對(duì)應(yīng)琳要,創(chuàng)建一個(gè)Looper的同時(shí),會(huì)創(chuàng)建一個(gè)MessageQueue**秤茅;

  1. 而Handler與它們的關(guān)系稚补,只是簡(jiǎn)單的聚集關(guān)系,即Handler里會(huì)引用當(dāng)前線程里的特定Looper和MessageQueue框喳;
  2. 在一個(gè)線程中课幕,只能有一個(gè)Looper和MessageQueue,但是可以有多個(gè)Handler五垮,而且這些Handler可以共享一個(gè)Looper和MessageQueue乍惊;
  3. Message被存放在 MessageQueue中润绎,一個(gè) MessageQueue中可以包含多個(gè)Message對(duì)象。
    【注意:】
    Looper對(duì)象用來(lái)為一個(gè)線程開(kāi)啟一個(gè)消息循環(huán)涂佃,從而操作MessageQueue;
    默認(rèn)情況下,Android創(chuàng)建的線程沒(méi)有開(kāi)啟消息循環(huán)Looper,但是主線程例外。
    系統(tǒng)自動(dòng)為主線程創(chuàng)建Looper對(duì)象,開(kāi)啟消息循環(huán);
    所以主線程中使用new來(lái)創(chuàng)建Handler對(duì)象。而子線程中不能直接new來(lái)創(chuàng)建Handler對(duì)象就會(huì)異常。
    子線程中創(chuàng)建Handler對(duì)象,步驟如下:
Looper.prepare();
Handler handler = new Handler() {
    //handlemessage(){}
}
Looper.loop();
4.3Handler類中常用方法:
  1. handleMessage() 用在主線程中喳钟,構(gòu)造Handler對(duì)象時(shí)酬蹋,重寫(xiě)handleMessage()方法匕垫。該方法根據(jù)工作線程返回的消息標(biāo)識(shí),來(lái)分別執(zhí)行不同的操作春寿。
  2. sendEmptyMessage() 用在工作線程中,發(fā)送空消息。
  3. sendMessage() 用在工作線程中,立即發(fā)送消息析藕。
4.4Message消息類中常用屬性:
  1. arg1 用來(lái)存放整型數(shù)據(jù)
  2. arg2 用來(lái)存放整型數(shù)據(jù)
  3. obj 用來(lái)存放Object數(shù)據(jù)
  4. what 用于指定用戶自定義的消息代碼筹煮,這樣便于主線程接收后劫扒,根據(jù)消息代碼不同而執(zhí)行不同的相應(yīng)操作澎灸。
    示例代碼(1)簡(jiǎn)單的加載網(wǎng)絡(luò)圖片:
 private TextView text_main_info;
    private ProgressDialog pDialog;
    private ImageView image_main;
    private Handler handler = null;
    public static final String urlString = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1517382140&di=f11ad1bb20b04cfb4ebc96d337d0ff21&imgtype=jpg&er=1&src=http%3A%2F%2Fpic4.nipic.com%2F20091217%2F3885730_124701000519_2.jpg";

    @SuppressLint("HandlerLeak")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text_main_info = (TextView) findViewById(R.id.text_main_info);
        pDialog = new ProgressDialog(MainActivity.this);
        pDialog.setMessage("Loading...");
        image_main = (ImageView) findViewById(R.id.image_main);

        // 主線程中的handler對(duì)象會(huì)處理工作線程中發(fā)送的Message其兴。根據(jù)Message的不同編號(hào)進(jìn)行相應(yīng)的操作。
        handler = new Handler() {
            public void handleMessage(android.os.Message msg) {
                // 工作線程中要發(fā)送的信息全都被放到了Message對(duì)象中,也就是上面的參數(shù)msg中。要進(jìn)行操作就要先取出msg中傳遞的數(shù)據(jù)柒莉。
                switch (msg.what) {
                    case 0:
                        // 工作線程發(fā)送what為0的信息代表線程開(kāi)啟了。主線程中相應(yīng)的顯示一個(gè)進(jìn)度對(duì)話框
                        pDialog.show();
                        break;
                    case 1:
                        // 工作線程發(fā)送what為1的信息代表要線程已經(jīng)將需要的數(shù)據(jù)加載完畢搀擂。本案例中就需要將該數(shù)據(jù)獲取到哨颂,顯示到指定ImageView控件中即可箫措。
                        image_main.setImageBitmap((Bitmap) msg.obj);
                        break;
                    case 2:
                        // 工作線程發(fā)送what為2的信息代表工作線程結(jié)束弦牡。本案例中,主線程只需要將進(jìn)度對(duì)話框取消即可。
                        pDialog.dismiss();
                        break;
                }
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 當(dāng)工作線程剛開(kāi)始啟動(dòng)時(shí)餐塘,希望顯示進(jìn)度對(duì)話框妥衣,此時(shí)讓handler發(fā)送一個(gè)空信息即可。
                // 當(dāng)發(fā)送這個(gè)信息后戒傻,主線程會(huì)回調(diào)handler對(duì)象中的handleMessage()方法税手。handleMessage()方法中
                // 會(huì)根據(jù)message的what種類來(lái)執(zhí)行不同的操作。
                handler.sendEmptyMessage(0);

                // 工作線程執(zhí)行訪問(wèn)網(wǎng)絡(luò)需纳,加載網(wǎng)絡(luò)圖片的任務(wù)芦倒。
                Bitmap bitmap=HttpClientHelper.getImageBitmap(urlString);
                // 工作線程將要發(fā)送給主線程的信息都放到一個(gè)Message信息對(duì)象中。
                // 而Message對(duì)象的構(gòu)建建議使用obtain()方法生成不翩,而不建議用new來(lái)生成兵扬。
                Message msgMessage = Message.obtain();
                // 將需要傳遞到主線程的數(shù)據(jù)放到Message對(duì)象的obj屬性中,以便于傳遞到主線程口蝠。
                msgMessage.obj = bitmap;
                // Message對(duì)象的what屬性是為了區(qū)別信息種類器钟,而方便主線程中根據(jù)這些類別做相應(yīng)的操作。
                msgMessage.what = 1;
                // handler對(duì)象攜帶著Message中的數(shù)據(jù)返回到主線程
                handler.sendMessage(msgMessage);

                // handler再發(fā)出一個(gè)空信息妙蔗,目的是告訴主線程工作線程的任務(wù)執(zhí)行完畢傲霸。一般主線程會(huì)接收到這個(gè)消息后,
                // 將進(jìn)度對(duì)話框關(guān)閉
                handler.sendEmptyMessage(2);
            }
        }).start();
    }

在網(wǎng)速很快的情況下會(huì)看不到進(jìn)度條灭必,你們可以用打斷點(diǎn)的方式去查看代碼執(zhí)行順序狞谱。
(2)打地鼠小游戲:
新建一個(gè)頁(yè)面代碼如下:

 private Handler handler = null;
    private ImageView image_main_mouse;
    private int position;
    private boolean flag = false;
    private int[][] positionArr;
    private int countNum;
    private int beatNum;
    private TextView mTvStop;


    @SuppressLint("HandlerLeak")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_beat_mouse);

        findView();
        initData();

        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 0:
                        image_main_mouse.setVisibility(View.VISIBLE);
                        // 獲取0-8之間的隨機(jī)數(shù)[0,8),半閉合區(qū)間禁漓。目的是隨機(jī)獲取給定的8個(gè)坐標(biāo)位置跟衅。
                        // 獲取隨機(jī)數(shù)有兩種辦法:
                        // 方法一:
                        // Math.random()*positionArr.length,注意偽隨機(jī)數(shù)是個(gè)半閉合區(qū)間播歼。即隨機(jī)數(shù)不可能為positionArr.length
                        // 方法二:
                        // new Random().nextInt(positionArr.length);
                        position = (int) (Math.random() * positionArr.length);
                        image_main_mouse.setX(positionArr[position][0]);
                        image_main_mouse.setY(positionArr[position][1]);
                        break;
                    default:
                        break;
                }
            }
        };

        image_main_mouse = (ImageView) findViewById(R.id.image_main_mouse);


        image_main_mouse.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                image_main_mouse.setVisibility(View.GONE);
                beatNum++;
                Toast.makeText(BeatMouseActivity.this, "總出現(xiàn)次數(shù):" + countNum + ";擊中次數(shù):" + beatNum, Toast.LENGTH_SHORT).show();
                return false;
            }
        });

        mTvStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (flag) {
                    mTvStop.setText("開(kāi)始");
                } else {
                    mTvStop.setText("停止");
                }
                flag = !flag;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while (flag) {
                            try {
                                countNum++;
                                // 獲取0-500之間的隨機(jī)數(shù)伶跷,再加上500,目的是讓老鼠出現(xiàn)的間隙時(shí)間也隨機(jī)秘狞,最短出現(xiàn)間隙為500毫秒叭莫,最長(zhǎng)為999毫秒。
                                Thread.sleep(new Random().nextInt(500) + 500);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            handler.sendEmptyMessage(0);
                        }

                    }
                }).start();

            }
        });
    }

    private void initData() {
        positionArr = new int[8][2];
        for (int i = 0; i < positionArr.length; i++) {
            //設(shè)置X軸坐標(biāo)
            positionArr[i][0] = getWindowWidth(this) / positionArr.length * i;
            //設(shè)置Y軸坐標(biāo)
            positionArr[i][1] = getWindowHeight(this) / positionArr.length * i;

        }
    }

    private void findView() {
        image_main_mouse = (ImageView) findViewById(R.id.image_main_mouse);
        mTvStop = (TextView) findViewById(R.id.tv_stop);
    }

    /**
     * 獲取屏幕寬度
     */
    public static int getWindowWidth(Activity activity) {
        DisplayMetrics dm = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
        return dm.widthPixels;
    }

    /**
     * 獲取屏幕高度
     */
    public static int getWindowHeight(Activity activity) {
        DisplayMetrics dm = new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
        return dm.heightPixels;
    }

下面是示例圖:

device-2018-01-24-160745.png

我把以上兩個(gè)示例代碼合并到了一個(gè)項(xiàng)目里烁试,并上傳Github雇初,鏈接為:https://github.com/EspressoToast/HandlerDemo
這一講先告一段落。下一講我會(huì)為大家分析一下Handler源碼减响。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末靖诗,一起剝皮案震驚了整個(gè)濱河市郭怪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刊橘,老刑警劉巖鄙才,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異促绵,居然都是意外死亡攒庵,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)败晴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浓冒,“玉大人,你說(shuō)我怎么就攤上這事尖坤●烧簦” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵糖驴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我佛致,道長(zhǎng)贮缕,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任俺榆,我火速辦了婚禮感昼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罐脊。我一直安慰自己定嗓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布萍桌。 她就那樣靜靜地躺著宵溅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪上炎。 梳的紋絲不亂的頭發(fā)上恃逻,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音藕施,去河邊找鬼寇损。 笑死,一個(gè)胖子當(dāng)著我的面吹牛裳食,可吹牛的內(nèi)容都是我干的矛市。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼诲祸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼浊吏!你這毒婦竟也來(lái)了而昨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤卿捎,失蹤者是張志新(化名)和其女友劉穎配紫,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體午阵,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡躺孝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了底桂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片植袍。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖籽懦,靈堂內(nèi)的尸體忽然破棺而出于个,到底是詐尸還是另有隱情,我是刑警寧澤暮顺,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布厅篓,位于F島的核電站,受9級(jí)特大地震影響捶码,放射性物質(zhì)發(fā)生泄漏羽氮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一惫恼、第九天 我趴在偏房一處隱蔽的房頂上張望档押。 院中可真熱鬧,春花似錦祈纯、人聲如沸令宿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)粒没。三九已至,卻和暖如春油昂,著一層夾襖步出監(jiān)牢的瞬間革娄,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工冕碟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拦惋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓安寺,卻偏偏與公主長(zhǎng)得像厕妖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挑庶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容