请选择 进入手机版 | 继续访问电脑版

[Android] Android Handler的利用 详解

[复制链接]
查看50 | 回复14 | 2021-9-14 05:12:03 | 显示全部楼层 |阅读模式

在Android开发 中,我们常常 会碰到 如许 一种环境 :在UI界面上举行 某项操作后要实行 一段很耗时的代码,比如我们在界面上点击了一个”下载“按钮,那么我们必要 实行 网络哀求 ,这是一个耗时操作,由于 不知道什么时间 才能完成。为了保证不影响UI线程,以是 我们会创建一个新的线程去实行 我们的耗时的代码。当我们的耗时操作完成时,我们必要 更新UI界面以告知用户操作完成了。以是 我们大概 会写出如下的代码:

  1. package ispring.com.testhandler;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.widget.Button;
  6. import android.widget.TextView;
  7. public class MainActivity extends Activity implements Button.OnClickListener {
  8. private TextView statusTextView = null;
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState) {
  11. super.onCreate(savedInstanceState);
  12. setContentView(R.layout.activity_main);
  13. statusTextView = (TextView)findViewById(R.id.statusTextView);
  14. Button btnDownload = (Button)findViewById(R.id.btnDownload);
  15. btnDownload.setOnClickListener(this);
  16. }
  17. @Override
  18. public void onClick(View v) {
  19. DownloadThread downloadThread = new DownloadThread();
  20. downloadThread.start();
  21. }
  22. class DownloadThread extends Thread{
  23. @Override
  24. public void run() {
  25. try{
  26. System.out.println("开始下载文件");
  27. //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
  28. Thread.sleep(5000);
  29. System.out.println("文件下载完成");
  30. //文件下载完成后更新UI
  31. MainActivity.this.statusTextView.setText("文件下载完成");
  32. }catch (InterruptedException e){
  33. e.printStackTrace();
  34. }
  35. }
  36. }
  37. }
复制代码

上面的代码演示了单击”下载“按钮后会启动一个新的线程去实行 实际 的下载操作,实行 完毕后更新UI界面。但是在实际 运行到代码MainActivity.this.statusTextView.setText(“文件下载完成”)时,会报错如下,体系 崩溃退出:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
错误的意思是只有创建View的原始线程才能更新View。出现如许 错误的缘故原由 是Android中的View不是线程安全的,在Android应用启动时,会主动 创建一个线程,即程序的主线程,主线程负责UI的展示、UI变乱 消息的派发处理等等,因此主线程也叫做UI线程,statusTextView是在UI线程中创建的,当我们在DownloadThread线程中去更新UI线程中创建的statusTextView时自然 会报上面的错误。Android的UI控件黑白 线程安全的,着实 很多平台的UI控件都黑白 线程安全的,比如C#的.Net Framework中的UI控件也黑白 线程安全的,以是 不仅仅在Android平台中存在从一个新线程中去更新UI线程中创建的UI控件的标题 。不同的平台提供了不同的办理 方案以实现跨线程跟新UI控件,Android为相识 决这种标题 引入了Handler机制。

那么Handler到底是什么呢?Handler是Android中引入的一种让开发 者参与处理线程中消息循环的机制。每个Hanlder都关联了一个线程,每个线程内部都维护了一个消息队列MessageQueue,如许 Handler实际 上也就关联了一个消息队列。可以通过Handler将Message和Runnable对象发送到该Handler所关联线程的MessageQueue(消息队列)中,然后该消息队列不停 在循环拿出一个Message,对其举行 处理,处理完之后拿出下一个Message,继续举行 处理,周而复始。当创建一个Handler的时间 ,该Handler就绑定了当前创建Hanlder的线程。从这时起,该Hanlder就可以发送Message和Runnable对象到该Handler对应的消息队列中,当从MessageQueue取出某个Message时,会让Handler对其举行 处理。

Handler可以用来在多线程间举行 通讯 ,在另一个线程中去更新UI线程中的UI控件只是Handler利用 中的一种典型案例,除此之外,Handler可以做很多其他的事变 。每个Handler都绑定了一个线程,假设存在两个线程ThreadA和ThreadB,并且HandlerA绑定了 ThreadA,在ThreadB中的代码实行 到某处时,出于某些缘故原由 ,我们必要 让ThreadA实行 某些代码,此时我们就可以利用 Handler,我们可以在ThreadB中向HandlerA中加入某些信息以告知ThreadA中该做某些处理了。由此可以看出,Handler是Thread的代言人,是多线程之间通讯 的桥梁,通过Handler,我们可以在一个线程中控制另一个线程去做某事。

