前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >通信之线程间通信(上)-handler

通信之线程间通信(上)-handler

作者头像
fanfan
发布2022-05-07 14:08:18
4950
发布2022-05-07 14:08:18
举报
文章被收录于专栏:编程思想之路编程思想之路

本文主要有三大方面

一是handler,Looper,messagequeue之间概念

二是线程间通信时handler的使用,包括在主线程中创建handler和在子线程中创建handler

三是对于looper的源码的简单分析,帮助理解记忆

Chapter One,背景介绍

虽然Android是单线程模型(即只能在主线程更新UI),但是仅仅靠主线程是远远不够的,比如如果在主线程中进行请求网络的操作,那就会造成阻塞,用户用起来会很不舒服。所以,除了UI的更新外,一些耗时的操作可以通过开启其他线程来处理。主线程和子线程之间需要数据交换等通信,子线程和子线程之间同样也需要通信。

目前线程中的通信是借助handler实现的,但handler的作用不仅限于线程间通信,还有延时启动ruannable,还有一点需要说明:一个线程Thread对应一个looper,一个looper对应一个消息队列messagequeue,一个消息队列messagequeue中可以包含多条message消息,一个线程中可以有多个handler

所以也就是说对于一个线程,如果想要使用handler,必须存在一个looper,一个messagequeue。对于主线程而言,在创建主线程ActivityThread时会先将looper准备好,所以在主线程中可以直接使用handler。而如果是想要创建子线程自己的handler则必须自己手动对message loop进行prepare。

研究一段代码分三步:whw----------what,how,why,接下来就根据这个来学习下handler

转载请注明出处:

本文出自

海天之蓝  通信之线程间通信(上)-handler

Chapter Two , 概念型介绍

一:Handler:

handler可以用来发送和处理message和runnable对象(其实就是handler有两大作用,一是handler可以将message和runnable对象发送给消息队列入列,二是消息队列对消息的处理又是在handler中)每一个handler和一个单一线程以及一个该线程的消息队列对应。当创建一个handler时,会与创建他的线程和线程的消息队列绑定,从这时候开始,handler会向消息队列发message和runnables,并且在message和runnable从消息队列出来时message和runnable进行处理。

对于handler有两种用法:在未来的某一刻去执行一个message或者是runnable.或者是在另一个线程中执行一个action(即在该线程中使用另一个线程中的handler处理消息)

可以通过post,postDelay sendMessage ,sendMessageDelayed来安排一个消息进入消息队列。

  • post:针对runnable对象而言的,当接受到runnable对象时消息队列会调用enqueuemessage方法将消息入列
  • sendMessage:针对message而言的,会将一个带有bundle数据的msg入列,该msg会被handler处理,所实现的handler必须重写handlermessage方法来处理消息队列中的message。

当你的应用程序正在进行处理消息的操作时,应用程序主线程会专门用来运行一个消息队列来管理顶级的应用程序对象(activities,broadcastreceivers,等等)以及任何他们创建的窗口。你也可以创建自己的线程,可以通过handler来实现子线程和主线程的交互。和之前一样调用post和sendMessage方法,只不过是来自于子线程。handler发送过来的runnable和message会被安排在handler对应的消息队列中并且在出队列时对他们进行处理。

二:MessageQueue:

MessageQueue是个final类,持有从looper分发下来的message的list列表.Message不是直接被添 加到消息队列中去的,而是通过与looper相关的handler分发的。 你可以调用Looper.myQueue()方法获取到当前线程的消息队列messagequeue对象.

三:Looper:

Looper用来为一个线程循环消息message.线程默认是没有消息循环的;可以调用Looper.prepare()方法创建一个message loop,在loop停止之前可以用它来处理messages。大部分的message loop是通过handler类交互的

PS:不要问我为什么没有叙述message,毕竟他是客观被动的被放置在了消息队列中

Chapter Three,创建Handler进行进程间通信

第一种情况,使用主线程中的handler进行message的send和process

只有主线程才能去更新UI线程,如果程序中开启了子线程,而又想去更新UI,这时候就要借助Handler将消息传给主线程,进行刷新UI,简单的总结一下handler的用法

1,在activity中创建handler,更新UI的操作在此进行:

