性能优化:AsyncLayoutInflater异步加载布局源码分析 | 七日打卡

3,122 阅读4分钟

前言

如果你布局加载很慢,或者有一些自己想法。那么这个AsyncLayoutInflater异步加载布局你值得拥有。

Api 用法

用法简单,就传递一个context,一个布局id ,一个 回调,接着在回调里面设置view,就好。具体用法如下:

class AsyncLayoutInflaterActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //异步加载布局
        AsyncLayoutInflater(this).inflate(R.layout.activity_async_layout_inflater,null)
        { view: View, i: Int, viewGroup: ViewGroup? ->
             //异步加载布局
            setContentView(view)
        }
    }
}

从入口开始分析源码

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

值得重点关注是这是这两行代码

AsyncLayoutInflater.InflateRequest request = mInflateThread.obtainRequest();

mInflateThread.enqueue(request);

让我们一句一句来解析吧!

AsyncLayoutInflater.InflateRequest request = mInflateThread.obtainRequest();

在里主要是从对象缓存池里面取出一个InflateRequest对象,如果对象缓存池对象为空,就去新建一个。对象缓存可以减少创建对象的消耗。 对象缓存池很多地方都用到,比比皆是。主要是供后续的应用程序重复使用,从而减少创建对象和释放对象的次数,进而改善应用程序的性能。

mInflateThread.enqueue(request);

这里是启动一个线程,异步死循环去取布局解析任务,没有就阻塞

我们接着走,去到核心布局解析任务的地方,runInner()这里会被死循环执行,request = mQueue.take();去阻塞队列里面取出一个任务, 接着会交给LayoutInflater.inflate去执行,生成一个View.这里有一个小细节,等会说。生成view之后,这里会发一个handle消息,通知一下。

 public void runInner() {
            AsyncLayoutInflater.InflateRequest request;
            try {
                request = mQueue.take();//取任务
            } catch (InterruptedException ex) {
                return;
            }
            try {
                //生成view
                request.view = request.inflater.mInflater.inflate(request.resid, request.parent, false);
            } catch (RuntimeException ex) {
            }
            //发一个handle消息
            Message.obtain(request.inflater.mHandler, 0, request).sendToTarget();
        }

继续让我们看看 Handle Callback 的代码。

来让我们分析一下,这里有一个小小的容错机制,如果view为空,会调用去inflate生成一个view。接着回调入口的OnInflateFinishedListener, 接着调用 mInflateThread.releaseRequest(request);释放对象到缓存池中,完成整个流程。

但要注意的一点是,这是不是子线程了,是UI线程。

到这里,我们就到尾声了。

   private Callback mHandlerCallback = new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            AsyncLayoutInflater.InflateRequest request = (AsyncLayoutInflater.InflateRequest) msg.obj;
            //如果view为空,会调用去inflate生成一个view
            if (request.view == null) {
                request.view = mInflater.inflate(
                        request.resid, request.parent, false);
            }
            request.callback.onInflateFinished(
                    request.view, request.resid, request.parent);
            mInflateThread.releaseRequest(request);
            return true;
        }
    };

让我看看整体的流程图

小细节分析

我们说说上面那个生成view的小细节, BasicInflater 继承于LayoutInflater,异步解析xml时候,原生的布局会回调到这里。这里要回溯到LayoutInflater源码的方法createViewFromTag里面是这样判断的。如下

     if (-1 == name.indexOf('.')) {
      //原生布局
      view = onCreateView(context, parent, name, attrs);
      } else {
          //自定义
          view = createView(context, name, null, attrs);
        }

接着我们看看BasicInflateronCreateView,核心代码。这里我闻到了狸猫换太子味道,我们可以直接在前面添加自己的代码。去替换成我们的view,换皮肤功能岂不是实现了呀。

  @Override
 protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
            for (String prefix : sClassPrefixList) {
               try {
                       // 优先去"android.widget.",android.webkit.",android.app.查找
                       // 因为大部分布局都在这里面 比如 button edittext 。android.widget.Textview
                        //但为什么这里不用准确命中呢?比如我们知道TextView 是在android.widget下的。
                        View view = createView(name, prefix, attrs);
                         if (view != null) {
                             return view;
                           }
                       } catch (ClassNotFoundException e) {
                       }
                   }
            //布局内视图不在以上三个包内,则调用父类方法构建
            return super.onCreateView(name, attrs);
        }
    }

比如在前面加上下面代码,岂不是可以替换自己的view了。

  if(name.equals("TextView")){
    return createView("MyTextView","com.diy.view.", attrs);
   }

onCreateView中会优先去"android.widget.",android.webkit.",android.app.查找, 因为大部分布局都在这里面。 比如 button edittext 。android.widget.Textview,命中了就返回view。

但为什么这里不用准确命中呢?比如我们知道TextView 是在android.widget下的。有大佬解惑一下?

对于文中对象缓存池可以看我这篇文章

Android 布局异步加载中的pools对象缓存池源码分析

文中提到阻塞,也可以从我这篇文中了解一下。为啥阻塞不占有资源,可以直接滑到中间观看,中间有对这个进行解释。

调用线程的start(),线程真的会马上执行吗?

面试官:让我手写一个Handle

总结

一天一篇文章,写着写着就已经不知道自己要写什么了,才发现自己无知与知识缺乏。

本人知识有限,如有描述错误之处,望虎正。

你看这个像不像你欠我的赞。

谢谢大家。你的赞就像冬日暖阳。