Android訊息機制Handler解析(源碼+Demo)

來源:互聯網
上載者:User

Android訊息機制Handler解析(源碼+Demo)

Handler是開發人員在面試過程中最常見的問題之一了,這篇文章將較為全面地對Handler進行解讀,包括源碼層,以及使用方法。

如果看完文章有疑問,歡迎在評論中一起探討

基本內容包括:

看完文章之後,可以用這個圖片進行複習。

 一、什麼是HandlerHandler是Android提供用來更新UI的一套機制,也是一套訊息處理機制,可以用它來發送訊息,也可以用它來接收訊息。 二、為什麼使用HandlerAndroid在設計之時,就封裝了一套訊息的建立、傳遞、處理機制,若不遵循這樣的處理機制,就沒辦法更新UI資訊,並且會拋出異常 三、Handler用法1、postdelayed()延時發送執行子線程 文字輪詢Demo(實現每隔一秒鐘更新一次Textview的功能)
public class MainActivity extends AppCompatActivity {    private TextView mTextView;    private Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);        }    };    private String[] str = new String[]{"傲慢","偏見","殭屍"};    private int index = 0;    MyRunnable myRunnable = new MyRunnable();    private class MyRunnable implements Runnable{        @Override        public void run() {            index = index % 3;            mTextView.setText(str[index]);            index ++;            mHandler.postDelayed(myRunnable,1000);        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.tv);        mHandler.postDelayed(myRunnable,1000);    }}

2、sendMessage()回調handleMessage()傳遞訊息Demo:在子線程中得到資訊,發送至主線程,更新textview的內容

 

public class MainActivity extends AppCompatActivity {    private TextView mTextView;    Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            mTextView.setText(msg.obj+""+ "arg1="+msg.arg1 + " arg2="+msg.arg2);            super.handleMessage(msg);        }    };    private  class Person{        String name;        int age;        @Override        public String toString() {            return "name="+name+" age="+age;        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.tv);        new Thread(){            @Override            public void run() {                Message msg = new Message();                msg.arg1 = 1;                msg.arg2 = 2;                Person person = new Person();                person.name = "pig";                person.age = 10;                msg.obj = person;                mHandler.sendMessage(msg);            }        }.start();    }}


