android 三种切回主线程更新UI的方法 handler.post runOnUiThread

一、前期知识储备

(1)上官方文档:参见Handler类中的描述

 android 三种切回主线程更新UI的方法 handler.post runOnUiThread

 android 三种切回主线程更新UI的方法 handler.post runOnUiThread

首先,post和postDelay都是Handler的方法,用以在子线程中发送Runnable对象的方法;

其次,Android中post()方法可以直接在非UI线程中更新UI,不同与Handelr的Send类方法,需要进行切换;

最后,两个方法在实现UI线程事件的时间上有所区别,postDelayed()方法用以延期执行,post则是立即执行;

(2)Handler类的post类方法和send类方法联系与区别

post类方法,以匿名内部类的形式发送Runnable对象,在Runnable对象重写的run()方法中直接对UI进行更新;

 
  1. new Thread(new Runnable() {

  2. @Override

  3. public void run() {

  4. /**

  5. 耗时操作

  6. */

  7. handler.post(new Runnable() {

  8. @Override

  9. public void run() {

  10. /**

  11. 更新UI

  12. */

  13. }

  14. });

  15. }

  16. }).start();

三种切回主线程的实例:

 
  1. final Handler handler = new Handler();

  2. new Thread(new Runnable() {

  3. @Override

  4. public void run() {

  5. // 素描算法处理 耗时操作

  6. final Bitmap bitmap1 = SketchUtil.testGaussBlur(finalBitmap,1,1);

  7. final Bitmap bitmap2 = SketchUtil.testGaussBlur(finalBitmap,10,10);

  8. final Bitmap bitmap3 = SketchUtil.testGaussBlur(finalBitmap,20,20);

  9.  
  10. // 三种切回主线程更新UI的方法

  11. imageView.post(new Runnable() {

  12. @Override

  13. public void run() {

  14. imageView.setImageBitmap(bitmap1); // 素描图

  15. }

  16. });

  17.  
  18. runOnUiThread(new Runnable() {

  19. @Override

  20. public void run() {

  21. orignView.setImageBitmap(bitmap2); // 素描图

  22. }

  23. });

  24.  
  25. handler.post(new Runnable() {

  26. @Override

  27. public void run() {

  28. threeView.setImageBitmap(bitmap3); // 素描图

  29. }

  30. });

  31. }

  32. }).start();

注意:使用handler方法切回主线程时,注意handler的实例化要放在主线程中,而不能在新开的子线程中,否则报错:

RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

这是因为,Handler在哪里创建,就获得哪里的Looper。主线程创建的Handler,即默认使用主线程的Looper。

常见常用的post()类方法汇总:

post(Runnable)

postAtTime(Runnable,long)

postDelayed(Runnable long)

send类方法,比如sendMessage()方法,使用该方法发送构造好的Message,然后用Handler的handleMessage()方法接收发送出来的消息,在方法中对UI进行更新;

 
  1. private Handler handler = new Handler(){

  2. @Override

  3. public void handleMessage(Message msg) {

  4. super.handleMessage(msg);

  5. switch (msg.what) { //判断标志位

  6. case 1:

  7. /**

  8. 获取数据,更新UI

  9. */

  10. break;

  11. }

  12. }

  13. };

  14.  
  15.  
  16. public class WorkThread extends Thread {

  17.  
  18. @Override

  19. public void run() {

  20. super.run();

  21. /**

  22. 耗时操作

  23. */

  24.  
  25. //从全局池中返回一个message实例,避免多次创建message(如new Message)

  26. Message msg =Message.obtain();

  27. msg.obj = data;

  28. msg.what=1; //标志消息的标志

  29. handler.sendMessage(msg);

  30. }

  31. }

常见常用的send类方法汇总:

 

sendEmptyMessage(int)

sendMessage(Message)

sendMessageAtTime(Message,long)

sendMessageDelayed(Message,long)

分析:谷歌为Android系统提供了一系列的post类方法用以发送Runnable对象,又提供了一系列的send类方法用以发送Message对象,其实二者并不矛盾也不重复,打开post类方法的源码,就会发现最终发送的Runnable对象也会转变成Message对象进行发送。谷歌提供两类方法应该是分别处理不同的场景,发送的消息较为复杂时,且每种消息对应一种UI的更新时选择使用send类方法;而当子线程中只发出一种消息时,则直接使用post方法发送消息,且直接在post方法的内部实现UI的更新。

(3)Message的构造

 
  1. public final class Message implements Parcelable {

  2. public int what;

  3. public int arg1;

  4. public int arg2;

  5. public Object obj;

  6. ...

  7. }

Message类中有这几个成员变量描述消息,其中what是我们定义的消息码,为了让接收者能知道消息是关于什么的。arg1和arg2用于发送一些integer类型的值。obj用于传输任意类型的值。

(4)Handler接收消息的方法-handleMessage()方法

Handler是Android系统为处理异步消息机制所提供的类,我们在实际开发中最常使用handler的场景即是在子线程中处理耗时操作后利用Handler的post类方法或者send类方法来发送消息;而当使用的是send类方法时,需要使用Handler来接收消息,同时使用Handler的handleMessage()方法来处理消息。

 
  1. private static class MyHandler extends Handler{

  2.  
  3. //对Activity的弱引用

  4. private final WeakReference<HandlerActivity> mActivity;

  5.  
  6. public MyHandler(HandlerActivity activity){

  7. mActivity = new WeakReference<HandlerActivity>(activity);

  8. }

  9.  
  10. @Override

  11. public void handleMessage(Message msg) {

  12. HandlerActivity activity = mActivity.get();

  13. if(activity==null){

  14. super.handleMessage(msg);

  15. return;

  16. }

  17. switch (msg.what) {

  18. case DOWNLOAD_FAILED:

  19. Toast.makeText(activity, "下载失败", Toast.LENGTH_SHORT).show();

  20. break;

  21. case DOWNLOAD_SUCCESS:

  22. Toast.makeText(activity, "下载成功", Toast.LENGTH_SHORT).show();

  23. Bitmap bitmap = (Bitmap) msg.obj;

  24. activity.imageView.setVisibility(View.VISIBLE);

  25. activity.imageView.setImageBitmap(bitmap);

  26. break;

  27. default:

  28. super.handleMessage(msg);

  29. break;

  30. }

  31. }

  32. }

这里为避免出现内存泄漏,使用了静态内部类和弱引用结合的方式来实现。

二、上代码,看具体实现

view.post(new Runnable() {

    @Override

    public void run() {

view.performClick();//需要处理是事件,Android开发中常见的是UI操作

}});

注:post的方法意在main主线程执行完后立即调用。

view.postDelayed(new Runnable() {

    @Override

    public void run() {

view.performClick();//需要处理是事件,Android开发中常见的是UI操作

}},3000);//延迟3秒注:postDelayed的方法意在延迟执行,在main主线程执行完后延迟3秒后开始调用。

三、和Handler结合使用—开发中常见

实际开发中,最常见的就是和Handler结合使用,开启异步任务

下面实现从网络中加载一张图片,并且显示在UI界面上

 

(1)在 Activity 中创建 handler 对象,调用工作线程执行;

 
  1. public class MainActivity extends AppCompatActivity {

  2.  
  3. ImageView threadIv;

  4. ImageView runnableIv;

  5. SendThread sendThread;

  6. PostRunnable postRunnable;

  7. private final MyHandler handler = new MyHandler(this);

  8.  
  9. @Override

  10. protected void onCreate(Bundle savedInstanceState) {

  11. super.onCreate(savedInstanceState);

  12. setContentView(R.layout.activity_main);

  13. threadIv = (ImageView) findViewById(R.id.thread_iv);

  14. runnableIv = (ImageView) findViewById(R.id.runnable_iv);

  15.  
  16. sendThread = new SendThread(handler);

  17. sendThread.start();

  18.  
  19. postRunnable = new PostRunnable(handler);

  20. postRunnable.setRefreshUI(new PostRunnable.RefreshUI() {

  21. @Override

  22. public void setImage(byte[] data) {

  23. runnableIv.setImageBitmap(getBitmap(data));

  24. }

  25. });

  26. new Thread(postRunnable).start();

  27. }

  28.  
  29. /**

  30. 为避免handler造成的内存泄漏

  31. 1、使用静态的handler,对外部类不保持对象的引用

  32. 2、但Handler需要与Activity通信,所以需要增加一个对Activity的弱引用

  33. */

  34. private static class MyHandler extends Handler {

  35. private final WeakReference<Activity> mActivityReference;

  36.  
  37. MyHandler(Activity activity) {

  38. this.mActivityReference = new WeakReference<Activity>(activity);

  39. }

  40.  
  41. @Override

  42. public void handleMessage(Message msg) {

  43. super.handleMessage(msg);

  44. MainActivity activity = (MainActivity) mActivityReference.get(); //获取弱引用队列中的activity

  45. switch (msg.what) { //获取消息,更新UI

  46. case 1:

  47. byte[] data = (byte[]) msg.obj;

  48. activity.threadIv.setImageBitmap(activity.getBitmap(data));

  49. break;

  50. }

  51. }

  52. }

  53.  
  54. private Bitmap getBitmap(byte[] data) {

  55. return BitmapFactory.decodeByteArray(data, 0, data.length);

  56. }

  57.  
  58. @Override

  59. protected void onDestroy() {

  60. super.onDestroy();

  61. //避免activity销毁时,messageQueue中的消息未处理完;故此时应把对应的message给清除出队列

  62. handler.removeCallbacks(postRunnable); //清除runnable对应的message

  63. //handler.removeMessage(what) 清除what对应的message

  64. }

  65. }

(2)实现 runnable 接口,通过 post(Runnable)通信,并通过给定的回调接口通知 Activity 更新

 
  1. public class PostRunnable implements Runnable {

  2.  
  3. private Handler handler;

  4. private RefreshUI refreshUI;

  5. byte[] data = null;

  6.  
  7. public PostRunnable(Handler handler) {

  8. this.handler = handler;

  9. }

  10.  
  11. @Override

  12. public void run() {

  13. /**

  14. * 耗时操作

  15. */

  16. final Bitmap bitmap = null;

  17. HttpClient httpClient = new DefaultHttpClient();

  18. HttpGet httpGet = new HttpGet("http://i3.17173cdn.com/2fhnvk/YWxqaGBf/cms3/FNsPLfbkmwgBgpl.jpg");

  19. HttpResponse httpResponse = null;

  20. try {

  21. httpResponse = httpClient.execute(httpGet);

  22. if (httpResponse.getStatusLine().getStatusCode() == 200) {

  23. data = EntityUtils.toByteArray(httpResponse.getEntity());

  24. }

  25. } catch (IOException e) {

  26. e.printStackTrace();

  27. }

  28.  
  29. //返回结果给UI线程

  30. handler.post(new Runnable() {

  31. @Override

  32. public void run() {

  33. refreshUI.setImage(data);

  34. }

  35. });

  36. }

  37.  
  38. public interface RefreshUI {

  39. public void setImage(byte[] data);

  40. }

  41.  
  42. public void setRefreshUI(RefreshUI refreshUI) {

  43. this.refreshUI = refreshUI;

  44. }

  45. }

文章后期更改过,将文章主旨内容从post()方法和postDelayed()方法的联系与区别转到Android中使用Handler进行异步消息的处理,以几段代码为例,分析了Handler的发送消息的方法:①post类方法;②send类方法;及Handler类接收消息和处理消息的方法handleMessage();最后模拟了从网上下载图片进而在UI界面中进行更新。

提供一些其他Android消息机制分析,帮助理解读者理解:

android 三种切回主线程更新UI的方法 handler.post runOnUiThread

①Handler是Android消息机制的上层接口,通过它可以轻松地将一个任务切换到Handler所在的线程中去执行,该线程既可以是主线程,也可以是子线程,要看构造Handler时使用的构造方法中传入的Looper位于哪里;

②Handler的运行需要底层的MessageQueue和Looper的支撑,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,而线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper;

③上述代码中的第一个Handler-mainHandler,实例化的时候,直接在onCreate()方法中new出了实例,其实是其已经在主线程中了,主线程-ActivityThread,ActivityThread被创建时就会初始化Looper,这就是主线程中默认可以直接使用Handler的原因;

④上述代码中的第二个Handler-workHandler,它在实例化的时候,参数传入了 mHandlerThread.getLooper() ,注意,这个Handler使用的就不是主线程的Looper了,而是子线程的Looper,HandlerThread在调用start()方法之后,就可以获取到子线程的Looper,然后将其传入workHandler的构造方法中,那么此时的workHandler就会运行在子线程中,用于处理耗时操作。

⑤Handler的工作原理:Handler创建时会采用当前线程的Looper来构建内部消息循环系统,如果当前线程没有Looper,那么就会报错“Can`t create handler inside thread that has not called Looper.prepare()”解决方法有两个:为当前线程创建Looper即可,像上述代码中workHandler,或者在一个有Looper的线程中创建Handler也行,就像上述代码中的mainHandler一样;

⑥调用Handler的post方法会将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法来发送一个消息,这个消息同样会在Looper中去处理。其实post方法最终也是通过send方法来完成的。每当Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable的run方法或者Handler的handleMessage方法就会被调用。注意Looper是运行在创建Handler所在的线程中的,这样一来Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了;

⑦Looper的工作原理:Looper在Android的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。注意关注一些重要的Looper的方法:

  • Looper.prepare()-为当前线程创建一个Looper;
  • Looper.loop()-开启消息循环,只有调用该方法,消息循环系统才会开始循环;
  • Looper.prepareMainLooper()-为主线程也就是ActivityThread创建Looper使用;
  • Looper.getMainLooper()-通过该方法可以在任意地方获取到主线程的Looper;
  • Looper.quit() Looper.quitSafely()-退出Looper,自主创建的Looper建议在不使用的时候退出

⑧ActivityThread主线程通过ApplicationThread和AMS进行进程间通信