Handler提供了两种方式办理 我们在本文一开始碰到 的标题 (在一个新线程中更新主线程中的UI控件),一种是通过post方法,一种是调用sendMessage方法。

a. 利用 post方法,代码如下:

  1. package ispring.com.testhandler;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.os.Handler;
  5. import android.view.View;
  6. import android.widget.Button;
  7. import android.widget.TextView;
  8. public class MainActivity extends Activity implements Button.OnClickListener {
  9. private TextView statusTextView = null;
  10. //uiHandler在主线程中创建,所以自动绑定主线程
  11. private Handler uiHandler = new Handler();
  12. @Override
  13. protected void onCreate(Bundle savedInstanceState) {
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.activity_main);
  16. statusTextView = (TextView)findViewById(R.id.statusTextView);
  17. Button btnDownload = (Button)findViewById(R.id.btnDownload);
  18. btnDownload.setOnClickListener(this);
  19. System.out.println("Main thread id " + Thread.currentThread().getId());
  20. }
  21. @Override
  22. public void onClick(View v) {
  23. DownloadThread downloadThread = new DownloadThread();
  24. downloadThread.start();
  25. }
  26. class DownloadThread extends Thread{
  27. @Override
  28. public void run() {
  29. try{
  30. System.out.println("DownloadThread id " + Thread.currentThread().getId());
  31. System.out.println("开始下载文件");
  32. //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
  33. Thread.sleep(5000);
  34. System.out.println("文件下载完成");
  35. //文件下载完成后更新UI
  36. Runnable runnable = new Runnable() {
  37. @Override
  38. public void run() {
  39. System.out.println("Runnable thread id " + Thread.currentThread().getId());
  40. MainActivity.this.statusTextView.setText("文件下载完成");
  41. }
  42. };
  43. uiHandler.post(runnable);
  44. }catch (InterruptedException e){
  45. e.printStackTrace();
  46. }
  47. }
  48. }
  49. }
复制代码

我们在Activity中创建了一个Handler成员变量uiHandler,Handler有个特点,在实行 new Handler()的时间 ,默认环境 下Handler会绑定当前代码实行 的线程,我们在主线程中实例化了uiHandler,以是 uiHandler就主动 绑定了主线程,即UI线程。当我们在DownloadThread中实行 完耗期间 码后,我们将一个Runnable对象通过post方法传入到了Handler中,Handler会在合适的时间 让主线程实行 Runnable中的代码,如许 Runnable就在主线程中实行 了,从而准确 更新了主线程中的UI。以下是输出效果 :

这里写图片形貌

通过输出效果 可以看出,Runnable中的代码所实行 的线程ID与DownloadThread的线程ID不同,而与主线程的线程ID类似 ,因此我们也由此看出在实行 了Handler.post(Runnable)这句代码之后,运行Runnable代码的线程与Handler所绑定的线程是同等 的,而与实行 Handler.post(Runnable)这句代码的线程(DownloadThread)无关。

b. 利用 sendMessage方法,代码如下:

  1. package ispring.com.testhandler;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.os.Handler;
  5. import android.os.Message;
  6. import android.view.View;
  7. import android.widget.Button;
  8. import android.widget.TextView;
  9. public class MainActivity extends Activity implements Button.OnClickListener {
  10. private TextView statusTextView = null;
  11. //uiHandler在主线程中创建,所以自动绑定主线程
  12. private Handler uiHandler = new Handler(){
  13. @Override
  14. public void handleMessage(Message msg) {
  15. switch (msg.what){
  16. case 1:
  17. System.out.println("handleMessage thread id " + Thread.currentThread().getId());
  18. System.out.println("msg.arg1:" + msg.arg1);
  19. System.out.println("msg.arg2:" + msg.arg2);
  20. MainActivity.this.statusTextView.setText("文件下载完成");
  21. break;
  22. }
  23. }
  24. };
  25. @Override
  26. protected void onCreate(Bundle savedInstanceState) {
  27. super.onCreate(savedInstanceState);
  28. setContentView(R.layout.activity_main);
  29. statusTextView = (TextView)findViewById(R.id.statusTextView);
  30. Button btnDownload = (Button)findViewById(R.id.btnDownload);
  31. btnDownload.setOnClickListener(this);
  32. System.out.println("Main thread id " + Thread.currentThread().getId());
  33. }
  34. @Override
  35. public void onClick(View v) {
  36. DownloadThread downloadThread = new DownloadThread();
  37. downloadThread.start();
  38. }
  39. class DownloadThread extends Thread{
  40. @Override
  41. public void run() {
  42. try{
  43. System.out.println("DownloadThread id " + Thread.currentThread().getId());
  44. System.out.println("开始下载文件");
  45. //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
  46. Thread.sleep(5000);
  47. System.out.println("文件下载完成");
  48. //文件下载完成后更新UI
  49. Message msg = new Message();
  50. //虽然Message的构造函数式public的,我们也可以通过以下两种方式通过循环对象获取Message
  51. //msg = Message.obtain(uiHandler);
  52. //msg = uiHandler.obtainMessage();
  53. //what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别
  54. //出不同的Message,以便我们做出不同的处理操作
  55. msg.what = 1;
  56. //我们可以通过arg1和arg2给Message传入简单的数据
  57. msg.arg1 = 123;
  58. msg.arg2 = 321;
  59. //我们也可以通过给obj赋值Object类型传递向Message传入任意数据
  60. //msg.obj = null;
  61. //我们还可以通过setData方法和getData方法向Message中写入和读取Bundle类型的数据
  62. //msg.setData(null);
  63. //Bundle data = msg.getData();
  64. //将该Message发送给对应的Handler
  65. uiHandler.sendMessage(msg);
  66. }catch (InterruptedException e){
  67. e.printStackTrace();
  68. }
  69. }
  70. }
  71. }