3、sendToTarget()傳遞訊息與第二種用法原理一致
public class MainActivity extends AppCompatActivity {    private TextView mTextView;    Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            mTextView.setText(msg.obj+""+ "arg1="+msg.arg1 + " arg2="+msg.arg2);            super.handleMessage(msg);        }    };    private  class Person{        String name;        int age;        @Override        public String toString() {            return "name="+name+" age="+age;        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.tv);        new Thread(){            @Override            public void run() {                Message msg = mHandler.obtainMessage();//同樣可以擷取Message對象                msg.arg1 = 1;                msg.arg2 = 2;                Person person = new Person();                person.name = "pig";                person.age = 10;                msg.obj = person;                msg.sendToTarget();            }        }.start();    }}

4、使用CallBack截獲Handler的訊息
public class MainActivity extends AppCompatActivity {    private TextView mTextView;    Handler mHandler = new Handler(new Handler.Callback() {        //傳入CallBack對象,對於重載的傳回值為bollean的handleMessage()        //傳回值為false,將先執行這個方法,再執行傳回值為void的handleMessage()方法        //傳回值為true,只執行這個方法        @Override        public boolean handleMessage(Message msg) {            Toast.makeText(MainActivity.this, "截獲訊息", Toast.LENGTH_SHORT).show();            return false;        }    }){        public void handleMessage(Message msg) {            Toast.makeText(MainActivity.this, "發出訊息", Toast.LENGTH_SHORT).show();        }    };    private  class Person{        String name;        int age;        @Override        public String toString() {            return "name="+name+" age="+age;        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.tv);        Button btn = (Button) findViewById(R.id.btn);        btn.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                mHandler.sendEmptyMessage(0);            }        });    }}

四、為什麼在Android中智能通過Handler機制在主線程中更新UI? 最根本的是解決多線程並發問題。假如在同一個Activity中,有多個線程同時更新UI,且沒有加鎖,那會導致什麼問題呢?UI更新混亂。假如加鎖呢?會導致效能下降。使用Handler機制,我們不用去考慮多線程的問題,所有更新UI的操作,都是在 主線程訊息佇列中輪詢去處理的。 五、Handler機制的原理 1、Handler封裝了訊息的發送(主要包括訊息發送給誰)Looper(1)內部包含一個訊息佇列,即MessageQueue,所有Handler發送的訊息都會進入這個隊列(2)Looper.loop方法,是一個死迴圈,不斷從MessageQueue取出訊息,如有訊息就處理,沒有就阻塞 2、MessageQueue,一個訊息佇列,可以添加訊息,處理訊息 3、Handler內部會跟Looper進行關聯,也就是說,在Handler內部可以找到Looper,找到了Looper也就找到了MessageQueue,在Handler中發送訊息,其實就是向Message發送訊息, 總結:Handler負責發送訊息,Looper負責接收訊息,並把訊息回傳給Handler自己,而MessageQueue是一個儲存訊息的容器。  源碼層:Android的應用程式是通過ActivityThread進行建立,在ActivityThread預設建立一個Main線程,一個Looper,所有更新UI的線程都是通過Main線程進行建立的。查看Looper.loop的原始碼,發現確實是一個死迴圈
public static void loop() {    final Looper me = myLooper();    if (me == null) {        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");    }    final MessageQueue queue = me.mQueue;    // Make sure the identity of this thread is that of the local process,    // and keep track of what that identity token actually is.    Binder.clearCallingIdentity();    final long ident = Binder.clearCallingIdentity();    for (;;) {        Message msg = queue.next(); // might block        if (msg == null) {            // No message indicates that the message queue is quitting.            return;        }        // This must be in a local variable, in case a UI event sets the logger        Printer logging = me.mLogging;        if (logging != null) {            logging.println(">>>>> Dispatching to " + msg.target + " " +                    msg.callback + ": " + msg.what);        }        msg.target.dispatchMessage(msg);        if (logging != null) {            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);        }        // Make sure that during the course of dispatching the        // identity of the thread wasn't corrupted.        final long newIdent = Binder.clearCallingIdentity();        if (ident != newIdent) {            Log.wtf(TAG, "Thread identity changed from 0x"                    + Long.toHexString(ident) + " to 0x"                    + Long.toHexString(newIdent) + " while dispatching to "                    + msg.target.getClass().getName() + " "                    + msg.callback + " what=" + msg.what);        }        msg.recycleUnchecked();    }}

發現是通過msg.target.dispatchMessage()方法來處理訊息,查看其源碼
public void dispatchMessage(Message msg) {    if (msg.callback != null) {        handleCallback(msg);    } else {        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}
從源碼看出,當有CallBack的時候,會截獲訊息,沒有的話會回調handleMessage()來處理訊息 而對於SendMessage()系列的方法,這裡不再做過多解析,但從其源碼可以看出,確實是最終把訊息傳入了訊息佇列中。 六、建立與線程相關的Handler  在子線程中建立Handler,需要通過Looper.prepare()擷取Looper,且調用Looper.loop()方法對訊息佇列中的Message進行輪詢

 

 

public class MainActivity extends AppCompatActivity {    private TextView mTextView;    public Handler mHandler = new Handler(){//主線程中的Handler        @Override        public void handleMessage(Message msg) {            Log.d("CurrentThread",Thread.currentThread()+"");//列印Thread 的ID        }    };    class MyThread extends Thread{        private Handler handler;//子線程中的Handler        @Override        public void run() {            Looper.prepare();//擷取Looper            handler = new Handler(){                @Override                public void handleMessage(Message msg) {                    Log.d("CurrentThread",Thread.currentThread()+"");                }            };            Looper.loop();//輪詢訊息佇列        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        MyThread thread= new MyThread();        thread.start();        try {            thread.sleep(500);        } catch (InterruptedException e) {            e.printStackTrace();        }        thread.handler.sendEmptyMessage(1);        mHandler.sendEmptyMessage(1);    }}
輸出的結果03-31 20:56:06.498 1804-1816/? D/CurrentThread: Thread[Thread-113,5,main]03-31 20:56:06.578 1804-1804/com.lian.handlerdemo D/CurrentThread: Thread[main,5,main] 七、HandlerThread HandlerThread本質是一個Thread,區別在於他在run()之後建立了一個含有訊息佇列的Looper,這樣我們在子線程中建立Handler時候只需指定使用HandlerThread中的Looper,不用再調用Looper.prepare(),looper.loop()等,簡化了操作。Android系統提供的Handler使用的Looper預設綁定了UI線程的訊息佇列,所以我們在Handler中不能進行耗時操作,而對於非UI線程,若想使用訊息機制,HandlerThread內部的Looper是最合適的,他不會阻塞UI線程。
public class MainActivity extends AppCompatActivity {    private TextView mTextView;    public HandlerThread mHandlerThread;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mHandlerThread = new HandlerThread("handler thread");        mHandlerThread.start();        Handler handler = new Handler(mHandlerThread.getLooper()){//通過getLooper()擷取Looper            @Override            public void handleMessage(Message msg) {                Log.d("current thread","" + Thread.currentThread());            }        };        handler.sendEmptyMessage(1);    }}

結果:03-31 21:36:42.770 7225-7237/? D/currentthread: Thread[handler thread,5,main]八、主線程與子線程資訊互動 主線程中的Handler與子線程中的Handler互相發送訊息,只要調用對方的sendMessage()就可以了

 

public class MainActivity extends AppCompatActivity {    public Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            Log.d("current thread", "" + Thread.currentThread());            Message message = new Message();            message.what = 1;            handler.sendMessageDelayed(message,1000);//向子線程的Handler發送訊息        }    };    public HandlerThread mHandlerThread;    public Handler handler;    private Button btn1,btn2;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn1 = (Button) findViewById(R.id.btn);        btn2 = (Button) findViewById(R.id.btn2);        mHandlerThread = new HandlerThread("handler thread");//指定HandlerThread的名字        mHandlerThread.start();        handler = new Handler(mHandlerThread.getLooper()){//通過getLooper()擷取Looper            @Override            public void handleMessage(Message msg) {                Log.d("current thread", "" + Thread.currentThread());                Message message = new Message();                mHandler.sendMessageDelayed(message,1000);//向主線程中的Handler發送訊息            }        };        btn1.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                handler.sendEmptyMessage(1);//開始發送訊息            }        });        btn2.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                handler.removeMessages(1);//停止發送訊息            }        });    }}
結果:03-31 22:21:11.422 16748-16760/com.lian.handlerdemo D/currentthread: Thread[handler thread,5,main]03-31 22:21:12.422 16748-16748/com.lian.handlerdemo D/currentthread: Thread[main,5,main]03-31 22:21:13.422 16748-16760/com.lian.handlerdemo D/currentthread: Thread[handler thread,5,main]03-31 22:21:14.422 16748-16748/com.lian.handlerdemo D/currentthread: Thread[main,5,main]03-31 22:21:15.426 16748-16760/com.lian.handlerdemo D/currentthread: Thread[handler thread,5,main]03-31 22:21:16.426 16748-16748/com.lian.handlerdemo D/currentthread: Thread[main,5,main]03-31 22:21:20.414 16748-16760/com.lian.handlerdemo D/currentthread: Thread[handler thread,5,main]03-31 22:21:21.414 16748-16748/com.lian.handlerdemo D/currentthread: Thread[main,5,main]03-31 22:21:22.414 16748-16760/com.lian.handlerdemo D/currentthread: Thread[handler thread,5,main]03-31 22:21:23.418 16748-16748/com.lian.handlerdemo D/currentthread: Thread[main,5,main]

 

九、四種更新UI的方法 1、Handler.post(); 2、Handler.sendMessage();第一二種方法事實上沒有本質的區別,都是通過發送訊息,在UI線程中更新UI,前面已經做過示範,不再贅述 3、runOnUIThread()使用方法:
public class MainActivity extends AppCompatActivity {    TextView mTextView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.tv);        new Thread(){            @Override            public void run() {                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                runOnUiThread(new Runnable() {                    @Override                    public void run() {                        mTextView.setText("更新UI");                    }                });            }        }.start();    }}

我們查看runOnUIThread()的原始碼
public final void runOnUiThread(Runnable action) {    if (Thread.currentThread() != mUiThread) {        mHandler.post(action);    } else {        action.run();    }}
可以發現,其本質上仍然是通過Handler.post()方法再UI線程中更新UI  4、View.post()使用方法
public class MainActivity extends AppCompatActivity {    TextView mTextView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.tv);        new Thread(){            @Override            public void run() {                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                mTextView.post(new Runnable() {                    @Override                    public void run() {                        mTextView.setText("更新UI");                    }                });            }        }.start();    }}

查看其源碼,一樣是採用Handler.post()方法更新UI
public boolean post(Runnable action) {    final AttachInfo attachInfo = mAttachInfo;    if (attachInfo != null) {        return attachInfo.mHandler.post(action);    }    // Assume that post will succeed later    ViewRootImpl.getRunQueue().post(action);    return true;}
十、在非UI線程中更新UI的方法 先看一個Demo
public class MainActivity extends AppCompatActivity {    TextView mTextView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.tv);        new Thread(){            @Override            public void run() {                mTextView.setText("更新UI了");            }        }.start();    }}
結果:


 

驚訝地發現,成功更新了UI,並沒有拋出異常 然而當我們先讓線程休眠2s,再更新
public class MainActivity extends AppCompatActivity {    TextView mTextView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTextView = (TextView) findViewById(R.id.tv);        new Thread(){            @Override            public void run() {                try {                    Thread.sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                mTextView.setText("更新UI了");            }        }.start();    }}
更新失敗,拋出異常

這是什麼原因呢?在Activity中有一個ViewRootImpl類,這個類沒有執行個體化的時候,系統不會檢測當前線程是否UI線程,而這個類的執行個體化是在Activity的onResume()中實現,所以,當我們沒有讓子線程休眠時,直接更新UI,系統還來不及檢測當前線程是否UI線程,於是我們成功更新了UI,而休眠二秒中後,ViewRootImpl已經執行個體化,此時更新UI就會拋出異常。當然,在實際開發中,這意義不大,我們還是要在UI線程中更新UI。 十一、常見的兩個問題 使用Handler常遇到的兩個異常:1、非UI線程更新UI也就是我們上面遇到的問題拋出這個異常:android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 2、子線程中建立Handler缺少Looper拋出這個異常:java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 查看源碼
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;}

發現,沒有Looper,就會拋出這個運行時異常。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.