Android高级UI面试题汇总(含详细解析 十五)

Android并发编程高级面试题汇总最全最细面试题讲解持续更新中👊👊
👀你想要的面试题这里都有👀
👇👇👇

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。


由于面试题内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要2023年Android中高级最全面试真题答案 完整文档的可扫描下方卡片免费获取~

PS:(文末还有使用ChatGPT机器人小福利哦!!大家不要错过)

目录

img

第一章 Java方面

  • Java基础部分
  • Java集合
  • Java多线程
  • Java虚拟机

img

第二章 Android方面

  • Android四大组件相关
  • Android异步任务和消息机制
  • Android UI绘制相关
  • Android性能调优相关
  • Android中的IPC
  • Android系统SDK相关
  • 第三方框架分析
  • 综合技术
  • 数据结构方面
  • 设计模式
  • 计算机网络方面
  • Kotlin方面

img

第三章 音视频开发高频面试题

  • 为什么巨大的原始视频可以编码成很小的视频呢?这其中的技术是什么呢?
  • 怎么做到直播秒开优化?
  • 直方图在图像处理里面最重要的作用是什么?
  • 数字图像滤波有哪些方法?
  • 图像可以提取的特征有哪些?
  • 衡量图像重建好坏的标准有哪些?怎样计算?

img

第四章 Flutter高频面试题

  • Dart部分
  • Flutter部分

img

第五章 算法高频面试题

  • 如何高效寻找素数
  • 如何运用二分查找算法
  • 如何高效解决雨水问题
  • 如何去除有序数组的重复元素
  • 如何高效进行模幂运算
  • 如何寻找最长回文子串

img

第六章 Andrio Framework方面

  • 系统启动流程面试题解析
  • Binder面试题解析
  • Handler面试题解析
  • AMS面试题解析

img

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值