Android 你对Handler了解多少?(使用详解篇)

    • 前言
    • 什么是Handler
    • 为什么要用Handler
    • Handler 、Looper、MessageQueue、Message
    • 使用举例
    • Handler引发的内存泄露
    • 总结

前言

Handler一直是面试官必问的面试题,当然,你每次面试之前都温习了相关的知识点,但当你回答Handler相关问题的时候,你又回答的不是很好,这样的局面是不是经常碰到,那咱扪心自问一句,你真的懂Handler吗,对于刚入门的同学,往往都不知道Handler到底是个啥东西,但对于有经验的同学,你也不一定能真正的懂Handler,所以此文对有经验的同学也可以过来看看哟。我们接下来会对Handler做一个详细的讲解,讲解分为:

  1. Android 你对Handler了解多少?(使用详解篇)
  2. Android 你对Handler了解多少?(源码解析篇)
  3. Android 你对Handler了解多少?(深入解析篇)
  4. Android 手把手教你写Handler (原理运用篇)
  5. Android 你对Handler了解多少?(面试篇)

什么是Handler

Handler是一个消息分发对象。Handler是Android给我们提供用来更新UI的一套机制,也是一套消息处理机制,我们可以发消息,也可以通过它处理消息,其最大的作用就是线程切换。

为什么要用Handler

在Android开发中多线程的应用中,将工作线程中需更新UI的操作信息传递到UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理,最根本的目的就是为了解决多线程并发的问题,如果在一个Activity中有多个线程,并且没有加锁,就会出现界面错乱的问题。但是如果对这些更新UI的操作都加锁处理,又会导致性能下降。处于对性能的问题考虑,Android给我们提供这一套更新UI的机制我们只需要遵循这种机制就行了。不用再去关系多线程的问题,所有的更新UI的操作,都是在主线程的消息队列中去轮训的

Handler 、Looper、MessageQueue、Message

讲到Handler,都离不开Looper、MessageQueue、Message这三者和Handler之间的关系,面试过程中也经常问到这三者之间的关系,那么这三这于Handler之间到底有什么关系?

Looper
也称之为:消息泵,是MessageQueue的管理者,会不断从MessageQueue中取出消息,并将消息分给对应的Handler处理。每一个线程只有一个Looper,每个线程在初始化Looper之后,然后Looper会维护好该线程的消息队列,用来存放Handler发送的Message,并处理消息队列出队的Message。它的特点是它跟它的线程是绑定的,处理消息也是在Looper所在的线程去处理,所以当我们在主线程创建Handler时,它就会跟主线程唯一的Looper绑定,从而我们使用Handler在子线程发消息时,最终也是在主线程处理,达到了异步的效果。

那么有人会问,我创建Handler的时候从来都没创建Looper呀,这是因为在主线程中,ActivityThread会自动帮我们创建,这个知识点到后面会详细讲解到。

  1. Looper.prepare(): 为当前线程创建Looper对象,说白了就是初始化
  2. Looper.myLooper():可以获得当前线程的Looper对象
  3. Looper.prepareMainLooper():为UI线程创建Looper对象
  4. Looper.getMainLooper():// 获得UI线程的Lopper对象

MessageQueue
也称之为:消息队列,其内部接口就是一个链表结构,存储的是Message,每个线程最多只有一个MessageQueue,MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue

Message
Handler接收和处理的消息对象,就是MessageQueue里面存放的对象,一个MessageQueu可以包括多个Message。当我们需要发送一个Message时,我们一般不建议使用new Message()的形式来创建,更推荐使用Message.obtain()来获取Message实例,因为在Message类里面定义了一个消息池,当消息池里存在未使用的消息时,便返回,如果没有未使用的消息,则通过new的方式创建返回,所以使用Message.obtain()的方式来获取实例可以大大减少当有大量Message对象而产生的垃圾回收问题。其主要字段用途如下

  1. what:用户自定义的消息码,让接收者识别消息
  2. arg1、arg2:轻量级存储int类型的数据
  3. obj:存储任意对象
  4. replyTo:线程通信时使用

