Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android消息機制Handler解析(源碼+Demo)

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

編輯:關於Android編程

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

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

基本內容包括:

\

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

  一、什麼是Handler Handler是Android提供用來更新UI的一套機制,也是一套消息處理機制,可以用它來發送消息,也可以用它來接收消息。   二、為什麼使用Handler Android在設計之時,就封裝了一套消息的創建、傳遞、處理機制,若不遵循這樣的處理機制,就沒辦法更新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,就會拋出這個運行時異常。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved