【Android】Handler和线程通信

原创
2012/11/15 14:40
阅读数 1.5K

Handler定义

Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. 

Handler提供了一种线程间通讯的机制,子线程可以用Handler向目标线程的消息队列发送Message或者Runnable。

Handler的使用情景:子线程希望在另外一条线程中完成一些处理,于是可以在目标线程中实现一个Handler,子线程通过这个handler发送Message通知它执行相应的操作。

 

使用Handler

Scheduling messages is accomplished with the post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long) method

Handler提供了几个方法用于发送消息:sendMessage—— 发送Message对象,post——发送Runnable(其实最终Runnbale也会被作为message的callback封装到Message中)。Message会在handleMessage中被处理,而Runnable会被直接运行它的run()。

 

一个典型的例子:通过子线程修改应用的UI

 子线程直接修改应用UI错误示范:

public class MainActivity extends Activity implements OnClickListener{
   TextView tvTest;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvTest = (TextView) findViewById(R.id.tv_test);
        tvTest.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        new WorkerThread().start();
    }

    class WorkerThread extends Thread{
        @Override
        public void run() {
           tvTest.setText("from thread:" + getId()); //错误的做法,线程中直接更新textview
        }
    }
}

WorkerThread试图要更改TextView的内容时,程序就会抛出异常:

11-15 03:09:21.881: E/AndroidRuntime(1661): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
11-15 03:09:21.881: E/AndroidRuntime(1661):  at android.view.ViewRoot.checkThread(ViewRoot.java:2932)
11-15 03:09:21.881: E/AndroidRuntime(1661):  at android.view.ViewRoot.requestLayout(ViewRoot.java:629)
11-15 03:09:21.881: E/AndroidRuntime(1661):  at android.view.View.requestLayout(View.java:8267)
11-15 03:09:21.881: E/AndroidRuntime(1661):  at android.view.View.requestLayout(View.java:8267)

异常原因是主线程non trhead-safeUI的更新必须在主线程实现,而且android设置了检查机制来阻止我们从别的线程更新UI,如LOG中的checkThread方法。

 

解决办法:子线程通过主线程的Handler发送更新请求

public class MainActivity extends Activity implements OnClickListener{

	TextView tvTest;
	Handler mHandler;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvTest = (TextView) findViewById(R.id.tv_test);
        tvTest.setOnClickListener(this);
        mHandler = new Handler(){  //在主线程创建Handler实例
        	
    		@Override
    		public void handleMessage(Message msg) { //消息的处理
    			tvTest.setText("update view");
    		}
        };
        
    }  class WorkerThread extends Thread{

	@Override
	public void run() {
	    mHandler.sendEmptyMessage(0); //向handler发送一个消息
	}
    }

	@Override
	public void onClick(View v) {
	    new WorkerThread().start(); //开启线程
	}
}

 

 

 线程的消息传递是如何工作的?

上面的例子因为handler是在主线程中,主线程被创建的时候会默默地绑定一个looper,因此我们不需要去操作Looper,但是如果handler在子线程中被创建,那么我们就需要手动让Looper启动起来。

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare(); // 线程绑定looper,如果当前线程没有looper,这里会自动new一个

          mHandler = new Handler() {
              public void handleMessage(Message msg) { //实现handleMessage,写入自己的处理
                  // process incoming messages here
              }
          };

          Looper.loop(); //让Looper循环获取Message
      }
  }

 

总结一下操作的步骤:

1.在线程中绑定一个Looper:Looper.prepare();

2.创建一个class继承Handler并实现handleMessage方法;

3.启动Looper,开始从MessageQueue循环获取并分发接收到的Message:Looper.loop()。




 Handler 的作用只有2个:向消息队列发送消息 和 处理分发到的Message,实现线程的通信还涉及几个关键的类: Looper, Message 和 MessageQueue

 

Message

就是消息的封装类,它有几个public字段用来标记和存放数据: what(int,用于识别,相当于id), arg1, arg2(2个用来存放数据的int) 和 obj(存放object),当然还可以用setData()存放一个bundle(开销会比较大)。

还有2个内部成员可以了解一下:

callback—— 存放Runnable

target—— 目标线程的Handler

 

MessageQueue

存放Message的队列。

 

Looper

MessageQueue和Handler之间的齿轮,负责获取Message并分发给Handler。

 

重点突出一下Looper,消息在队列和handler之间的传递就是looper 的loop()方法实现的,这是一个死循环,不停地查询MessageQueue获取仍未处理的Message,通知handler的dispatchMessage(),附上源码:

public static void loop() {
        Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this   thread.");
        }
        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();
        
        while (true) {
            Message msg = queue.next(); // might block
            if (msg != null) {
                if (msg.target == null) {
                    // No target is a magic identifier for the quit message.
                    return;
                }

                long wallStart = 0;
                long threadStart = 0;

                // 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);
                    wallStart = SystemClock.currentTimeMicro();
                    threadStart = SystemClock.currentThreadTimeMicro();
                }

                msg.target.dispatchMessage(msg);

                if (logging != null) {
                    long wallTime = SystemClock.currentTimeMicro() - wallStart;
                    long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;

                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                    if (logging instanceof Profiler) {
                        ((Profiler) logging).profile(msg, wallStart, wallTime,
                                threadStart, threadTime);
                    }
                }

                // 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.recycle();
            }
        }
    }



第一篇帖子。。 逻辑还很混乱。。希望大神们给点建议~

展开阅读全文
加载中
点击加入讨论🔥(1) 发布并加入讨论🔥
打赏
1 评论
5 收藏
0
分享
返回顶部
顶部