Handler
主要有一下方法

  1. void handleMessage(Message msg):处理消息的方法。该方法通常用于被重写
  2. final boolean hasMessages(int what, Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
  3. final boolean hasMessages(int what):检查消息队列中是否包含what属性为指定值的消息
  4. sendEmptyMessage(int what):发送一个空消息
  5. final boolean sendEmptyMessageDelayed(int what, long delayMillis):发送多少毫秒后发送空消息
  6. final boolean sendMessage(Message msg):立即发送消息
  7. final boolean sendMessageDelayed(Message msg, long delayMillis):发送多少毫秒后发送消息
  8. final void removeCallbacks(Runnable r):将MessageQueue中的Runnable对象移除

使用举例

从子线程发送消息到主线程

  public class MainActivity extends AppCompatActivity {    TextView btn;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn=findViewById(R.id.btn);        new Thread(new Runnable() {           @Override           public void run() {               //在子线程发送一个消息,不建议new Message() 建议用Message.obtain()               Message msg=new Message();               handler.sendMessage(msg);           }       }).start();    }    //在主线程创建Handler对象,重写handleMessage方法,这个方法就是接收并处理消息的方法    @SuppressLint("HandlerLeak")    private Handler handler=new Handler(){        @Override        public void handleMessage(@NonNull Message msg) {            super.handleMessage(msg);            //msg就是子线程发送过来的消息        }    };}

发送一个空消息

 handler.sendEmptyMessage(what);

同一消息不能发送两次

 Message msg=new Message(); handler.sendMessage(msg); handler.sendMessage(msg);

发送一个带有obj属性的消息

public class MainActivity extends AppCompatActivity {    TextView btn;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn=findViewById(R.id.btn);        new Thread(new Runnable() {           @Override           public void run() {                             Message msg=new Message();               msg.obj="我是消息";               handler.sendMessage(msg);           }       }).start();    }      @SuppressLint("HandlerLeak")    private Handler handler=new Handler(){        @Override        public void handleMessage(@NonNull Message msg) {            super.handleMessage(msg);            String str= (String) msg.obj;            Toast.makeText(MainActivity.this,str,1000).show();        }    };}

发送一个带有what标识的消息

 public class MainActivity extends AppCompatActivity {    TextView btn;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn=findViewById(R.id.btn);        new Thread(new Runnable() {           @Override           public void run() {                     Message msg1=new Message();               msg1.obj="我是消息1";               msg1.what=1;               handler.sendMessage(msg1);               Message msg2=new Message();               msg2.obj="我是消息2";               msg2.what=2;               handler.sendMessage(msg2);           }       }).start();    }      @SuppressLint("HandlerLeak")    private Handler handler=new Handler(){        @Override        public void handleMessage(@NonNull Message msg) {            super.handleMessage(msg);            switch (msg.what){                case 1:                    String str1= (String) msg.obj;                    Toast.makeText(MainActivity.this,"第一个消息是"+str1,1000).show();                    break;                case 2:                    String str2= (String) msg.obj;                    Toast.makeText(MainActivity.this,"第二个消息是"+str2,1000).show();                    break;            }        }    };}

Handler还可以发送post一个Runnable

public class MainActivity extends AppCompatActivity {    TextView btn;    private Handler mHandler = new Handler();    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn=findViewById(R.id.btn);                new Thread(new Runnable() {            @Override            public void run() {                //在子线程post一个Runnable对象                Log.e("TAG",Thread.currentThread().getName());                mHandler.post(new Runnable() {                    @Override                    public void run() {                        //这里运行在主线程,可能有人很好奇为什么运行在主线程,我这不是在子线程中吗?                        //当你使用Handler的时候,已经把线程切换过来了,Handler的线程是默认创建的                        Log.e("TAG",Thread.currentThread().getName());                        btn.setText("已点击");                                            }                });            }        }).start();    }}

以上可以看出,Handler发送消息有两种方式,一个是直接Message,一个是通过post一个Runnable,那么这两个有什么区别呢,我们来稍微看一下源码

public final boolean post(@NonNull Runnable r) {       return  sendMessageDelayed(getPostMessage(r), 0);    }
public final boolean sendMessage(@NonNull Message msg) {        return sendMessageDelayed(msg, 0);    }

可以看出两个方法都是通过调用sendMessageDelayed(),post方法的底层调用sendMessageDelayed的时候,却是通过getPostMessage®来将Runnable对象来转为Message,我们再来看看getPostMessage®里面是怎么实现的

 private static Message getPostMessage(Runnable r) {        Message m = Message.obtain();        m.callback = r;        return m;    }

然后,looper进行消息循环,进行消息的分发,

 public void dispatchMessage(Message msg) {        if (msg.callback != null) {            handleCallback(msg);        } else {            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }

如果回调不为空,handleCallback(msg) 找个方法就会执行,

 private static void handleCallback(Message message) {        message.callback.run();    }

最终Runnable也是转化为一个Message,而这个Message只被一个变量赋值,就是Runnable的回调函数,Run方法里面的操作。

那么问题又来了,Goole工程师为什么要设计这两种方式?
我的总结是这样的,如果需要进行很多数据来传输,我们可以使用sendMessage,因为Meaage可以赋值很多丰富的对象,如果我们只需要进行某一个动作,可以用post Runnable,在run方法里面实现即可

Handler引发的内存泄露

在Android中Handler引发的内存泄露是非常常见的,也是面试的时候会问到的,那么为什么会泄露呢?在Android中非静态内部内持有外部类的引用会导致泄露,这句话什么意思呢?就是说在Handler处理消息的时候,Handler必须处理完所有消息才会与外部类解除引用关系,如果此时外部Activity需要提前被销毁了,而Handler因还未完成消息处理而继续持有外部Activity的引用。由于上述引用关系,垃圾回收器(GC)便无法回收Activity,从而造成内存泄漏。
下面就是解决方案,静态内部类+弱引用

public class MainActivity extends AppCompatActivity {   private TextView mTv;   private MyHandler myHandler;   @Override   protected void onCreate(Bundle savedInstanceState) {       super.onCreate(savedInstanceState);       setContentView(R.layout.activity_main);       myHandler = new MyHandler(this);   }   //这样的写法会造成内存泄漏   //mHandler是MainActivity的非静态内部类的实例,它持有外部类的引用,我们知道handler的消息是在一个loop   //中不断的轮询处理消息,那么当MainActivity退出时,消息队列中还有没处理的消息或正在处理的消息,所以会造成内存泄漏  @SuppressLint("HandlerLeak")  private Handler mHandler = new Handler(){      @Override      public void handleMessage(Message msg) {          super.handleMessage(msg);      }  };   //这样写是正确的写法   static class MyHandler extends Handler{       //创建一个软引用       private WeakReference reference;       public MyHandler(Context context){           reference = new WeakReference(context);       }       @Override       public void handleMessage(Message msg) {           MainActivity mainActivity = (MainActivity) reference.get();           if(mainActivity != null){               //TODO------               mainActivity.mTv.setText("11");           }       }   }  @Override  protected void onDestroy() {     super.onDestroy();     myHandler.removeCallbacksAndMessages(null);  }   }

总结

Handler是主要用于主线程更新UI,在Android中也是一套非常重要的UI更新机制,当然如果处理不当就会造成内存泄露,里面其实还设及到了很多知识点,就比如为什么要在主线程更新UI,子线程不能更新吗?后面我们会更深入研究它的源码。

更多相关文章

  1. Android实时绘制效果(一)
  2. 通过PhoneGap在Android上去推送通知
  3. Android(安卓)NDK开发之旅36--FFmpeg音视频同步播放用C实现
  4. Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
  5. android 资料
  6. Android(安卓)4.1 - 如何使用systrace做性能分析
  7. 记一次趣头条 Android(安卓)面试经历!
  8. GCM架构概览
  9. 【Android那些高逼格的写法】LinkedBlockingQueue与ArrayBlockin

随机推荐

  1. Android(安卓)AppWidget一个简单教程
  2. Inflate()---Android之Inflate()方法用途
  3. Android动态加载相关文章记录
  4. Android Bitmap转换
  5. Android(安卓)情景模式设置
  6. android 远程下载桌面背景图案——URLCon
  7. Android获得本应用本次开机后的流量
  8. Android(安卓)给应用定制皮肤
  9. android进度条修改教程(颜色,高度)
  10. 安卓开发实例 Android开发实例