美文网首页
【Android面试】高级UI面试题——View.inflate

【Android面试】高级UI面试题——View.inflate

作者: 小城哇哇 | 来源:发表于2023-09-11 14:59 被阅读0次

View.inflater过程与异步inflater

这道题想考察什么?

考察同学对xml解析过程,以及异步解析过程,但是异步解析在使用过程中有诸多限制,所以使用的并不太多。

考生应该如何回答

Inflate解析

我们先分析一下View.inflate 的代码:

// View.java
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}

上面的代码不难发现是调用了LayoutInflater.inflate,于是我们将目光放到这个函数里面来

// LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();

    // 获取资源解析器 XmlResourceParser
    XmlResourceParser parser = res.getLayout(resource);
    try {
        // 解析View
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        final Context inflaterContext = mContext;
        // 存储传进来的根布局
        View result = root;
        try {
            final String name = parser.getName();
            //解析 merge标签,rInflate方法会将 merge标签下面的所有子 view添加到根布局中
            // 这也是为什么 merge 标签可以简化布局的效果
            if (TAG_MERGE.equals(name)) {
                // 必须要有父布局,否则报错
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                }
                // 解析 merge标签下的所有的 View,添加到根布局中
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // 获取 xml 布局的根 View 对象,比如 LinearLayout 对象,FrameLayout 对象等
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                ViewGroup.LayoutParams params = null;
                // 第一种情况:root != null &&attachToRoot 为false,通过 root 来获取根节点的布局参数 ViewGroup.LayoutParams 对象,也就是说,把 xml 中的根节点的 layout_ 开头的属性,如layout_width 和 layout_height 对应的值转为布局参数对象中的字段值,如width 和 height 值
                if (root != null) {
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // 将 root 提供的 LayoutParams 设置给View
                        temp.setLayoutParams(params);
                    }
                }
                // 渲染子View
                rInflateChildren(parser, temp, attrs, true);
                // 第二种情况:root != null,且attachToRoot = true,新解析出来的 View 会被 add 到 root 中去,然后将 root 作为结果返回
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
                // 第三种情况:root == null,或者 attachToRoot = false,直接返回View,这意味着此时的temp没有解析xml中的layout属性
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }
        }
        // 返回result,可能是View,也可能是root
        return result;
    }
}

从上面的代码大家应该不难发现,inflate 主要是解析xml的属性,并生成xml的根布局temp,并且基于inflate的参数情况,来确定是否将temp添加到 解析它的 外层布局上面,也就是root变量上面。

inflate 的三个参数,其中第二和第三参数分下面几种情况:

  • 当 root != null 且 attachToRoot == false 时,新解析的 View 会直接作为结果返回,而且 root 会为新解析的 View 生成 LayoutParams 并设置到该 View 中去。
  • 当 root != null 且 attachToRoot == true 时,新解析出来的 View 会被 add 到 root 中去,然后将 root 作为结果返回。
  • 当 root == null 且 attachToRoot == false 时,新解析的 View 会直接作为结果返回,注意:此时的view是没有解析它xml中的layout params属性的,所以,xml中设置的宽高将可能失效

接下来,我们分析一下createViewFromTag 源码

// LayoutInflater.java
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    return createViewFromTag(parent, name, context, attrs, false);
}

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                       boolean ignoreThemeAttr) {
    ...
    if (view == null) {
        if (-1 == name.indexOf('.')) {
            // 创建android sdk 的View,例如:LinearLayout、Button等,最终还是调用的createView
            view = onCreateView(context, parent, name, attrs);
        } else {
            // 创建 用户自定义View
            view = createView(context, name, null, attrs);
        }
    }
    ...
    return view;
}

从上面的函数我们发现他们主要是执行了onCreateView函数,那么onCreateView又做了什么呢?

public final View createView(@NonNull Context viewContext, @NonNull String name,
                             @Nullable String prefix, @Nullable AttributeSet attrs)
    throws ClassNotFoundException, InflateException {
    // 从缓存中获取constructor
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    Class<? extends View> clazz = null;
    try {
        // 没有缓存,则创建
        if (constructor == null) {
            // 通过全类名获取 Class 对象,如果是sdk的View,需要拼接字符串
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                                  mContext.getClassLoader()).asSubclass(View.class);

            // 获取 Constructor 对象
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            // 用静态 HashMap保存,优化性能,同一个类,下次就可以直接拿这个constructor创建
            sConstructorMap.put(name, constructor);
        } else {
        }

        try {
            // 创建View对象
            final View view = constructor.newInstance(args);
            // 返回创建的View对象
            return view;
        } 
    } 
}

通过上面大家不难发现 onCreateView 是在通过反射的方式去创建XML中定义的view。并将他们构建成

小结

整个Inflater.inflate的过程,其实就是就是先进行xml解析,拿到xml中相关的tag的情况下,然后通过反射创建tag对应的View对象的一个流程。

异步inflate

Google开发者在v4包中增加了一个用来异步inflate layouts的帮助类。

AsyncLayoutInflater

AsyncLayoutInflater 是来帮助作异步加载 layout 的,inflate(int, ViewGroup, OnInflateFinishedListener) 方法运行结束以后 OnInflateFinishedListener 会在主线程回调返回 View;这样作旨在 UI 的懒加载或者对用户操作的高响应。

简单的说咱们知道默认状况下 setContentView 函数是在 UI 线程执行的,其中有一系列的耗时动做:Xml的解析、View的反射建立等过程一样是在UI线程执行的,AsyncLayoutInflater 就是来帮咱们把这些过程以异步的方式执行,保持UI线程的高响应。
AsyncLayoutInflater 只有一个构造函数及普通调用函数:inflate(int resid, ViewGroup parent, AsyncLayoutInflater.OnInflateFinishedListener callback),使用也很是方便。

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new AsyncLayoutInflater(AsyncLayoutActivity.this)
                .inflate(R.layout.async_layout, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
                    @Override
                    public void onInflateFinished(View view, int resid, ViewGroup parent) {
                        setContentView(view);
                    }
                });
        // 别的操做
    }
AsyncLayoutInflater 构造函数
    public AsyncLayoutInflater(@NonNull Context context) {
        mInflater = new BasicInflater(context);
        mHandler = new Handler(mHandlerCallback);
        mInflateThread = InflateThread.getInstance();
    }

主要作了三件事情:
1.建立 BasicInflater。BasicInflater 继承自 LayoutInflater,只是覆写了 onCreateView:优先加载sClassPrefixList 中做描述的这三个前缀的 view,而后才按照默认的流程去加载,由于大多数状况下咱们 Layout 中使用的View都在这三个 package 下。

private static class BasicInflater extends LayoutInflater {
        private static final String[] sClassPrefixList = {
            "android.widget.",
            "android.webkit.",
            "android.app."
        };
        @Override
        protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
            for (String prefix : sClassPrefixList) {
                try {
                    View view = createView(name, prefix, attrs);
                    if (view != null) {
                        return view;
                    }
                } catch (ClassNotFoundException e) {
                }
            }
            return super.onCreateView(name, attrs);
        }
    }

2.建立 Handler。建立 Handler 和它普通的做用同样,就是为了线程切换,AsyncLayoutInflater 是在异步里 inflate layout,那建立出来的 View 对象须要回调给主线程,就是经过 Handler 来实现的。

3.获取 InflateThread 对象。InflateThread 从名字上就好理解,是来作 Inflate 工做的工做线程,经过 InflateThread.getInstance 能够猜想 InflateThread 里面是一个单例,默认只在一个线程中作全部的加载工做,这个类咱们会在下面重点分析

inflate

AsyncLayoutInflater 中有一个非常重要的函数:inflate,那么这个函数是怎样的呢?我们一起看代码

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

首先,会经过 InflateThread 去获取一个 InflateRequest,其中有一堆的成员变量。为何须要这个类呢?由于后续异步 inflate 须要一堆的参数(对应 InflateRequest 中的变量),会致使方法签名过长,而使用 InflateRequest 就避免了这一点。那么InflateRequest是什么呢?大家参考下面的代码

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

从上面的代码不难发现,InflateRequest 其实就是一个类,这个类代表了一个xml解析的请求所需要的基本信息。

