ViewStub源码分析

  ViewStub是一种特殊的View,Android官方给出的解释是:一种不可见的(GONE)、size是0的占位view,多用于运行时

延迟加载的,也就是说真正需要某个view的时候。在实际项目中,我发现它试用的场景大体有2种:

1. 某种只第一次需要显示的view,比如某个介绍性的东西,比如用户触发了某些操作,给他弹出一个使用介绍,

这种介绍只出现一次,之后的操作中不会再出现,所以这种情况下可以先用ViewStub来占个位,等介绍的view真正

需要的时候才inflate,一般情况下这种view的创建也比较expensive;

2. 某种直到运行时某一刻的时候才知道应该加载哪个view,比如你可能需要根据服务器返回的某些数据来决定是显示View 1

还是显示View 2,这种情况下,也可以先用ViewStub占位,直到可以做决定了在inflate相应的view;

为了接下来的分析方便,这里我先贴一段ViewStub的xml代码,如下:

<ViewStub
    android:id="@+id/stub"
    android:inflatedId="@+id/realView" // 可设置,不过一般意义不大
    android:layout="@layout/my_real_view"
    android:visibility="GONE" // really no need, don‘t write useless code
    android:layout_width="match_parent"
    android:layout_height="40dip" />

在代码里你同样可以通过findViewById(R.id.stub)来找到此ViewStub,当真正的"my_real_view"布局被inflate之后,

ViewStub这个控件就被从view层次结构中删除了,取而代之的就是"my_real_view"代表的布局。注意下,这里的所有

layout_width/height都是用来控制inflate之后的view的,而不是给ViewStub用的,这个可能是Android系统里唯一的例外。

在我们的Java代码里使用的方式,官方推荐的做法是:

ViewStub stub = (ViewStub) findViewById(R.id.stub);
View inflated = stub.inflate();

但这里有一点需要留意的就是findViewById(R.id.stub),因为我们知道stub在第一次被inflate之后就会从view层次结构中

删除,所以你应该要确保这样的findViewById只会被调用一次(一般都是这样的情况),但在有些你没想到的case下

可能也会被调用多次。前几天我们在项目里就遇到了这个情况,出现了NPE,最终调查发现是stub的findViewById被又调用

了一次,导致了NPE(因为层次结构中已经找不到这个id了)。好的做法是比如把这种findViewById的放在onCreate类似

