Android應(yīng)用界面開發(fā)——Handler(實(shí)現(xiàn)倒計(jì)時(shí))

Android的消息傳遞機(jī)制是另一種形式的“事件處理”毛好,這種機(jī)制主要是為了解決Android應(yīng)用的多線程問題——Android平臺(tái)只允許UI線程修改Activity里的UI組件表窘,這會(huì)導(dǎo)致新啟動(dòng)的線程無法動(dòng)態(tài)改變界面組件的屬性值匣屡。但在實(shí)際Android應(yīng)用開發(fā)中,需要讓新啟動(dòng)的線程周期性的改變界面組件的屬性值女气,這就需要借助于Handler的消息機(jī)制來實(shí)現(xiàn)了杏慰。

多線程與異步


在學(xué)習(xí)Handler之前,有必要了解一下多線程與異步炼鞠。

當(dāng)一個(gè)程序第一次啟動(dòng)時(shí)缘滥,Android會(huì)同時(shí)啟動(dòng)一條主線程(Main Thread),主線程主要負(fù)責(zé)處理與UI相關(guān)的事件谒主,所以朝扼,主線程通常又被叫做UI線程。

當(dāng)在主線程中進(jìn)行耗時(shí)操作時(shí)(例如請求網(wǎng)絡(luò)資源)霎肯,主線程可能被卡死擎颖,這就需要?jiǎng)?chuàng)建一個(gè)新的線程來完成耗時(shí)操作,該操作完成后再通知主線程(Handler可以完成線程與線程之間的通信工作)观游,這幾個(gè)線程同時(shí)工作搂捧,就是多線程,而這種處理方式就是異步懂缕。

什么是Handler允跑?


一個(gè)Handler允許發(fā)送、處理消息和與線程消息隊(duì)列相關(guān)的可執(zhí)行對象搪柑。

Handler類的主要作用:

  • 在新啟動(dòng)的線程中發(fā)送消息聋丝。
  • 在主線程中獲取、處理消息工碾。

Handler類包含如下方法用于發(fā)送潮针、處理消息:

  • void handleMessage(Message msg):處理消息的方法。該方法通常用于被重寫倚喂。
  • final boolean hasMessages(int what):檢查消息隊(duì)列中是否包含what屬性為指定值的消息。
  • final boolean hasMessages(int what, Object object):檢查消息隊(duì)列中是否包含what屬性為指定值且object屬性為指定對象的消息瓣戚。
  • 多個(gè)重載的Message obtainMessage():獲取消息端圈。
  • sendEmptyMessage(int what):發(fā)送空消息。
  • final boolean sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒后發(fā)送空消息子库。
  • final boolean sendMessage(Message msg):立即發(fā)送消息舱权。
  • final boolean sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒后發(fā)送消息。

Message仑嗅、Handler宴倍、MessageQueue张症、Looper工作原理


  • Message:Handler接收和處理的消息對象。
    • 2個(gè)整型數(shù)值:輕量級(jí)存儲(chǔ)int類型的數(shù)據(jù)鸵贬。
    • 1個(gè)Object:任意對象俗他。
    • replyTo:線程通信時(shí)使用。
    • what:用戶自定義的消息碼阔逼,讓接收者識(shí)別消息兆衅。
  • MessageQueue:Message的隊(duì)列。
    • 采用先進(jìn)先出的方式管理Message嗜浮。
    • 每一個(gè)線程最多可以擁有一個(gè)羡亩。
  • Looper:消息泵,是MessageQueue的管理者危融,會(huì)不斷從MessageQueue中取出消息畏铆,并將消息分給對應(yīng)的Handler處理。
    • 每個(gè)線程只有一個(gè)Looper吉殃。
    • Looper.prepare():為當(dāng)前線程創(chuàng)建Looper對象辞居。
    • Looper.myLooper():可以獲得當(dāng)前線程的Looper對象。
  • Handler:能把消息發(fā)送給MessageQueue寨腔,并負(fù)責(zé)處理Looper分給它的消息速侈。

異步消息機(jī)制處理流程圖如下:


異步消息處理的整個(gè)流程如上圖所示,首先需要在主線程當(dāng)中創(chuàng)建一個(gè)Handler對象迫卢,并重寫handleMessage()方法倚搬。然后當(dāng)子線程中需要進(jìn)行UI操作時(shí),就創(chuàng)建一個(gè)Message對象乾蛤,并通過Handler將這條消息發(fā)送出去每界。之后這條消息會(huì)被添加到MessageQueue的隊(duì)列中等待被處理,而Looper則會(huì)一直嘗試從MessageQueue中取出待處理消息家卖,最后分發(fā)回Handler的handleMessage()方法中眨层。由于Handler是在主線程中創(chuàng)建的,所以此時(shí)handleMessage()方法中的代碼也會(huì)在主線程中運(yùn)行上荡,于是在這里就可以安心地進(jìn)行UI操作了趴樱。

實(shí)現(xiàn)倒計(jì)時(shí)Demo


效果如下:

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.trampcr.countdowndemo.MainActivity">

    <TextView
        android:id="@+id/count_number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text=""
        android:textColor="#000000"
        android:textSize="50sp" />

    <Button
        android:id="@+id/start"
        android:layout_width="150dp"
        android:layout_height="60dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="30dp"
        android:text="開始倒計(jì)時(shí)"
        android:textColor="#000000"
        android:textSize="18sp" />