复制代码

通过Message与Handler举行 通讯 的步骤是:
1. 重写Handler的handleMessage方法,根据Message的what值举行 不同的处理操作
2. 创建Message对象
固然 Message的构造函数式public的,我们还可以通过Message.obtain()或Handler.obtainMessage()来获得一个Message对象(Handler.obtainMessage()内部着实 调用了Message.obtain())。
3. 设置Message的what值
Message.what是我们自定义的一个Message的辨认 码,以便于在Handler的handleMessage方法中根据what辨认 出不同的Message,以便我们做出不同的处理操作。
4. 设置Message的所携带的数据,简单数据可以通过两个int范例 的field arg1和arg2来赋值,并可以在handleMessage中读取。
5. 假如 Message必要 携带复杂的数据,那么可以设置Message的obj字段,obj是Object范例 ,可以赋予恣意 范例 的数据。或者可以通过调用Message的setData方法赋值Bundle范例 的数据,可以通过getData方法获取该Bundle数据。
6. 我们通过Handler.sendMessage(Message)方法将Message传入Handler中让其在handleMessage中对其举行 处理。
必要 阐明 的是,假如 在handleMessage中 不必要 判断 Message范例 ,那么就无须设置Message的what值;而且让Message携带数据也不是必须的,只有在必要 的时间 才必要 让其携带数据;假如 确实必要 让Message携带数据,应该只管 利用 arg1或arg2或两者,能用arg1和arg2办理 的话就不要用obj,由于 用arg1和arg2更高效。
程序的运行效果 如下:

这里写图片形貌

由上我们可以看出,实行 handleMessage的线程与创建Handler的线程是同一线程,在本示例中都是主线程。实行 handleMessage的线程与实行 uiHandler.sendMessage(msg)的线程没有关系。

本文紧张 是对Android中Handler的作用于怎样 利用 举行 了初步先容 ,假如 大家想相识 Handler的内部实现原理,可以参见下一篇博文《深入源码剖析 Android中的Handler,Message,MessageQueue,Looper》

到此这篇关于Android Handler的利用 详解的文章就先容 到这了,更多干系 Android Handler的利用 内容请搜刮 脚本之家从前 的文章或继续欣赏 下面的干系 文章盼望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

avatar 珍惜637 | 2021-9-21 01:26:16 | 显示全部楼层
不是惊喜,是惊吓!
回复

使用道具 举报

avatar 123457466 | 2021-10-4 10:04:47 | 显示全部楼层
admin楼主是男的还是女的?
回复

使用道具 举报

avatar 音乐之家1 | 2021-10-6 04:21:37 | 显示全部楼层
楼上长在线啊?
回复

使用道具 举报

avatar 狒狒V | 2021-10-7 21:08:46 | 显示全部楼层
楼上的刚出院吧?
回复

使用道具 举报

avatar 去火星三小时自 | 2021-10-8 12:43:07 | 显示全部楼层
学习雷锋,好好回帖!
回复

使用道具 举报

avatar 123457782 | 2021-10-10 03:22:39 | 显示全部楼层
admin楼主,您主治大夫在到处找您呢!
回复

使用道具 举报

avatar 楠木2017 | 2021-10-10 12:11:21 | 显示全部楼层
投admin楼主一票,不用谢哦!
回复

使用道具 举报

avatar 极忘投 | 2021-10-11 06:17:26 | 显示全部楼层
有品位!
回复

使用道具 举报

avatar 索支较 | 2021-10-12 00:17:49 | 显示全部楼层
刚看见一个妹子,很漂亮!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则