的只会被执行一次的地方,否则你就应该格外小心,自己加些保护处理。

  说了这么多,接下来可以切入正题了,废话不多说,从ctor开始上代码:

    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes);

        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); // 从layout文件中读取inflateId的值,如果有的话
        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); // 同样的,读取真正的layout文件

        a.recycle();

        a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
        mID = a.getResourceId(R.styleable.View_id, NO_ID); // 读取View_id属性
        a.recycle();

        initialize(context);
    }

    private void initialize(Context context) {
        mContext = context;
        setVisibility(GONE); // 看到这里,你就明白了我前面xml文件中提到的没必要手动指定android:visibility="GONE"的原因
        setWillNotDraw(true); // 因为ViewStub只是个临时占位的,它不做任何绘制操作,所以打开这个标志位以优化绘制过程,提高性能
    }

  接下来,我们看几个相关的比较简单的方法,如下:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(0, 0); // 实现很简单,写死的大小为0,0
    }

    @Override
    public void draw(Canvas canvas) { // 不做任何绘制
    }

    @Override
    protected void dispatchDraw(Canvas canvas) { // 没啥需要dispatch的
    }

    /**
     * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
     * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
     * by the inflated layout resource. After that calls to this function are passed
     * through to the inflated view.
     *
     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
     *
     * @see #inflate()
     */
    @Override
    @android.view.RemotableViewMethod
    public void setVisibility(int visibility) { // setVisibility做了些特殊处理
        if (mInflatedViewRef != null) { // 真正View的弱引用
            View view = mInflatedViewRef.get();
            if (view != null) {
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else { // 当还没inflate的时候,如果调了setVisibility方法,如果不是GONE的情况会自动在这种情况下调inflate的
            super.setVisibility(visibility);
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                inflate(); // 非GONE的情况,会多调用inflate()方法,所以你拿到ViewStub的实例后既可以主动调
            }              // inflate()方法也可以通过setVisiblity(VISIBLE/INVISIBLE)来触发对inflate的间接调用
        }
    }

  最后,我们来看看真正关键的方法inflate的实现,代码如下:

    /**
     * Inflates the layout resource identified by {@link #getLayoutResource()}
     * and replaces this StubbedView in its parent by the inflated layout resource.
     *
     * @return The inflated layout resource.
     *
     */
    public View inflate() {
        final ViewParent viewParent = getParent();

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                final LayoutInflater factory;
                if (mInflater != null) {
                    factory = mInflater;
                } else {
                    factory = LayoutInflater.from(mContext);
                }
                final View view = factory.inflate(mLayoutResource, parent,
                        false); // 注意这行,实际上还是调用了LayoutInflater的inflate方法,注意最后一个参数是false,所以这里返回的
                                // view就是真正layout文件里的root view而不是parent了。
                if (mInflatedId != NO_ID) {
                    view.setId(mInflatedId); // 如果layout文件里有给真正的view指定id就在这里设置上
                }

                final int index = parent.indexOfChild(this);
                parent.removeViewInLayout(this); // 找到ViewStub在parent中的位置并删除ViewStub,看到了,在inflate的过程中
                                                 // ViewStub真的是被删掉了!!!
                final ViewGroup.LayoutParams layoutParams = getLayoutParams(); // 获取LayoutParams,就是你在xml文件中写的                                            // layout_xxx这样的属性,虽然实际上是属于ViewStub的,
                if (layoutParams != null) { // 但下面紧接着就用这个LayoutParams来添加真正的view了
                    parent.addView(view, index, layoutParams); // 注意下添加的位置就是原先ViewStub待的位置,                                                               // 所以等于就是完完全全替换了
                } else {
                    parent.addView(view, index); // 如果layoutParams是null的case
                }

                mInflatedViewRef = new WeakReference<View>(view); // 用view初始化弱引用

                if (mInflateListener != null) { // 通过mInflateListener将inflate完成事件通知出去。。。
                    mInflateListener.onInflate(this, view);
                }

                return view; // 返回真正的view
            } else { // ViewStub必须指定真正的layout文件!!!
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else { // ViewStub也必须有个parent view的!!!
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }

    /**
     * Specifies the inflate listener to be notified after this ViewStub successfully
     * inflated its layout resource.
     *
     * @param inflateListener The OnInflateListener to notify of successful inflation.
     *
     * @see android.view.ViewStub.OnInflateListener
     */
    public void setOnInflateListener(OnInflateListener inflateListener) {
        mInflateListener = inflateListener; // 技术上要想响应inflate完成事件,有2种途径,这里通过设置listener是一种方式,
    }                                       // 也可以通过更generic的方式:override真正view的onFinishInflate方法。

    /**
     * Listener used to receive a notification after a ViewStub has successfully
     * inflated its layout resource.
     *
     * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener)
     */
    public static interface OnInflateListener {
        /**
         * Invoked after a ViewStub successfully inflated its layout resource.
         * This method is invoked after the inflated view was added to the
         * hierarchy but before the layout pass.
         *
         * @param stub The ViewStub that initiated the inflation.
         * @param inflated The inflated View.
         */
        void onInflate(ViewStub stub, View inflated);
    }
}

  ViewStub的使用也被Android归到提高性能里面,之所以这么说一方面是因为ViewStub有延迟加载(直到需要的时候才inflate

真正的view)、先占位的作用,另外一方面也是由于对measure、layout、draw这3个关键过程的优化,因为ViewStub的这3个过程

很简单,或者根本就是do nothing,所以性能很好。综上,Android才建议我们用ViewStub来优化layout性能,当然也是真正合适使用

ViewStub的时机,而不是要你在所有的layout文件中都这么做,这个时机在开始的时候我也提到了,另外也可以参考下Android官方的文章:

http://developer.android.com/training/improving-layouts/loading-ondemand.html

http://developer.android.com/reference/android/view/ViewStub.html

P.S. 这篇文章本来应该是在上周末完成的,这周末去千岛湖outing,在酒店里实在无聊,发现还有篇未完成的文章就在这里补充下,enjoy。。。

时间: 2025-01-13 20:51:44

ViewStub源码分析的相关文章

布局优化之ViewStub源码分析

源码分析 1 @RemoteView 2 public final class ViewStub extends View { 3 private int mInflatedId; 4 private int mLayoutResource; 5 6 private WeakReference<View> mInflatedViewRef; 7 8 private LayoutInflater mInflater; 9 private OnInflateListener mInflateLis

Android应用setContentView与LayoutInflater加载解析机制源码分析

[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 其实之所以要说这个话题有几个原因: 理解xml等控件是咋被显示的原理,通常大家写代码都是直接在onCreate里setContentView就完事,没怎么关注其实现原理. 前面分析<Android触摸屏事件派发机制详解与源码分析三(Activity篇)>时提到了一些关于布局嵌套的问题,当时没有深入解释. 所以接下来主要分析的就是View或者ViewGroup对象是如何添加至应用程

Android之App界面的挂载与显示及源码分析

前言 入门 视图树 源码分析 前言 好久没有写博客了,都感觉有些生疏了. 总觉的人对自己要求高一些比较好,这样才进步比较快.接下来会继续给大家带来一些更有用的知识. 个人水平有限,如果感觉我的博客对您有用处,那就留个言给下鼓励:如果那里写的有误,请各位看客老爷多多拍砖! 注意:此处我使用的IDE是Android Studio 入门 相信每个人在学习Android时,都创建过很多Demo工程,那么下面的代码,大家一定非常眼熟了. public class MainActivity extends

Android中将xml布局文件转化为View树的过程分析(下)-- LayoutInflater源码分析

在Android开发中为了inflate一个布局文件,大体有2种方式,如下所示: // 1. get a instance of LayoutInflater, then do whatever you want LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // 2. you're in some View class, then jus

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Spark的Master和Worker集群启动的源码分析

基于spark1.3.1的源码进行分析 spark master启动源码分析 1.在start-master.sh调用master的main方法,main方法调用 def main(argStrings: Array[String]) { SignalLogger.register(log) val conf = new SparkConf val args = new MasterArguments(argStrings, conf) val (actorSystem, _, _, _) =