前言
如果你布局加载很慢,或者有一些自己想法。那么这个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);
}
接着我们看看BasicInflater
的onCreateView
,核心代码。这里我闻到了狸猫换太子味道,我们可以直接在前面添加自己的代码。去替换成我们的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对象缓存池源码分析
文中提到阻塞,也可以从我这篇文中了解一下。为啥阻塞不占有资源,可以直接滑到中间观看,中间有对这个进行解释。
总结
一天一篇文章,写着写着就已经不知道自己要写什么了,才发现自己无知与知识缺乏。
本人知识有限,如有描述错误之处,望虎正。
你看这个像不像你欠我的赞。