代码语言:javascript
复制
private Handler mHandler = new Handler(){
		public void handleMessage(android.os.Message msg) {
			
			switch (msg.what) {
			    case 0:
		         	mReceiveMsg.setText(format.format(System.currentTimeMillis())+":"
			    +"获取到来自子线程的消息为:"+msg.what);
				    break;
			    case 1:
			    case 2:
			    case 3:
			    case 4:
			    case 5:
			    case 6:
			    case 7:
			    case 8:
			    case 9:
			    	mReceiveMsg.setText(mReceiveMsg.getText().toString()+"\n"
			    +format.format(System.currentTimeMillis())+":"+"获取到来自子线程的消息为:"+msg.what);
				    break;
			    case FINISH_CHILD_THREAD:
			    	mReceiveMsg.setText(mReceiveMsg.getText().toString()+"\n"
			    +format.format(System.currentTimeMillis())+":"+"子线程已执行完毕");
			    	mStartThread.setEnabled(true); 
				    break;
			    case CHILD_THREAD_EXCEPTION:
			    	mReceiveMsg.setText(mReceiveMsg.getText().toString()+"\n"
						    +format.format(System.currentTimeMillis())+":"+"子线程出现异常");
						    	mStartThread.setEnabled(true); 

			default:
				break;
			}
		};
	};

2,在子线程中利用handler传递消息

代码语言:javascript
复制
class SubThread implements Runnable {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			for (int i = 0; i < 10; i++) {
				mHandler.obtainMessage(i).sendToTarget();
				try {
					//休眠
					TimeUnit.MILLISECONDS.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					mHandler.obtainMessage(CHILD_THREAD_EXCEPTION).sendToTarget();
				};
			}	
			//当线程结束时通知主线程
			mHandler.obtainMessage(FINISH_CHILD_THREAD).sendToTarget();
		}
}

在传递消息时还可以创建message对象用来传递非字符串类型数据

代码语言:javascript
复制
Message msg = new Message();
			msg.what = 1;
			mHandler.sendMessage(msg);

3,在主线程中开启子线程

代码语言:javascript
复制
ExecutorService exec = Executors.newCachedThreadPool();
			exec.execute(new SubThread());

在这里我使用到的是线程池,大家也可以直接创建thread对象,然后调用start方法,使用线程池,可以很好的去管理线程

就这几步,handler的用法就介绍完了,

那么handler的使用机制即内部原理到底是什么呢?这就涉及到了几个单词,handler,Message,MessageQueue,

looper

handler机制简单说就是以下几步:

1,子线程借助主线程的handler向主线程发送一条消息Message

2,主线程将接收到的Message保存到消息队列里MessageQueue

3,主线程里的looper发现消息队列里有消息时就会去调用handlerMessage方法,用来更新ui

接下来是handler用法实例展示:

第二种情况:使用子线程中的handler进行线程中的通信

在Looper.java类中对于在非主线程中使用handler有一个小的demo,按照示例,实现在子线程中使用handler进行线程间通信

如果所示,在主线程的edittext中写入要发送给子线程的数据,并在子线程中通过log打印出来,log打印结果如下

代码语言:javascript
复制
03-21 14:00:37.327: I/fang(17674): fcvht56
03-21 14:00:46.277: I/fang(17674): f66666676
03-21 14:01:19.557: I/fang(17674): f66666676jjjk
03-21 14:01:21.512: I/fang(17674): f66666676jjjk
03-21 14:03:31.487: I/fang(17674): gghiii
03-21 14:04:37.799: I/fang(17674): rrfgjht

demo代码如下:

代码语言:javascript
复制
public class SecondActivity extends Activity implements View.OnClickListener{

    MyThread mThread ;
    EditText mEt;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_second);
        TextView mTest = (TextView) findViewById(R.id.test);
        mEt = (EditText) findViewById(R.id.content);

        mTest.setOnClickListener(this);
        mThread = new MyThread();
        //开启线程
        mThread.start();

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.test:
                //创建message对象
                Message msg = new Message();
                //创建bundle对象,携带数据
                Bundle bundle = new Bundle();
                //获取到edittext中的内容并放入到bundle中
                bundle.putString("hh",mEt.getText().toString());
                msg.what = 1;
                msg.setData(bundle);
                //子线程的handler发送消息
                mThread.mHandler.sendMessage(msg);
                break;
            default:
                break;
        }
    }

    private class MyThread extends Thread{

        public Handler mHandler;
        @Override
        public void run() {
            super.run();
            //创建线程的looper和messagequeue
            Looper.prepare();
            mHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                        //子线程的handler进行处理消息
                       switch (msg.what){
                        case 1:
                            Log.i("fang",msg.getData().getString("hh"));
                            break;
                        default:
                            break;
                    }
                }
            };
            //开启消息队列中的消息循环
            Looper.loop();
        }
    }
}

