Android之handler消息传递机制
2016-09-01 21:42
489 查看
说明
今天学习的是Activity中UI组件中的信息传递Handler,相信很多朋友都知道,Android为了线程安全,并不允许我们在UI线程外操作UI;很多时候我们做界面刷新都需要通过Handler来通知UI组件更新!除了用Handler完成界面更新外,还可以使用runOnUiThread()来更新,甚至更高级的事务总线,当然,这里我们只讲解Handler,什么是Handler,执行流程,相关方法,子线程与主线程中中使用Handler的区别等!
Android 在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没有办法更新 UI 信息,就会抛出异常信息。
在新启动的线程中发送消息
在主线程中获取、处理消息
为了让主线程能”适时“地处理新启动的线程所发送的消息,显然只能通过回调的方式来实现——开发者只要重写Handler类中处理消息的方法(handleMessage),当新启动的线程发送消息时,消息会发送到与之关联的消息队列(MessageQueue),而Handler会不断地从MessageQueue中获取并处理消息——这将导致Handler类中处理消息的方法被回调。
这里面的几个概念需要了解一下:
Message
消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。
Message Queue
消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
Handler
Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。
UI线程
就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue;
Looper:每个线程只能够有一个Looper,扮演Message Queue和Handler之间桥梁的角色,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理!
简单点说
当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理!
sendEmptyMessage(int what):发送空消息
sendEmptyMessageDelayed(int what,long delayMillis):指定延时多少毫秒后发送空信息
sendMessage(Message msg):立即发送信息
sendMessageDelayed(Message msg):指定延时多少毫秒后发送信息
final boolean hasMessage(int what):检查消息队列中是否包含what属性为指定值的消息
如果是参数为(int what,Object object):除了判断what属性,还需要判断Object属性是否为指定对象的消息
1) 发送空消息方法(sendEmptyMessage)
代码示例: 简单的一个定时切换图片的程序,通过Timer定时器,定时修改ImageView显示的内容,从而形成帧动画
MainActivity.java:
上面的程序中通过Timer周期性地执行指定任务,Timer对象可调度TimerTask对象,TimerTask对象的本质就是启动一条新的线程,因为Android不允许在新线程中访问Activity中的界组件,因此程序只能在新线程里发送一条消息,通知系统更新ImageView组件。
我们重写了handler的handleMessage方法,该方法用于处理消息——当新线程发送消息时,该方法会被自动回调,handleMessage(Message msg)方法依然位于主线程,所以可以动态地修改ImageView组件的属性。
效果演示:
![](https://img-blog.csdn.net/20160902225650178)
2) 传递Message更新UI界面(sendMessage)
sendMessage 类方法,允许你安排一个带数据的 Message 对象到队列中,等待更新。
我们实现一个定时器,每隔一秒显示数字0-9:
我们用实现Runnable接口的方式定义一个子线程,在这个子线程里我们创建了Message对象(创建Message对象有两种方式,new Message() 以及 Message.obtain())以及一个Bundle对象,Bundle对象以键值对的方式来存放数据,然后把数据放入Message中,再通过sendMessage方法发送出去。
我们在按钮的点击事件中启动线程。
然后我们新增了一个handler类,并重写了它的handleMessage方法,在这里对具体的Message进行处理。如果发现Message的what字段的值是0x123,就提取对应Message中的数据。
效果演示:
![](https://img-blog.csdn.net/20160903112422386)
3) postDelayed更新UI(sendMessage)
post类方法把runnable对象作为消息放到队列中等待执行,run方法中就是具体执行过程。
我们还是实现一个定时器,每隔一秒显示数字0-9:
handler.post(new Runnable)方法是将这个Runnable对象加入到消息队列中之后立即得到执行,handler.postDelayed(new Runnable,Long long)方法是将这个Runnable对象每隔指定时间加入到消息队列一次。
上面代码中,我们点击按钮,post方法将会把Runnable对象加入到队列中执行,run方法得到执行;在run方法中我们更新UI组件,注意,这个时候并没有开启子线程,事实上上面操作都是在以恶搞线程即主线程中完成的。然后执行postDelayed方法,每隔1秒钟运行一次run方法,因此屏幕上将会出现0-9的数字显示。
效果演示:
![](https://img-blog.csdn.net/20160903131640497)
这其实就是Android中的异步消息处理机制。流程示意图如下所示:
![](https://img-blog.csdn.net/20160903141403430)
图片来源于第一行代码。
一条Message经过这样的流程辗转调用后,也就从子线程进入了主线程,从不能更新UI变成了可以更新UI。
下面使用一个实例来介绍Looper和handler的用法,将Handler写在子线程中的情况*:
如果是Handler写在了子线程中的话,我们就需要自己创建一个Looper对象了!创建的流程如下:
1 )直接调用Looper.prepare()方法即可为当前线程创建Looper对象,而它的构造器会创建配套的MessageQueue;
2 )创建Handler对象,重写handleMessage( )方法就可以处理来自于其他线程的信息了! 3
)调用Looper.loop()方法启动Looper
使用示例: 输入一个数,计算后通过Toast输出在这个范围内的所有质数
上面代码中在新线程中创建了Handler对象,由于在新线程中创建Handler时必须先创建Looper,因此程序先调用Looper.prepare()方法为当前线程创建一个Looper实例,并创建配套的MessageQueue,有了Looper对象之后,再创建Handler对象,该handler可以处理其他线程发送过来的消息。程序最后还调用了Looper.loop方法。
当我们需要一个工作线程,而不是把它当作一次性消耗品,用过即废的话,就可以使用它。
接下来我们实现一个按一下按钮,TextView变换内容的实例:
这个实例有所欠缺,我学艺也不精,大家看看就好,主要是直到HandlerThread的一般用法。
今天学习的是Activity中UI组件中的信息传递Handler,相信很多朋友都知道,Android为了线程安全,并不允许我们在UI线程外操作UI;很多时候我们做界面刷新都需要通过Handler来通知UI组件更新!除了用Handler完成界面更新外,还可以使用runOnUiThread()来更新,甚至更高级的事务总线,当然,这里我们只讲解Handler,什么是Handler,执行流程,相关方法,子线程与主线程中中使用Handler的区别等!
Handler类简介
什么是Handler
Handler 是 Android 给我们提供来更新 UI 的一套机制,也是一套消息处理的机制,我们可以发送消息,也可以通过它来处理消息,Handler 在我们的 framework中是非常常见的。Android 在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没有办法更新 UI 信息,就会抛出异常信息。
Handler类的主要作用
有两个:在新启动的线程中发送消息
在主线程中获取、处理消息
Handler的执行流程
上面的说法看上去很简单,似乎只要分成两步即可:在新启动的线程中发送消息(Message);然后再主线程中获取并处理消息。但这个过程涉及一个问题:在新启动的线程中何时发送消息呢?在主线程中又何时去获取并处理消息呢?为了让主线程能”适时“地处理新启动的线程所发送的消息,显然只能通过回调的方式来实现——开发者只要重写Handler类中处理消息的方法(handleMessage),当新启动的线程发送消息时,消息会发送到与之关联的消息队列(MessageQueue),而Handler会不断地从MessageQueue中获取并处理消息——这将导致Handler类中处理消息的方法被回调。
这里面的几个概念需要了解一下:
Message
消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。
Message Queue
消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
Handler
Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。
UI线程
就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue;
Looper:每个线程只能够有一个Looper,扮演Message Queue和Handler之间桥梁的角色,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理!
简单点说
当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理!
Handler的相关方法
void handleMessage(Message msg):处理消息的方法,通常是用于被重写!sendEmptyMessage(int what):发送空消息
sendEmptyMessageDelayed(int what,long delayMillis):指定延时多少毫秒后发送空信息
sendMessage(Message msg):立即发送信息
sendMessageDelayed(Message msg):指定延时多少毫秒后发送信息
final boolean hasMessage(int what):检查消息队列中是否包含what属性为指定值的消息
如果是参数为(int what,Object object):除了判断what属性,还需要判断Object属性是否为指定对象的消息
Handler的用法
Handler写在主线程中
在主线程中,因为系统已经初始化了一个Looper对象,所以我们直接创建Handler对象,就可以进行信息的发送与处理了!1) 发送空消息方法(sendEmptyMessage)
代码示例: 简单的一个定时切换图片的程序,通过Timer定时器,定时修改ImageView显示的内容,从而形成帧动画
MainActivity.java:
public class MainActivity extends AppCompatActivity { int currentImageId = 0; private ImageView imageView; int[] imgids = new int[]{ R.mipmap.one, R.mipmap.two, R.mipmap.three, R.mipmap.four, R.mipmap.five, R.mipmap.six, R.mipmap.seven, R.mipmap.eight, R.mipmap.nine, R.mipmap.ten, R.mipmap.eleven }; Handler myHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 0x123) { imageView.setImageResource(imgids[currentImageId++ % 11]); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView) this.findViewById(R.id.imageView); //使用定时器,每隔200毫秒让handler发送一个空信息 new Timer().schedule(new TimerTask() { @Override public void run() { myHandler.sendEmptyMessage(0x123); } }, 0, 200); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { moveTaskToBack(true); } return super.onKeyDown(keyCode, event); } @Override public void onBackPressed() { moveTaskToBack(true); } }
上面的程序中通过Timer周期性地执行指定任务,Timer对象可调度TimerTask对象,TimerTask对象的本质就是启动一条新的线程,因为Android不允许在新线程中访问Activity中的界组件,因此程序只能在新线程里发送一条消息,通知系统更新ImageView组件。
我们重写了handler的handleMessage方法,该方法用于处理消息——当新线程发送消息时,该方法会被自动回调,handleMessage(Message msg)方法依然位于主线程,所以可以动态地修改ImageView组件的属性。
效果演示:
2) 传递Message更新UI界面(sendMessage)
sendMessage 类方法,允许你安排一个带数据的 Message 对象到队列中,等待更新。
我们实现一个定时器,每隔一秒显示数字0-9:
public class MainActivity extends AppCompatActivity { private static final String NUMBER_SHOW = "number"; private Button button; private TextView textView; Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { if(msg.what == 0x123){ Bundle bundle = msg.getData(); int i = bundle.getInt(NUMBER_SHOW); textView.setText(" "+i); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); } private void initViews() { textView = (TextView)this.findViewById(R.id.textView); button = (Button)this.findViewById(R.id.button); button.setOnClickListener(buttonOnClickListener); } View.OnClickListener buttonOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { Thread thread = new Thread(new WorkRunnable()); thread.start(); } }; class WorkRunnable implements Runnable{ @Override public void run() { int i = 0; while(i<10){ Message msg = Message.obtain(); msg.what = 0x123; Bundle bundle = new Bundle(); bundle.putInt(NUMBER_SHOW,i); msg.setData(bundle); handler.sendMessage(msg); try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } i++; } } } }
我们用实现Runnable接口的方式定义一个子线程,在这个子线程里我们创建了Message对象(创建Message对象有两种方式,new Message() 以及 Message.obtain())以及一个Bundle对象,Bundle对象以键值对的方式来存放数据,然后把数据放入Message中,再通过sendMessage方法发送出去。
我们在按钮的点击事件中启动线程。
然后我们新增了一个handler类,并重写了它的handleMessage方法,在这里对具体的Message进行处理。如果发现Message的what字段的值是0x123,就提取对应Message中的数据。
效果演示:
3) postDelayed更新UI(sendMessage)
post类方法把runnable对象作为消息放到队列中等待执行,run方法中就是具体执行过程。
我们还是实现一个定时器,每隔一秒显示数字0-9:
public class MainActivity extends AppCompatActivity { private static final String NUMBER_SHOW = "number"; private Button start,end; private TextView textView; Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); } private void initViews() { textView = (TextView)this.findViewById(R.id.textView); start = (Button)this.findViewById(R.id.start); start.setOnClickListener(buttonOnClickListener); end = (Button)this.findViewById(R.id.end); end.setOnClickListener(endOnClickListener); } View.OnClickListener endOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }; View.OnClickListener buttonOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { handler.post(new PostDelayRunnable()); } }; class PostDelayRunnable implements Runnable{ int i = 0; @Override public void run() { if (i >= 10) { handler.removeCallbacks(this); } else { textView.setText(" " + i); handler.postDelayed(this, 1000); } i++; } } }
handler.post(new Runnable)方法是将这个Runnable对象加入到消息队列中之后立即得到执行,handler.postDelayed(new Runnable,Long long)方法是将这个Runnable对象每隔指定时间加入到消息队列一次。
上面代码中,我们点击按钮,post方法将会把Runnable对象加入到队列中执行,run方法得到执行;在run方法中我们更新UI组件,注意,这个时候并没有开启子线程,事实上上面操作都是在以恶搞线程即主线程中完成的。然后执行postDelayed方法,每隔1秒钟运行一次run方法,因此屏幕上将会出现0-9的数字显示。
效果演示:
Handler 与 Looper、MessageQueue 的关系
handler 负责发送消息,Looper 负责接收 Handler 发送消息,并直接把消息回传给 handler 自己,MessageQueue 就是一个存储消息的容器。这其实就是Android中的异步消息处理机制。流程示意图如下所示:
图片来源于第一行代码。
一条Message经过这样的流程辗转调用后,也就从子线程进入了主线程,从不能更新UI变成了可以更新UI。
下面使用一个实例来介绍Looper和handler的用法,将Handler写在子线程中的情况*:
如果是Handler写在了子线程中的话,我们就需要自己创建一个Looper对象了!创建的流程如下:
1 )直接调用Looper.prepare()方法即可为当前线程创建Looper对象,而它的构造器会创建配套的MessageQueue;
2 )创建Handler对象,重写handleMessage( )方法就可以处理来自于其他线程的信息了! 3
)调用Looper.loop()方法启动Looper
使用示例: 输入一个数,计算后通过Toast输出在这个范围内的所有质数
public class MainActivity extends AppCompatActivity { static final String UUPER_NUM = "upper"; EditText etNum; Button button; CalThread calThread; class CalThread extends Thread{ public Handler handler; @Override public void run() { Looper.prepare(); handler = new Handler(){ @Override public void handleMessage(Message msg) { if(msg.what == 0x1233){ int upper = msg.getData().getInt(UUPER_NUM); List<Integer> nums = new ArrayList<Integer>(); outer: for(int i = 2;i<=upper;i++){ for(int j = 2;j<=Math.sqrt(i);j++){ if(i!=2&&i%j==0){ continue outer; } } nums.add(i); } Toast.makeText(MainActivity.this,nums.toString(),Toast.LENGTH_SHORT).show(); } } }; Looper.loop(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); etNum = (EditText)this.findViewById(R.id.editText); calThread = new CalThread(); calThread.start(); button = (Button)this.findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Message msg = Message.obtain(); msg.what = 0x1233; Bundle bundle = new Bundle(); bundle.putInt(UUPER_NUM,Integer.parseInt(etNum.getText().toString())); msg.setData(bundle); calThread.handler.sendMessage(msg); } }); } }
上面代码中在新线程中创建了Handler对象,由于在新线程中创建Handler时必须先创建Looper,因此程序先调用Looper.prepare()方法为当前线程创建一个Looper实例,并创建配套的MessageQueue,有了Looper对象之后,再创建Handler对象,该handler可以处理其他线程发送过来的消息。程序最后还调用了Looper.loop方法。
HandlerThread
HandlerThread 继承于 Thread,所以它本质就是个 Thread。与普通的 Thread 的差别就在于,它有个 Looper 成员变量。这个 Looper 其实就是对消息队列以及队列处理逻辑的封装,简单来说就是消息队列+消息循环。当我们需要一个工作线程,而不是把它当作一次性消耗品,用过即废的话,就可以使用它。
接下来我们实现一个按一下按钮,TextView变换内容的实例:
这个实例有所欠缺,我学艺也不精,大家看看就好,主要是直到HandlerThread的一般用法。
public class MainActivity extends AppCompatActivity { private Button start,end; private TextView textView; class MyHandler extends Handler{ public MyHandler(){ } public MyHandler(Looper looper){ super(looper); } @Override public void handleMessage(Message msg) { if(msg.what == 99){ String name = (String)msg.obj; textView.setText(name); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); } private void initViews() { textView = (TextView)this.findViewById(R.id.textView); start = (Button)this.findViewById(R.id.start); start.setOnClickListener(buttonOnClickListener); end = (Button)this.findViewById(R.id.end); end.setOnClickListener(endOnClickListener); } View.OnClickListener endOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }; View.OnClickListener buttonOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { HandlerThread handlerThread = new HandlerThread("handlerThread"); handlerThread.start(); //创建handler对象必须创建Looper对象,Looper对象由HandlerThread.getLooper方法得到 MyHandler myHandler = new MyHandler(handlerThread.getLooper()); Message msg = myHandler.obtainMessage(99); msg.obj = "张三"; //把消息传给目标对象,即生成Message对象的Handler对象 msg.sendToTarget(); } }; }
相关文章推荐
- Android中的消息机制:Handler消息传递机制
- Android中的消息机制:Handler消息传递机制
- Android Handler消息传递机制
- android Handler 消息传递机制
- android の Handler消息传递机制
- Android笔记二十五.Android事件Handler消息传递机制
- Android Handler消息传递机制
- Android22_Handler消息传递机制——源码赏析
- Handler Looper源码解析(Android消息传递机制)
- Android 中 Handler 消息传递机制
- Android 中 Handler 消息传递机制
- Android Handler类消息传递机制(三)不同类中使用Handler
- Android Handler类消息传递机制(二)同一类(或内部类)中使用Handler
- Android中Handler消息传递机制
- android学习之——Handler消息传递机制
- Android Handler消息传递机制
- 解析Android的 消息传递机制Handler
- 解析Android的 消息传递机制Handler
- Android Handler消息传递机制
- Android 中 Handler 消息传递机制