Android應用界面開發——Handler(實現倒計時)

Android應用界面開發——Handler(實現倒計時)

Android的消息傳遞機制是另一種形式的“事件處理”,這種機制主要是爲了解決Android應用的多線程問題——Android平臺只允許UI線程修改Activity裏的UI組件,這會導致新啓動的線程無法動態改變界面組件的屬性值。但在實際Android應用開發中,需要讓新啓動的線程週期性的改變界面組件的屬性值,這就需要藉助於Handler的消息機制來實現了。

多線程與異步

在學習Handler之前,有必要了解一下多線程與異步。

當一個程序第一次啓動時,Android會同時啓動一條主線程(Main Thread),主線程主要負責處理與UI相關的事件,所以,主線程通常又被叫做UI線程。

當在主線程中進行耗時操作時(例如請求網絡資源),主線程可能被卡死,這就需要創建一個新的線程來完成耗時操作,該操作完成後再通知主線程(Handler可以完成線程與線程之間的通信工作),這幾個線程同時工作,就是多線程,而這種處理方式就是異步。

什麼是Handler?

一個Handler允許發送、處理消息和與線程消息隊列相關的可執行對象。

Handler類的主要作用:

  1. 在新啓動的線程中發送消息。
  2. 在主線程中獲取、處理消息。
    Handler類包含如下方法用於發送、處理消息:

    • void handleMessage(Message msg):處理消息的方法。該方法通常用於被重寫。
    • final boolean hasMessages(int what):檢查消息隊列中是否包含what屬性爲指定值的消息。
    • final boolean hasMessages(int what, Object object):檢查消息隊列中是否包含what屬性爲指定值且object屬性爲指定對象的消息。
    • 多個重載的Message obtainMessage():獲取消息。
    • sendEmptyMessage(int what):發送空消息。
    • final boolean sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒後發送空消息。
    • final boolean sendMessage(Message msg):立即發送消息。
    • final boolean sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒後發送消息。

Message、Handler、MessageQueue、Looper工作原理

- Message:Handler接收和處理的消息對象。


  1. 2個整型數值:輕量級存儲int類型的數據。
    1個Object:任意對象。
    replyTo:線程通信時使用。
    what:用戶自定義的消息碼,讓接收者識別消息。
    MessageQueue:Message的隊列。
    採用先進先出的方式管理Message。
    每一個線程最多可以擁有一個。
    Looper:消息泵,是MessageQueue的管理者,會不斷從MessageQueue中取出消息,並將消息分給對應的Handler處理。
    每個線程只有一個Looper。
    Looper.prepare():爲當前線程創建Looper對象。
    Looper.myLooper():可以獲得當前線程的Looper對象。
    Handler:能把消息發送給MessageQueue,並負責處理Looper分給它的消息。

異步消息機制處理流程圖如下:

異步消息處理的整個流程如上圖所示,首先需要在主線程當中創建一個Handler對象,並重寫handleMessage()方法。然後當子線程中需要進行UI操作時,就創建一個Message對象,並通過Handler將這條消息發送出去。之後這條消息會被添加到MessageQueue的隊列中等待被處理,而Looper則會一直嘗試從MessageQueue中取出待處理消息,最後分發回Handler的handleMessage()方法中。由於Handler是在主線程中創建的,所以此時handleMessage()方法中的代碼也會在主線程中運行,於是在這裏就可以安心地進行UI操作了。

實現倒計時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="開始倒計時"
    android:textColor="#000000"
    android:textSize="18sp" />
</RelativeLayout>

包含一個文本用於顯示倒計時數字,一個按鈕用於開啓倒計時。

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);
         }
      });
    }
}

首先創建一個Handler對象,並實現handleMessage方法,用於接收消息。

接下來在點擊事件中創建Message對象,不建議使用new Message(),而應該用handler.obtainMessage()來創建Message,然後使用handler.sendMessageDealyed延遲發送消息,發出的消息在上面創建好的handler中可以接收到,然後處理消息。

以上代碼就實現了倒計時效果,但是我們看到handler這部分代碼的背景色爲黃色,究其原因是handler是一個內部類,可能產生內存泄漏。

解決方法:使用外部類。

代碼如下:

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;
            }
         }
    }
}

定義一個外部類繼承Handler,並實現TextView的getter方法,最後運行效果和原來一樣,但是該方法可避免內存泄漏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章