Android AsyncInflater的源码解析及内存泄漏分析

先来看看AsyncInflater的基本使用。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new AsyncLayoutInflater(this).inflate(R.layout.activity_test, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
            @Override
            public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
                setContentView(view);
                Log.d(TAG, "onInflateFinished:");
            }
        });
        Log.d(TAG, "onCreate: ");
    }

这样就可以异步地加载ContentView了。内部是如何实现的呢?先来看看AsyncInflater的构造方法AsyncLayoutInflater。

public AsyncLayoutInflater(@NonNull Context context) {
		//1
        mInflater = new BasicInflater(context);
        //2
        mHandler = new Handler(mHandlerCallback);
        //3
        mInflateThread = InflateThread.getInstance();
}

初始化操作只有三行代码,先来看看BasicInflater。

private static class BasicInflater extends LayoutInflater {
	...
}

BasicInflater 继承自LayoutInflater,我们知道LayoutInflater可以根据R.layout.xxx来创造一个ContentView,BasicInflater也是。

再来看看代码2的Callback做了什么。

private Callback mHandlerCallback = new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
        	//A
            InflateRequest request = (InflateRequest) msg.obj;
			if (request.view == null) {
                request.view = mInflater.inflate(
                        request.resid, request.parent, false);
            }
            //B
            request.callback.onInflateFinished(
                    request.view, request.resid, request.parent);
            //C
            mInflateThread.releaseRequest(request);
            return true;
        }
};

子线程完成了耗时的布局创建后发送一个消息给主线程Handler进行回调,更新UI。注意,这里强调是在主线程回调。mHandler = new Handler(mHandlerCallback) 这样的方式获取的Handler会首先在调用它的线程里查找只属于该线程的Looper(也就是线程局部变量)。如果我们在子线程里new AsyncLayoutInflater,会出现两种结果。
1.该子线程无法提供Looper,直接报错。
2.该子线程可以提供Looper(例如HandlerThread),导致UI任务在子线程上调用,同样会报错。

对Handler和线程问题还不是很了解的朋友可以看看我写的这篇文章,非常建议大家理解Handler的内部原理,很多Android的库的用到了Handler,要知道Handler可是内存泄漏的常客,这些库对Handler使用非常熟练,理解了Handler的内部原理对这些库的源码阅读是很有帮助的。

回到代码,从代码A可以看出来InflateRequest被装入了Message中,来看看它的代码。

private static class InflateRequest {
        AsyncLayoutInflater inflater;
        ViewGroup parent;
        int resid;
        View view;
        OnInflateFinishedListener callback;

        InflateRequest() {
        }
}

这个类没有什么特别的功能,就是用来传递子线程Inflate好的ContentView及更新界面的回调。

主线程收到消息就在代码B更新界面。代码C进行释放操作,这是很有必要的,等等会分析。整个mHandlerCallback就做了这些事情,至此mHandler初始化也就完成了。

最后在代码3获取InflateThread 就行了。

//3
mInflateThread = InflateThread.getInstance();

mInflateThread 得到的是一个单例。Activity、View、Fragment等用到单例时要有内存泄漏的警觉。InflateThread这个类继承自Thread,它就是用于处理耗时操作的子线程,处理完后通过Handler回到主线程更新界面。

先看看这个线程在做什么。

@Override
public void run() {
	   //1
       while (true) {
           runInner();
       }
}

public void runInner() {
            InflateRequest request;
            try {
                request = mQueue.take();
            } catch (InterruptedException ex) {
                // Odd, just continue
                Log.w(TAG, ex);
                return;
            }

            try {
            	//2
                request.view = request.inflater.mInflater.inflate(
                        request.resid, request.parent, false);
            } catch (RuntimeException ex) {
                // Probably a Looper failure, retry on the UI thread
                Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"
                        + " thread", ex);
            }
            //3
            Message.obtain(request.inflater.mHandler, 0, request)
                    .sendToTarget();
        }

其实这个线程的任务很简单,就是把ContentView创造出来(代码2),然后更新界面(代码3)。因为可能有多个Activity要异步加载,所以这个子线程会在死循环里(代码1),不断等待新的加载请求。

再来看看obtainRequest方法的代码。

private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);

public InflateRequest obtainRequest() {
     InflateRequest obj = mRequestPool.acquire();
     if (obj == null) {
         obj = new InflateRequest();
     }
     return obj;
}

这个方法和Message.obtain()非常相似。一个InflateRequest的会经历这么几个过程。
调用obtainRequest方法从请求池里取出一个InflateRequest,接着等子线程完成了耗时任务后利用这个InflateRequest装载ContentView,然后InflateRequest被发送至主线程,
最后回收。

现在来分析刚刚提到的回收操作及它的必要性,来看看源码。

public void releaseRequest(InflateRequest obj) {
            obj.callback = null;
            obj.inflater = null;
            obj.parent = null;
            obj.resid = 0;
            //1
            obj.view = null;
            mRequestPool.release(obj);
}

操作很简单,就是各种置空然后把这个InflateRequest 回收到mRequestPool再利用。代码1的obj.view就是指Activity的ContentView。如果这里不置空会有什么后果呢?来看看下面这张图。
在这里插入图片描述

private static final InflateThread sInstance;

mInflateThread指向的是一个单例对象,这个单例是一个静态变量。一个静态变量间接地持有了Activity,即便Activity销毁了也无法被回收,这就导致内存泄漏了。

最后再来看看AsyncInflater的inflate方法,调用这个方法就可以实现异步加载布局了。

    @UiThread
    public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
        @NonNull OnInflateFinishedListener callback) {
        if (callback == null) {
            throw new NullPointerException("callback argument may not be null!");
        }
        //1
        InflateRequest request = mInflateThread.obtainRequest();
        request.inflater = this;
        request.resid = resid;
        request.parent = parent;
        request.callback = callback;
        //2
        mInflateThread.enqueue(request);
    }

代码1通过obtainRequest方法从请求池中获取一个空的InflateRequest,装入参数后放入请求队列中等待处理(代码2)。说到串行处理任务请求,Handler是非常好的选择。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值