android学习笔记----Handler的使用、内存泄漏、源码分析等一系列问题
记录一下自己参考的文章,方便回忆复习。
学习路径,先看慕课网视频:https://www.imooc.com/learn/267
本篇笔记将深入理解Android的Handler机制,并结合源码实例,讲解Looper、MessageqQueue、Handler之间的关系,和Handler的内存泄漏问题和解决办法。
因为是初学者,就先从慕课网上看这个视频,发现视频讲解比较精简,看了一半到分析Handler、Looper,MessageQueue三者之间的关系的时候有点蒙,然后去查看其它资料了,发现资料博客讲的详细,接着综合别人的讲解和自己去翻看源码理解就比较深入了。
目录
深入Android的消息机制源码详解~Handler,MessageQueue与Looper关系
问题代码:Handler和Looper深入思考~~~~为何不报错??
笔记关联: android学习笔记----HandlerThread学习
深入Android的消息机制源码详解~Handler,MessageQueue与Looper关系
https://blog.csdn.net/javazejian/article/details/50791598
备注:这篇文章从源码的角度分析了Handler、MessageQueue、Looper,跟着看了几遍收获很大。就是文章字体有点小,放大即可。
看完后再看一篇Android源码分析-消息队列和Looper
https://blog.csdn.net/singwhatiwanna/article/details/17361775
备注:这篇文章是《Android开发艺术探索》的作者任玉刚的博客中的一篇,适合把握整体大纲。
接着来一篇补充:
https://blog.csdn.net/u011240877/article/details/72892321
备注:这篇文章的亮点在于message源码的讲解过程。
批注:消息队列和Looper的工作机制
一个Handler会有一个Looper,一个Looper会有一个消息队列,Looper的作用就是循环的遍历消息队列,如果有新消息,就把新消息交给它的目标处理。每当我们用Handler来发送消息,消息就会被放入消息队列中,然后Looper就会取出消息发送给它的目标target。一般情况,一个消息的target是发送这个消息的Handler,这么一来,Looper就会把消息交给Handler处理,这个时候Handler的dispatchMessage方法就会被调用,一般情况最终会调用Handler的handleMessage来处理消息,用handleMessage来处理消息是我们常用的方式。
现在到了自问自答环节:
为什么Android设计只能UI线程更新UI?
①android的UI控件不是线程安全的,多线程并发访问可能导致UI控件处于不可预期状态,即UI界面更新混乱。
②如果加上锁机制,缺点有两个,一是加上锁会让UI访问逻辑变得复杂,二是锁机制会降低UI访问效率,锁会阻塞某些线程的执行。
综上,采用单线程模型来处理UI操作,用Handler切换到指定线程执行。
消息队列MessageQueue对象在什么时候创建?
在Looper的构造方法中创建,只要new Looper对象,就会附带new MessgaeQueue对象。
那么Looper对象是在什么时候创建的呢?
调用静态方法Looper.prepare()方法的时候创建的,如下:
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Handler和Looper公用一个消息队列吗?
是的,在Handler的构造方法中,一定会将Handler对象里的引用指向Looper对象里的消息队列对象,所以共用一个消息队列对象。
Handler和Looper关联吗?那他们是怎么关联的?
Handler里面有Looper引用,Handler一定与Looper关联,在Handler构造方法中,mLooper = Looper.myLooper();会从ThreadLocal中get获取Looper对象,此时就将Handler对象与Looper对象相关联。
Looper.loop()是怎么工作的?
loop()方法它里面有一个死循环for(;;),然后这里Message msg = queue.next(); // might block
没有消息的时候会阻塞,一旦有消息就会从消息队列中取出消息。然后会调用msg.target.dispatchMessage(msg);这里target一般是handler对象。
handler.sendMessage方法和handler.post方法在干嘛?
不管是sendMessage方法还是post的一些方法,最终都会执行到sendMessageAtTime方法,在这个方法里,会有消息进队列的操作,而Looper.loop()不断的尝试从消息队列取出消息执行,每取出一条消息,就会尝试执msg.target.dispatchMessage(msg)
总结:用post发送消息时,Handler对象绑定了哪个Looper,那么post参数传入的Runnable对象就在Looper属于的那个线程执行,直接调用的run()而不是start()。用handler.sendMessage方法发送消息时也是看Looper在哪个线程,handleMessage这个回调方法就在哪个线程执行。所以不管是sendMessage和post方法,均要看对应的Handler对象各自绑定的Looper属于哪个线程,对应的方法就在哪个线程执行。
那么dispatchMessage是怎么运行的?
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 这个方法很简单,直接调用msg.callback.run();这个callback就是传进来的Runnable对象
// 比如handler.post(myRunnable);这个myRunnable实现了Runnable接口,run()方法执行想做的操作
handleCallback(msg);
} else {
//如果我们设置了mCallback会由mCallback来处理消息
if (mCallback != null) {
if (mCallback.handleMessage(msg)) { // 如果返回true,拦截,直接return
return;
} // 返回false,不拦截,执行下面的handleMessage(msg)
}
//否则消息就由这里来处理,这是我们最常用的处理方式
handleMessage(msg);
}
}
如果调用的是handler的post方法,那么会调用handleCallback方法,里面会执行message.callback.run();这个callback就是传入进来的Runnable对象,见源码,handler.post是传入Runnable对象,将这个对象封装在一个Message对象里,而这个Message是在handler的getPostMessage方法创建的局部变量,然后Message对象的callback = r(Runnale对象),post开头的方法都会调用sendMessage开头的方法,传入之前都会调用getPostMessage方法。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r; // ===============就是这里
return m;
}
注意,这里执行的是message.callback.run(),是run方法而不是start方法,也就是要在当前调用run()的对象的线程运行,也就是消息队列所在的线程,也就是Looper所在的线程。这里的示例是main线程。
如果调用的是handler的sendMessage系列方法,则需要看创建Handler的时候,构造方法是否有参数,如果有参数并且是Handler的内部接口Callback匿名内部类实现的实例对象,会执行dispatchMessage方法里的mCallback.handleMessage(msg)。
而这个mCallback就是这里的匿名对象new Handler.Callback(){...}
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(MainActivity.this, "拦截了吗", Toast.LENGTH_SHORT).show();
return true;
}
}) {
@Override
public void handleMessage(Message msg) {
// textView.setText(msg.arg1 + "---" + msg.arg2);
Toast.makeText(MainActivity.this, "没拦截到,我出来显示", Toast.LENGTH_SHORT).show();
//textView.setText(msg.obj.toString());
}
};
那么需要重写的public boolean handleMessage方法,如果返回ture,就会拦截底下的public void handleMessage方法。返回false不拦截。当然如果不传这个接口的实现对象和false一样,不拦截,因为mCallback是null,进不了判断条件。
消息message创建的时候推荐用obtain,为什么?
message就说平时写单链表里面node结点,只不过这个结点里面有了更多的属性,Message对象的内部实现是链表,最大长度是50(private static final int MAX_POOL_SIZE = 50;)用于缓存消息对象,达到重复利用消息对象的目的,以减少消息对象的创建,所以通常我们要使用handler.obtainMessage方法(最终会调用Message里面的obtain()方法)来获取消息对象。
message.obtain目前有8个重载方法,我们选取其中一个查看,如下
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag,消息入队的时候会判断这个标志位
//如果没清空,则throw new IllegalStateException(msg + " This message is already in use.");
sPoolSize--;
return m;
}
}
return new Message();
}
如果这个Message的引用sPool不为null,就复用原来的message对象,否则new一个Message()对象,这个sPool在消息回收的时候会被赋值,先来看看消息回收
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE; // 标记消息已使用
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this; // 将sPool赋值对这个消息对象
sPoolSize++;
}
}
}
可以看到recycleUnchecked方法将当前 Message 标志为 FLAG_IN_USE,这样如果这个方法在被入队,就会报错。此外它还清除了其他数据,然后把这个消息加入了回收消息的链表中。
这个recycleUnchecked方法在MessageQueue.removeMessages()和Looper.loop()方法中都会有调用。
一个消息在被 Looper 处理时或者移出队列时会被标识为 FLAG_IN_USE
,然后会被加入回收的消息链表,这样我们调用 Message.obtain()
方法时就可以从回收的消息池中获取一个旧的消息,从而节约成本。
自定义与线程相关的Handler代码示例:
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
public class SecondActivity extends AppCompatActivity {
private static final String TAG = "SecondActivity";
public Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d(TAG, "======主线程handleMessage: " + Thread.currentThread().getName());
}
};
static class myThread extends Thread {
public Handler handler;
@Override
public void run() {
Looper.prepare();
handler = new Handler() { // 必须先有looper对象,否则在handler构造方法报错
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d(TAG, "=======子线程handleMessage: " + Thread.currentThread().getName());
}
};
Looper.loop();
}
}
private myThread myThread;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView就省略了,这里不分析布局
myThread myThread = new myThread();
myThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里睡眠为了保证handler实例化
myThread.handler.sendEmptyMessage(1);
// 睡眠后看main线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(2);
}
}
运行结果:
问题代码:Handler和Looper深入思考~~~~为何不报错??
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
public class SecondActivity extends AppCompatActivity {
private MyThread myThread;
// 实例化mHandler的时候就绑定main线程的Looper,所以mHandler是主线程的Handler对象
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.i("main", "======UI.Thread: " + Thread.currentThread());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myThread = new MyThread();
myThread.start();
mHandler.sendEmptyMessage(0);
myThread.mHandler.sendEmptyMessage(0);
}
/**
* Create MyThread
*/
class MyThread extends Thread {
// 因为子线程的Looper.prepare还没开始,此时ThreadLocal.get出来的还是main线程的Looper对象
// 所以MyThread里的mHandler绑定了main线程的Looper,mHandler相当于还是属于主线程。
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.i("main", "======Callback MyThread,currentThread: " + Thread.currentThread());
return false;
}
}) {
@Override
public void handleMessage(Message msg) {
Log.i("main", "======MyThread.currentThread: " + Thread.currentThread());
}
};
@Override
public void run() {
// Create looper
Looper.prepare();
Log.i("main", "======Thread:" + Thread.currentThread());
Looper.loop();
}
}
}
运行结果:
这里为什么会是main线程,奇怪了,我明明是在子线程实例化handler的,run方法还没开始执行,子线程的Looper对象还没被创建,那么handler在构造方法中怎么不报错呢?注意了,这个handler其实仍然属于主线程。
那也应该会执行
throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");
别忘了这个可是有条件的。
mLooper = Looper.myLooper();
if (mLooper == null) { .. 抛异常}
这个Looper.myLooper()方法的执行尝试返回Looper对象return sThreadLocal.get();
而在主线程创建的时候就已经Looper.prepare了,执行了sThreadLocal.set(new Looper(quitAllowed));
所以这个sThreadLocal里面是有main线程的Looper在里面的,这里其实就是将这里的mHandler和主线程的Looper绑定了。mHandler里面的消息队列引用也指向了主线程的消息队列。mQueue = mLooper.mQueue;
所以这里即使子线程还没初始化Looper对象也不报错,打印log就是main线程。(Handler对象绑定的Looper对象属于哪个线程,Handler对象就属于哪个线程,执行的一系列操作比如回调方法handleMessage方法就是在对应的线程)
Handler内存泄漏详解及其解决方案
https://blog.csdn.net/javazejian/article/details/50839443
备注:阅读这篇文章时,让自己对内部类有了新的认识,扩展文章如下:
非静态内部类、非静态匿名内部类会持有外部对象的引用:https://blog.csdn.net/moon_nife/article/details/54975983
非静态内部类持有外部类的引用 使用不慎会造成内存溢出:https://blog.csdn.net/mafei852213034/article/details/78262490
java 内部类(inner class)详解:https://blog.csdn.net/suifeng3051/article/details/51791812
批注:
Handler内存泄漏解决方案:
将Handler声明为静态内部类
静态类不持有外部类的对象,所以Activity可以随意被回收
public class MainActivity extends AppCompatActivity {
......
static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// 错误示范
Toast.makeText(MainActivity.this, "我出现了", Toast.LENGTH_SHORT).show();
}
}
......
}
但是没那么简单,静态类已经不持有外部类对象了,所以MainActivity.this会报语法错误,那怎么办呢?所以需要在Handler中增加一个对Activity的弱引用(WeakReference):
static class MyHandler extends Handler {
WeakReference<Activity> weakReference;
public MyHandler(Activity activity) {
weakReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Toast.makeText(weakReference.get(), "我出现了", Toast.LENGTH_SHORT).show();
}
}
private MyHandler myHandler = new MyHandler(this);
这样基本就完成了,但是当Activity finish后 handler对象还是在Message中排队。 还是会处理消息,这些处理有必要? 正常Activitiy finish后,已经没有必要对消息处理,那需要怎么做呢? 解决方案也很简单,在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable。 通过查看Handler的API,它有几个方法:removeCallbacks(Runnable r)和removeMessages(int what)等。
比如在Handler把消息处理完了后,但是页面销毁了,这个时候可能Handler会更新UI,但是比如TextView、ImageView之类的资源引用不见了,就会抛出异常。处理如下:
// Remove any pending posts of callbacks and sent messages whose obj is token.
// If token is null, all callbacks and messages will be removed.
@Override
protected void onDestroy() {
super.onDestroy();
myHandler.removeCallbacksAndMessages(null);
}
这样就可以了,总结3步
①改为handler静态内部类
②加上弱引用
③处理onDestroy()时removeCallbacksAndMessages(null)
某Demo部分测试代码:
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.lang.ref.WeakReference;
class Person {
public String name;
public int age;
@Override
public String toString() {
return "name: " + name + "age: " + age;
}
}
public class MainActivity extends AppCompatActivity {
int[] images = new int[]{R.drawable.image1, R.drawable.image2, R.drawable.image3};
int index;
private TextView textView;
private static final String TAG = "MainActivity";
static class MyHandler extends Handler {
WeakReference<Activity> weakReference;
public MyHandler(Activity activity) {
super(new Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d(TAG, "====================拦截了");
return false; // 返回true就拦截回调的handleMessgae,false不拦截,或者不设置Callback对象也不拦截
}
});
weakReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Toast.makeText(weakReference.get(), "没拦截到,我出来显示", Toast.LENGTH_SHORT).show();
}
}
private MyHandler myHandler = new MyHandler(this);
@Override
protected void onDestroy() {
super.onDestroy();
myHandler.removeCallbacksAndMessages(null);
}
/*private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(MainActivity.this, "拦截了吗", Toast.LENGTH_SHORT).show();
return true;
}
}) {
@Override
public void handleMessage(Message msg) {
// textView.setText(msg.arg1 + "---" + msg.arg2);
Toast.makeText(MainActivity.this, "没拦截到,我出来显示", Toast.LENGTH_SHORT).show();
//textView.setText(msg.obj.toString());
}
};*/
private ImageView imageView;
private MyRunnable myRunnable = new MyRunnable();
public void click(View view) {
//myHandler.removeCallbacks(myRunnable);
myHandler.sendEmptyMessage(1);
}
class MyRunnable implements Runnable {
@Override
public void run() {
++index;
index %= 3;
imageView.setImageResource(images[index]);
// 可以看到,post方法调用还是在主线程执行
Log.d(TAG, "============run: " + Thread.currentThread().getName());
myHandler.postDelayed(myRunnable, 1000);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.imageView);
textView = (TextView) findViewById(R.id.textview);
myHandler.postDelayed(myRunnable, 1000);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = myHandler.obtainMessage();
message.arg1 = 88;
message.arg2 = 100;
Person p = new Person();
p.age = 23;
p.name = "lcy";
message.obj = p;
// handler.sendMessage(message);
message.sendToTarget(); // 使用sendToTarget这个message必须handler.obtainMessage()获得,见源码
}
}).start();
}
}
扩展知识阅读:关于强引用、软引用、弱引用、虚引用的概念
WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。
关于强引用、软引用、弱引用、虚引用的概念参考下面文章即可
https://www.cnblogs.com/dolphin0520/p/3784171.html
关于内存泄漏注意事项参见如下:
https://www.jianshu.com/p/ec0f67e867c3
========================Talk is cheap, show me the code=======================