1. 为什么在activity中直接new一个handler的匿名内部类会有可能发生内存泄漏
scala
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
System.out.println("zz");
}
};
}
}
首先明确一点,在一个方法内部new一个匿名内部类的时候,这个匿名内部类会持有外部类的引用。 因此这个handler会持有activity这个对象引用。handler是被message所有的,可以看如下源码
ini
public static Message obtain(Handler h, int what, int arg1, int arg2) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
return m;
}
在这个源码中可以看出,这个message在获得的时候应该是和要处理的handler绑定的。也就是说message中已经知道了谁要去处理这个message。或者这样说更具体一点,在发送端发送这个message的时候呢,要进入共享的消息队列,这些都后面再详细说。
less
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
因此可以看出这个message会和发送的这个handler进行绑定,相当于这一个handler要在主线程中进行处理,同时在子线程中作为一个成员变量进行发送!总之可以明确这个handler是和message绑定的。 同时可以看出,message是和messageQueue进行绑定的,message是messageQueue的一个属性。接下来看这个messageQueue,在looper这个类内部有一个messageQueue这个属性
ini
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在代码中可以看出,这个mQueue在loope的构造方法中被创建。
csharp
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));
}
在后面的代码可以看出,这个looper被作为一个对象放到了sThreadLocal属性内部。同时看出这个sThreadLocal是一个静态变量。因此找到了GCRoot.因此,当这个message长期得不到处理的时候,就会发生内存泄漏。
2. 一个looper存在多个handler,如何保证线程安全的。
这个问题可以看一下handler整体的处理流程,在handler进行sendMessage操作的时候,会将这个messsage放入到messagequeue中
ini
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
以上仅仅是复制了部分源码,可以看出在代码中引入了synchroized锁,目的是保证在message进入这个共享队列的时候的线程安全性。 在取出消息的时候呢?
java
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
可以看上述代码,同时在取出的操作中也加入了synchroized锁。因此,这也是线程安全的。目的是不希望,插入队列和取出消息这两个操作产生冲突。
3.handler整个消息体制的一个运行过程
首先看最初始的源码,ActivityThread.java中的main方法,
ini
Looper.prepareMainLooper();
准备好looper,并创建好消息队列
ini
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
之后
ini
Looper.loop();
在这个方法内部进入一个for(;;)循环开始不断尝试进行取消息。因此,可以在主线程new一个handler,重写这个handlemessage方法。同时把这个handler放入到子线程中sendmessage进行跨线程通信。 同时应该明确的是看如下代码
less
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到同一个looper共享同一个messageQueue.而looper是和当前线程进行绑定的。看如下代码
ini
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
因此只要将一个handler仍到���同的线程里,就可以实现多线程之间的通信。
3.为什么new HandlerThread()默认是和主线程进行通信。看源码!
ini
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
csharp
public static void prepare() {
prepare(true);
}
csharp
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));
}
ini
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
关键之处就是在于looper会个当前的thread通过sThreadLocal进行绑定。
4.Message如何获取,handler是如何利用handler的。 在next()获取到消息后,
ini
msg.recycleUnchecked();
会执行以上方法。在这个方法内部只要是将message的所有属性都清空。
ini
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 = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
同时将清空后的message加入到缓存队列sPool中。 当取消息的时候呢?
ini
public static Message obtain(Message orig) {
Message m = obtain();
m.what = orig.what;
m.arg1 = orig.arg1;
m.arg2 = orig.arg2;
m.obj = orig.obj;
m.replyTo = orig.replyTo;
m.sendingUid = orig.sendingUid;
m.workSourceUid = orig.workSourceUid;
if (orig.data != null) {
m.data = new Bundle(orig.data);
}
m.target = orig.target;
m.callback = orig.callback;
return m;
}
可以看出,并没有真的new一个message出来,而是将orig这个message 的信息赋给缓存队列中的一个空内容的mssage里面。这利用到了享元模式的概念。不频繁的创建空间,避免OOM的产生。