看到这里你是否会有这个疑问: 既然发送message的是handler,处理message的也是handler,那为什么不直接让handler进行处理还非要绕那么大一圈儿呢?如果你有这个疑问,那说明你真的对handler的知识有了一定的掌握,并且对于消息队列和looper也有了一定的了解。 原因就是,当handler发送一个message时,此时线程并不想让handler立刻处理,或者说handler正在处理别的message,那此时该怎么办呢?我们只能去创建一个缓冲区,把handler发送过来的消息先暂时存放起来,然后根据优先级或者进来的顺序或者handler规定的时间再一个一个进行处理,让这些消息循环起来的就是looper,而messagequeue消息队列则相当于缓冲区

Chapter Four,源码研究

在明白了是什么和怎么做之后,接下里看一看源码的解释,为什么要用Looper.prepare去创建looper和messagequeue的实例,以及looper.loop是如何循环消息的? 首先来看prepare方法:

代码语言:javascript
复制
    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
     */
    public static void prepare() {
        prepare(true);
    }

prepare方法介绍:用于为当前线程初始化一个Looper,也就是说在哪个线程调用prepare就是为哪个线程初始化Looper。有了Looper,就可以通过loop进行消息循环,但在循环前需要去创建Handler进行消息的分发,因为message是经由handler发送给messagequeue的。当调用了prepare之后一定要记得去调用loop方法,并且在结束时调用quit方法。 Looper.prepare调用了Looper的私有静态方法prepare(boolean)

代码语言:javascript
复制
//quitallowed故名思义是说在调用quit时是否允许退出
 private static void prepare(boolean quitAllowed) {
         //如果已经创建了Looper,则sThreadLocal.get()不为null
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

线程中只允许创建一个Looper,如果多次prepare创建Looper则会抛出运行时异常“Only one Looper may be created per thread”,接下来调用Looper的构造器。

代码语言:javascript
复制
  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在Looper的构造方法中初始化了Looper中的消息队列和所对应的线程字段。从这里可以看出在Looper.prepare中创建了消息队列。 接下来看Looper的loop方法

代码语言:javascript
复制
/**
   *实现消息队列中的消息循环
  * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        //myLooper()是获取到sThreadLocal.get()中的值,如果已经执行了prepare,则获取到Looper对象
        final Looper me = myLooper();
        if (me == null) {
            //如果没有创建looper,则抛出运行时异常
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //获取到looper对象中的消息队列对象引用
         final MessageQueue queue = me.mQueue;

        ........
       .......
         .....
         for (;;) {
           //寻找下一条消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                //如果没有消息,则不需要循环
                // No message indicates that the message queue is quitting.
                return;
            }
            .......
           .......
           ........ 
          //msg.target是handler对象引用,在这里调用handler的dispatchMessage进行消息的分发
           msg.target.dispatchMessage(msg);
              ......
              ......
           .....
            ...
          //当消息分发给handler之后,就将该条消息清除  
          msg.recycleUnchecked();
        }
    }

从对loop方法的分析可以看出,在从消息队列取出消息后,就交由handler去进行分发

代码语言:javascript
复制
   public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

可以看出对于消息的处理分为两种情况:Message自己处理或者说是handler对其进行处理。 如果message自己添加了callback(Runnable对象,字段为callback),则使用自己的callback进行处理,但如果callback对象为null,则交由handle处理。 对于handler的处理方式又有一些区分,如果在创建handler时带有mCallback(对象为Callback,字段为mCallback),则交由callback处理,否则才会去有handler的handleMessage方法进行处理。

至此,对于handler,looper,messagequeue,message源码分析完毕。

总结一下就是

  • 调用Looper.prepare创建looper和messagequeue对象
  • handler通过调用post将runnable发送给messagequeue,或者通过调用sendMessage将message发送给messagequeue
  • messagequeue对消息进行一个管理,什么时间哪条消息出栈一是看handler放进来时的意愿,二是看顺序
  • Looper.loop将消息循环起来,利用handler去分发消息
  • 消息可以message自己处理,也可以handler去处理
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-02-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Chapter One,背景介绍
  • Chapter Two , 概念型介绍
    • 一:Handler:
      • 二:MessageQueue:
        • 三:Looper:
        • Chapter Three,创建Handler进行进程间通信
          • 第一种情况,使用主线程中的handler进行message的send和process
            • 第二种情况:使用子线程中的handler进行线程中的通信
            • Chapter Four,源码研究
            相关产品与服务
            消息队列 CMQ 版
            消息队列 CMQ 版(TDMQ for CMQ,简称 TDMQ CMQ 版)是一款分布式高可用的消息队列服务,它能够提供可靠的,基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)中的信息传递,存储在可靠有效的 CMQ 队列中,防止消息丢失。TDMQ CMQ 版支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档