然后,接下来对 InflateRequest 变量赋值以后会将其加到 InflateThread 中的一个队列中等待执行。

public void enqueue(InflateRequest request) {
        try {
            mQueue.put(request);
        } catch (InterruptedException e) {
            throw new RuntimeException(
                    "Failed to enqueue async inflate request", e);
        }
    }
InflateThread
private static class InflateThread extends Thread {
        private static final InflateThread sInstance;
        static {
            // 静态代码块,确保只会建立一次,而且建立即start。
            sInstance = new InflateThread();
            sInstance.start();
        }
       // 单例,避免重复创建线程带来的开销
        public static InflateThread getInstance() {
            return sInstance;
        }

        // 阻塞队列(保存封装过得request)
        private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
        private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);

        public void runInner() {
            InflateRequest request;
            try {
                request = mQueue.take(); // 虽然是死循环,但队列中没有数据会阻塞,不占用cpu
            } catch (InterruptedException ex) {
                // Odd, just continue
                Log.w(TAG, ex);
                return;
            }

            try {
            //解析xml的位置在这
                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);
            }
            // 发送消息到主线程
            Message.obtain(request.inflater.mHandler, 0, request)
                    .sendToTarget();
        }

        @Override
        public void run() {
            // 异步加载布局并使用handler进行
            while (true) {
                runInner();
            }
        }

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

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

        public void enqueue(InflateRequest request) {
            try {
                mQueue.put(request);// 添加到缓存队列中
            } catch (InterruptedException e) {
                throw new RuntimeException(
                        "Failed to enqueue async inflate request", e);
            }
        }
    }

总结:

SyncLayoutInflater使用的是ArrayBlockingQueue来实现此模型,所以如果连续大量的调用AsyncLayoutInflater创建布局,可能会造成缓冲区阻塞。
enqueue 函数:只是插入元素到 mQueue 队列中,若是元素过多那么是有排队机制的;
runInner 函数:运行于循环中,从 mQueue 队列取出元素,调用 inflate 方法返回主线程;

SyncLayoutInflater 就是用于异步加载layout的,可以用于懒加载中,或者是在用户想提升onCreate函数的执行效率的时候。但是,它有几个缺陷:
1)异步转换出来的 View 并没有被加到 parent view中,AsyncLayoutInflater 是调用了 LayoutInflater.inflate(int, ViewGroup, false),因此如果需要加到 parent view 中,就需要我们自己手动添加;
2)不支持加载包含 Fragment 的 layout。

有需要面试题的朋友可以关注一下哇哇,以上都可以分享!!!

相关文章

  • Android面试题

    Android面试必备: (一)、Java面试题 (二)、Android面试题 (三)、Android高级面试题

  • l 主要分为以下几部分: (1)java 面试题 (2)Android 面试题 (3)高级开发技术面试题 (...

  • Android面试题与解析

    主要分为以下几部分: (1)java面试题 (2)Android面试题 (3)高级开发技术面试题 (4)跨平台Hy...

  • 2019-10-17

    主要分为以下几部分:(1)java面试题 (2)Android面试题 (3)高级开发技术面试题 (4)跨平台Hyb...

  • 面试题

    最全的BAT大厂面试题整理答案Android面试一天一题(Day 37:一套高级工程师的面试题)Android面试...

  • 华为被虐,美团被刷花3个月从咸鱼到大厂Offer收割机「面试总结

    Android面试题由易—难,适合小白—高级架构师!(注:面试者通关利器!) 前言 【Android进阶小刘】是我...

  • 面试题

    关注的面试题文集 Android面试题整理 Android工程师面试题大全 Android 面试题总结之Andro...

  • ios面试题

    初级面试题 中级面试题 高级面试题 swift篇

  • Android面试总结二

    参考文章 40个Android面试题Java面试题集Android名企面试题及知识点整理Android面试题收集较...

  • Android 高级UI 目录

    Android 高级UI开发 目录 Android 高级UI1 Material Design概述Android ...

网友评论

      本文标题:【Android面试】高级UI面试题——View.inflate

      本文链接:https://www.haomeiwen.com/subject/dxgevdtx.html