Android(安卓)你对Handler了解多少?(使用详解篇)
Android 你对Handler了解多少?(使用详解篇)
- 前言
- 什么是Handler
- 为什么要用Handler
- Handler 、Looper、MessageQueue、Message
- 使用举例
- Handler引发的内存泄露
- 总结
前言
Handler一直是面试官必问的面试题,当然,你每次面试之前都温习了相关的知识点,但当你回答Handler相关问题的时候,你又回答的不是很好,这样的局面是不是经常碰到,那咱扪心自问一句,你真的懂Handler吗,对于刚入门的同学,往往都不知道Handler到底是个啥东西,但对于有经验的同学,你也不一定能真正的懂Handler,所以此文对有经验的同学也可以过来看看哟。我们接下来会对Handler做一个详细的讲解,讲解分为:
- Android 你对Handler了解多少?(使用详解篇)
- Android 你对Handler了解多少?(源码解析篇)
- Android 你对Handler了解多少?(深入解析篇)
- Android 手把手教你写Handler (原理运用篇)
- 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会自动帮我们创建,这个知识点到后面会详细讲解到。
- Looper.prepare(): 为当前线程创建Looper对象,说白了就是初始化
- Looper.myLooper():可以获得当前线程的Looper对象
- Looper.prepareMainLooper():为UI线程创建Looper对象
- 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对象而产生的垃圾回收问题。其主要字段用途如下
- what:用户自定义的消息码,让接收者识别消息
- arg1、arg2:轻量级存储int类型的数据
- obj:存储任意对象
- replyTo:线程通信时使用
Handler
主要有一下方法
- void handleMessage(Message msg):处理消息的方法。该方法通常用于被重写
- final boolean hasMessages(int what, Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
- final boolean hasMessages(int what):检查消息队列中是否包含what属性为指定值的消息
- sendEmptyMessage(int what):发送一个空消息
- final boolean sendEmptyMessageDelayed(int what, long delayMillis):发送多少毫秒后发送空消息
- final boolean sendMessage(Message msg):立即发送消息
- final boolean sendMessageDelayed(Message msg, long delayMillis):发送多少毫秒后发送消息
- 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,子线程不能更新吗?后面我们会更深入研究它的源码。
更多相关文章
- Android实时绘制效果(一)
- 通过PhoneGap在Android上去推送通知
- Android(安卓)NDK开发之旅36--FFmpeg音视频同步播放用C实现
- Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
- android 资料
- Android(安卓)4.1 - 如何使用systrace做性能分析
- 记一次趣头条 Android(安卓)面试经历!
- GCM架构概览
- 【Android那些高逼格的写法】LinkedBlockingQueue与ArrayBlockin