view.post导致内存泄漏

330 阅读1分钟

问题现象

image.png

问题分析

看GC Root很难看出是谁导致了泄漏,百度后得知该泄漏和View.post相关,所以分析 View.post 源码,分析过程中发现 View.post 源码再 Android 6.0 上下版本实现逻辑不通,下面分开分析

Android 6.0 及以下

  1. 如果 View attach 到 Window,则会将 View.post 的消息直接给 AttachInfo.mHandler 执行
  2. 如果 AttachInfo 为空(即 View detach Window),则将 View.post 的消息给 ViewRootImpl
  3. ViewRootImpl 将消息存储在 ThreadLocal 中的 RunQueue,改对象是持久对象,不会释放
  4. ViewRootImpl 会在 performTraversals 的时候将消息取出后执行,执行后移除消息

所以如果当在 AttachInfo  为空时(View detach Window),使用 View.post 则会导致内存泄漏

//View.java
public boolean post(Runnable action) {
    //当View attach 到 Window 后 AttachInfo 则不为空,否则为空
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
 
    ViewRootImpl.getRunQueue().post(action);
    return true;
}
 
 
//ViewRootImpl.java
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
static RunQueue getRunQueue() {
    RunQueue rq = sRunQueues.get();
    if (rq != null) {
        return rq;
    }
    rq = new RunQueue();
    sRunQueues.set(rq);
    return rq;
}
private void performTraversals() {
    ... ...
    //逐个取出消息执行,真正执行还是将消息传递给了 mAttachInfo.mHandler
    getRunQueue().executeActions(mAttachInfo.mHandler);
    ... ...
}
 
 
//RunQueue.java
void post(Runnable action) {
    postDelayed(action, 0);
}
void postDelayed(Runnable action, long delayMillis) {
    HandlerAction handlerAction = new HandlerAction();
    handlerAction.action = action;
    handlerAction.delay = delayMillis;
    synchronized (mActions) {
        mActions.add(handlerAction);
    }
}
void executeActions(Handler handler) {
    synchronized (mActions) {
        final ArrayList<HandlerAction> actions = mActions;
        final int count = actions.size();
        for (int i = 0; i < count; i++) {
            final HandlerAction handlerAction = actions.get(i);
            handler.postDelayed(handlerAction.action, handlerAction.delay);
        }
        actions.clear();
    }
}

Android 7.0 及以上 在 Android 7.0 之上,官方将 AttachInfo 为空时 post 的消息放到了 HandlerActionQueue ,由于其不是静态的 ThreadLocal,所以很快就会被GC,而不会导致泄露

//View.java
private HandlerActionQueue mRunQueue;
public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
     
    getRunQueue().post(action);
    return true;
}
 
private HandlerActionQueue getRunQueue() {
    if (mRunQueue == null) {
        mRunQueue = new HandlerActionQueue();
    }
    return mRunQueue;
}

解决方案 Runnable设置为静态内部类

private static class SetFocusAbleRunnable implements Runnable{
    private WeakReference<MyRecyclerView> weakReference;
    private SetFocusAbleRunnable(MyRecyclerViewtclRecyclerView){
        weakReference = new WeakReference<MyRecyclerView>(tclRecyclerView);
    }
    @Override
    public void run() {
        MyRecyclerView recyclerView = weakReference.get();
        if (recyclerView !=null){
            recyclerView .setFocusableInRunnable();
        }
    }
}