天天看點

安卓學習筆記之Handler

UI線程

當系統啟動的時候,就會建立一個主線程(Main Thread),然後這個主線程向UI元件分發事件,主線程和UI的元件進行互動,故稱UI線程。

線程安全

Android的UI線程是不安全的。引用一下,百度百科的解釋

線程安全就是多線程通路時,采用了加鎖機制,當一個線程通路該類的某個資料時,進行保護,其他線程不能進行通路直到該線程讀取完,其他線程才可使用。不會出現資料不一緻或者資料污染。 線程不安全就是不提供資料通路保護,有可能出現多個線程先後更改資料造成所得到的資料是髒資料

from 百度百科

既然這樣,Google給我們提供了更新ui界面的Handler類。

Handler

一個處理異步消息的類,建立一個子線程發送消息至主線程,在主線程更新相應的UI界面。比如在子線程進行長時間的網絡操作,然後更新UI界面上的TextView。

使用的方法有兩種,

1.post(runnable)&2.sendMessage(message)

先示範一下,所謂不能直接在UI線程更新TextView的操作。在XML布局檔案裡建立一個TextView。

package com.example.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.widget.Button;
import android.widget.TextView;

/**
 * Created by Does on 2017/7/20.
 */

public class HandlerExample extends Activity {

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

        tv_content= (TextView) findViewById(R.id.tv_content);
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep();
                    tv_content.setText("更新UI");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}
           

寫好之後,運作之後,

安卓學習筆記之Handler

Exception:隻有建立了視圖層次結構的原始線程才能觸及它的視圖

為了解決這種問題,我們使用Handler。

post(runnable)?

這裡模拟一個簡單的下載下傳功能。XML檔案裡面添加兩個元件,一個Button和一個Textview

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView tv_content;
    private Button btn_onclick;
    private Handler handler=new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_content= (TextView) findViewById(R.id.tv_content);
        btn_onclick= (Button) findViewById(R.id.btn_onclick);
        btn_onclick.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        DownLoadThread downLoadThread=new DownLoadThread();
        downLoadThread.start();
    }
    class DownLoadThread extends  Thread{
            @Override
            public void run() {
                try {
                    System.out.println("檔案正在下載下傳...");
                    Thread.sleep();
                    System.out.println("檔案下載下傳成功");
                    Runnable runnable=new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("目前線程的id是:  "+Thread.currentThread().getId());
                            MainActivity.this.tv_content.setText("textview已經改變");
                        }
                    };
                    handler.post(runnable);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


    }
}
           

運作效果如下,點選Button之前

安卓學習筆記之Handler

點選Button之後

安卓學習筆記之Handler

sendMessage(message)

public class HandlerExample extends Activity {

    private TextView tv_content;
    private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case :
                    tv_content.setText("UI變化了");
                    break;
                default:
                    break;
            }
        }
    };
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv_content= (TextView) findViewById(R.id.tv_content);
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep();
                    Message message=new Message();
                    message.what=;
                    handler.sendMessage(message);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}
           

效果和上面的是一樣的。

這個明顯和上一個有些不同,多了幾樣的東西。Message(),sendMessage(),handleMessage()

首先要子線程中,建立一個Handler的一個對象handler,然後執行handler.sendMessage(message),

handler攜帶message的一個對象,message.what=1,給它一個辨別,随便取。在handleMessage()方法裡,就會接受到傳過去的值,進而在handleMessage裡進行UI的更新操作。

Handler運作機制

接下來我們說說内部機制的運作。主要有用到Looper,Handler,MessageQueu(消息隊列).從源碼入手,

當我們手機一啟動的時候,系統預設主線程會先調用prepareMainLooper()方法。先執行prepare(false)

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            ...
            sMainLooper = myLooper();
        }
    }
           

接着建立一個Looper對象,将ThreadLocal設定為線程安全的對象。

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
           

接着sThreadLocal向Looper類裡傳過去quitAllowed,并在Looper()構造器裡建立了一個MessageQueue的對象mQueue。

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mRun = true;
        mThread = Thread.currentThread();
    }
           

最後,再調用prepareMainLooper()中的myLooper(),取出線程安全的Looper對象。

public static Looper myLooper() {
        return sThreadLocal.get();
    }
           

這是系統幫我們做的事情!!

接着我們需要做一些事情更新UI的操作,我們在MainActivity方法中建立了一個Handler對象,

public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
        ...

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
           

handler取出了系統為我們建立的Looper對象,并取出系統建立的mQueue對象。此時我們建立的handler就持有了系統剛建立的Looper對象和MessageQueue對象。

**

發送消息

**

當我們發送消息的時候,sendMessage

sendMessage(Message msg)

    sendMessageDelayed(Message msg, long delayMillis)

    public final boolean sendMessageAtFrontOfQueue(Message msg) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, );
    }

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
           

首先是取出Looper對象中MessageQueue在enqueueMessage()方法中,handler攜帶我們要發送的Message,然後放入消息隊列MessageQueue中

boolean enqueueMessage(Message msg, long when) {
        ...

        boolean needWake;
        synchronized (this) {
            ...

            msg.when = when;
            Message p = mMessages;
            if (p == null || when ==  || when < p.when) {

                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {

                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }
        }
        ...
        return true;
    }
           

循環取出

接着Looper調用loop()方法

public static void loop() {
        final Looper me = myLooper();
        ...

        final MessageQueue queue = me.mQueue;

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); 
            ...
            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;

            msg.target.dispatchMessage(msg);
            ...
            final long newIdent = Binder.clearCallingIdentity();
            ...

            msg.recycle();
        }
    }
           

myLooper()取出目前Looper對象

me.mQueue拿到目前的MessageQueue對象

queue.next();取出下一個消息

如果消息存在 則調用消息的msg.target.dispatchMessage(msg);

處理消息

這就是我們重寫的方法,

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }