问题现象
问题分析
看GC Root很难看出是谁导致了泄漏,百度后得知该泄漏和View.post相关,所以分析 View.post 源码,分析过程中发现 View.post 源码再 Android 6.0 上下版本实现逻辑不通,下面分开分析
Android 6.0 及以下
- 如果 View attach 到 Window,则会将 View.post 的消息直接给 AttachInfo.mHandler 执行
- 如果 AttachInfo 为空(即 View detach Window),则将 View.post 的消息给 ViewRootImpl
- ViewRootImpl 将消息存储在 ThreadLocal 中的 RunQueue,改对象是持久对象,不会释放
- 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();
}
}
}