</RelativeLayout>

包含一個(gè)文本用于顯示倒計(jì)時(shí)數(shù)字,一個(gè)按鈕用于開啟倒計(jì)時(shí)酪捡。

MainActivity.java
public class MainActivity extends AppCompatActivity {

    private TextView mCountNumber;
    private Button mStart;

    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 88888:
                    int value = (int) msg.obj;
                    mCountNumber.setText(String.valueOf(value / 1000));
                    msg = Message.obtain();//重新獲取消息
                    msg.arg1 = 0;
                    msg.arg2 = 1;
                    msg.what = 88888;
                    msg.obj = value - 1000;
                    if (value > 0){
                        sendMessageDelayed(msg, 1000);
                    }
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mCountNumber = (TextView) findViewById(R.id.count_number);
        mStart = (Button) findViewById(R.id.start);

        mStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = handler.obtainMessage();
                message.arg1 = 0;
                message.arg2 = 1;
                message.what = 88888;
                message.obj = 10000;
                handler.sendMessageDelayed(message, 1000);
            }
        });
    }
}

首先創(chuàng)建一個(gè)Handler對象叁征,并實(shí)現(xiàn)handleMessage方法,用于接收消息逛薇。

接下來在點(diǎn)擊事件中創(chuàng)建Message對象捺疼,不建議使用new Message(),而應(yīng)該用handler.obtainMessage()來創(chuàng)建Message永罚,然后使用handler.sendMessageDealyed延遲發(fā)送消息啤呼,發(fā)出的消息在上面創(chuàng)建好的handler中可以接收到卧秘,然后處理消息。

以上代碼就實(shí)現(xiàn)了倒計(jì)時(shí)效果官扣,但是我們看到handler這部分代碼的背景色為黃色翅敌,究其原因是handler是一個(gè)內(nèi)部類,可能產(chǎn)生內(nèi)存泄漏醇锚。

解決方法:使用外部類哼御。

代碼如下:

public class MainActivity extends AppCompatActivity {

    private TextView mCountNumber;
    private Button mStart;
    private CountDownHandler mCountDownHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mCountNumber = (TextView) findViewById(R.id.count_number);
        mStart = (Button) findViewById(R.id.start);
        mCountDownHandler = new CountDownHandler(this);

        mStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = mCountDownHandler.obtainMessage();
                message.arg1 = 0;
                message.arg2 = 1;
                message.what = 88888;
                message.obj = 10000;
                mCountDownHandler.sendMessageDelayed(message, 1000);
            }
        });
    }

    public TextView getmCountNumber() {
        return mCountNumber;
    }

    public static class CountDownHandler extends Handler {

        public final WeakReference<MainActivity> mainActivityWeakReference;

        public CountDownHandler(MainActivity activity) {
            mainActivityWeakReference = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity mainActivity = mainActivityWeakReference.get();
            switch (msg.what) {
                case 88888:
                    int value = (int) msg.obj;
                    mainActivity.getmCountNumber().setText(String.valueOf(value / 1000));
                    msg = Message.obtain();//重新獲取消息
                    msg.arg1 = 0;
                    msg.arg2 = 1;
                    msg.what = 88888;
                    msg.obj = value - 1000;
                    if (value > 0) {
                        sendMessageDelayed(msg, 1000);
                    }
                    break;
            }
        }
    }
}

定義一個(gè)外部類繼承Handler,并實(shí)現(xiàn)TextView的getter方法焊唬,最后運(yùn)行效果和原來一樣恋昼,但是該方法可避免內(nèi)存泄漏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赶促,一起剝皮案震驚了整個(gè)濱河市液肌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸥滨,老刑警劉巖嗦哆,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異婿滓,居然都是意外死亡老速,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門凸主,熙熙樓的掌柜王于貴愁眉苦臉地迎上來橘券,“玉大人,你說我怎么就攤上這事卿吐∨越ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵嗡官,是天一觀的道長箭窜。 經(jīng)常有香客問我,道長衍腥,這世上最難降的妖魔是什么磺樱? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮婆咸,結(jié)果婚禮上竹捉,老公的妹妹穿的比我還像新娘。我一直安慰自己擅耽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布物遇。 她就那樣靜靜地躺著乖仇,像睡著了一般憾儒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乃沙,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天起趾,我揣著相機(jī)與錄音,去河邊找鬼警儒。 笑死训裆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜀铲。 我是一名探鬼主播边琉,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼记劝!你這毒婦竟也來了变姨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對情侶失蹤厌丑,失蹤者是張志新(化名)和其女友劉穎定欧,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怒竿,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡砍鸠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耕驰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爷辱。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖耍属,靈堂內(nèi)的尸體忽然破棺而出托嚣,到底是詐尸還是另有隱情,我是刑警寧澤厚骗,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布示启,位于F島的核電站,受9級(jí)特大地震影響领舰,放射性物質(zhì)發(fā)生泄漏夫嗓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一冲秽、第九天 我趴在偏房一處隱蔽的房頂上張望舍咖。 院中可真熱鬧,春花似錦锉桑、人聲如沸排霉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攻柠。三九已至球订,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瑰钮,已是汗流浹背冒滩。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浪谴,地道東北人开睡。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像苟耻,于是被迫代替她去往敵國和親篇恒。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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