文章目录

  • LayoutInflater 流程解析
    • 1 frameworks/base/core/java/android/app/Activity.java
      • 1.1 Activity#setContentView(int layoutResID)
      • 1.2 Activity#getWindow()
      • 1.3 Activity#attach(...)
    • 2 frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
      • 2.1 PhoneWindow#PhoneWindow(Context context)
      • 2.2 PhoneWindow#setContentView(int layoutResID)
      • 2.3 PhoneWindow#installDecor()
      • 2.4 PhoneWindow#generateDecor(int featureId)
      • 2.5 PhoneWindow#generateLayout(DecorView decor)
    • 3 frameworks/base/core/java/com/android/internal/policy/DecorView.java
      • 3.1 DecorView#onResourcesLoaded(LayoutInflater inflater, int layoutResource)
    • 4 frameworks/base/core/java/com/android/view/LayoutInflater.java
      • 4.1 LayoutInflater#inflate(int resource, ViewGroup root)
      • 4.2 LayoutInflater#inflate(int resource, ViewGroup root, boolean attachToRoot)
      • 4.3 LayoutInflater#inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
      • 4.4 LayoutInflater#createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr)
      • 4.5 LayoutInflater#createView(String name, String prefix, AttributeSet attrs)
      • 4.6 LayoutInflater#rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate)
    • 5 findViewById()流程
      • 5.1 frameworks/base/core/java/com/android/app/Activity.java
      • 5.2 frameworks/base/core/java/com/android/view/Window.java
      • 5.3 frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
      • 5.4 frameworks/base/core/java/com/android/view/View.java
      • 5.5 frameworks/base/core/java/com/android/view/ViewGroup.java

LayoutInflater 流程解析

在上一篇文章《Android Style和自定义属性》中,大家对Style和Theme的使用有了比较清晰的认识,知道了在项目中如何更合理的规划Style。有同学就提问了,在xml文件中定义的属性,是如何加载并传递给View呢?View又是如何创建的呢?

带着这两个问题,我们来阅读Android LayoutInflater相关的源码,阅读这块的源码,主要涉及四个类:

  • frameworks/base/core/java/android/app/Activity.java
  • frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
  • frameworks/base/core/java/com/android/internal/policy/DecorView.java
  • frameworks/base/core/java/com/android/view/LayoutInflater.java

在Activity的onCreate()方法中,我们会对当前Activity设置布局,我们以这儿为入口来进行研究。

public class MainActivity extends Activity {    ...    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ...        setContentView(R.layout.activity_main);        ...    }    ...}

1 frameworks/base/core/java/android/app/Activity.java

1.1 Activity#setContentView(int layoutResID)

public void setContentView(@LayoutRes int layoutResID) {    getWindow().setContentView(layoutResID);    initWindowDecorActionBar();}

1.2 Activity#getWindow()

getWindow()会返回一个Window实例化对象

public Window getWindow() {    return mWindow;}

而mWindow的初始化是在attach中

1.3 Activity#attach(…)

final void attach(Context context, ActivityThread aThread,        Instrumentation instr, IBinder token, int ident,        Application application, Intent intent, ActivityInfo info,        CharSequence title, Activity parent, String id,        NonConfigurationInstances lastNonConfigurationInstances,        Configuration config, String referrer, IVoiceInteractor voiceInteractor,        Window window, ActivityConfigCallback activityConfigCallback) {    ...    // 初始化一个PhoneWindow对象    mWindow = new PhoneWindow(this, window, activityConfigCallback);    ...    // 给window对象设置回调,Window的实例化对象可以回调Activity的回调方法    mWindow.setCallback(this);    ...}

2 frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

private DecorView mDecor;

mDecor是继承自FrameLayout的ViewGroup,它是整个Window的最顶层view,包含状态栏,导航栏,内容显示栏三块区域。具体层级关系见下图:

2.1 PhoneWindow#PhoneWindow(Context context)

public PhoneWindow(Context context) {    super(context);    mLayoutInflater = LayoutInflater.from(context);}

这里初始化了mLayoutInflater,最终执行(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)获取到LayoutInflater的实现类,具体对象为PhoneLayoutInflater。

2.2 PhoneWindow#setContentView(int layoutResID)

// mContentParent是一个放置Window内容的ViewGroup实例,它是mDecor本身,或mDecor的内容所在的子节点。ViewGroup mContentParent;public void setContentView(int layoutResID) {    // 判断mContentParent是否为空,为空执行installDecor(),第一次进来为空;    // 否则判断是否设置Activity的场景切换动画,默认为flase,则会将mContentParent中的子View全部清除    if (mContentParent == null) {        installDecor();    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {        mContentParent.removeAllViews();    }    // 如果设置了Activity的场景切换动画,则会根据layoutResID获取到场景并赋值给mTransitionManager    // 否则将根据layoutResID加载布局并设置给mContentParent    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext());        transitionTo(newScene);    } else {        // *************** 核心方法,重点 ***************        // 该方法的作用是将ID为layoutResID的布局作为子布局加入mContentParent中        mLayoutInflater.inflate(layoutResID, mContentParent);    }    mContentParent.requestApplyInsets();    // 回调Callback#onContentChanged()通知Activity    // onContentChanged()在Activity中为空方法,我们可以在自己的Activity中复写这个方法,实现自己的逻辑。在Activity布局文件发生改动,即调用setContentView()或者addContentView()之后会调用onContentChanged()。    final Callback cb = getCallback();    if (cb != null && !isDestroyed()) {        cb.onContentChanged();    }    mContentParentExplicitlySet = true;}

这个函数中有两个重要方法:installDecor()和mLayoutInflater.inflate(layoutResID, mContentParent)。
这两个方法分别加载的是Android源码中的系统根布局和用户自定义的R.layout.activity_main布局。我们主要看下Android是如何加载系统根布局的,看懂了之后,加载用户自定义布局也就融会贯通了。

2.3 PhoneWindow#installDecor()

private void installDecor() {    // 如果mDecor为空,则实例化;否则将当前window设置给mDecor    if (mDecor == null) {        mDecor = generateDecor(-1);        ...    } else {        mDecor.setWindow(this);    }    // 如果mContentParent为空,则根据mDecor获取到mContentParent    if (mContentParent == null) {        mContentParent = generateLayout(mDecor);        ...    }}

2.4 PhoneWindow#generateDecor(int featureId)

protected DecorView generateDecor(int featureId) {    ...    // new一个DecorView对象    return new DecorView(context, featureId, this, getAttributes());}

注意:
此处有个getAttributes()方法,该方法为父类Window中的方法,返回WindowManager.LayoutParams类。可见,该类返回的是当前Window的一些属性,这些属性的赋值通过setAttributes()方法。

2.5 PhoneWindow#generateLayout(DecorView decor)

protected ViewGroup generateLayout(DecorView decor) {    ...    int layoutResource;    int features = getLocalFeatures();    ...    // 根据features的值对layoutResource进行初始化    ...    mDecor.startChanging();    // 根据layoutResource加载布局资源    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);    // 生成contentParent,其中ID_ANDROID_CONTENT=com.android.internal.R.id.content    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);    ...    mDecor.finishChanging();    // 返回contentParent    return contentParent;}

这里layoutResource会根据features的值来进行初始化,以加载不同的布局,但是无论是什么布局,其中必然包含一个ID为content的控件,一般为FrameLayout。
例如layoutResource初始化为R.layout.screen_title_icons:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:fitsSystemWindows="true"    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent">    ...    <FrameLayout android:id="@android:id/content"      android:layout_width="match_parent"      android:layout_height="0dip"      android:layout_weight="1"      android:foregroundGravity="fill_horizontal|top"      android:foreground="?android:attr/windowContentOverlay" />LinearLayout>

3 frameworks/base/core/java/com/android/internal/policy/DecorView.java

3.1 DecorView#onResourcesLoaded(LayoutInflater inflater, int layoutResource)

这里第一个参数为PhoneWindow中初始化的mLayoutInflater,第二个参数为需要加载的系统布局(如R.layout.screen_title_icons)

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {    ...    mDecorCaptionView = createDecorCaptionView(inflater);    // 根据layoutResource,通过LayoutInflater加载布局。这里第二个参数为null,说明layoutResource这个布局没有加载到任何布局下,而是以根布局的形式存在。    final View root = inflater.inflate(layoutResource, null);    if (mDecorCaptionView != null) {        if (mDecorCaptionView.getParent() == null) {            addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));        }        mDecorCaptionView.addView(root, new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));    } else {        // 将加载好的root View加到DecorView内,布局大小为MATCH_PARENT        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));    }    mContentRoot = (ViewGroup) root;    initializeElevation();}

这个函数执行完后,PhoneWindow中的局部变量mDecor就有了子布局(如R.layout.screen_title_icons)。

4 frameworks/base/core/java/com/android/view/LayoutInflater.java

final Object[] mConstructorArgs = new Object[2];

这个全局变量定义了构造器的参数,默认为两个。这个变量经过赋值后传递给Constructor.newInstance(args),从而生成View。

4.1 LayoutInflater#inflate(int resource, ViewGroup root)

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

4.2 LayoutInflater#inflate(int resource, ViewGroup root, boolean attachToRoot)

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {    final Resources res = getContext().getResources();    ...    // 返回一个XmlResourceParser实例化对象,通过这个对象可以读取给定资源ID的布局描述。    // 这个函数实际上是对getXml()的一个简单封装。    // getLayout(resource)的内部实现为:return loadXmlResourceParser(id, "layout");    final XmlResourceParser parser = res.getLayout(resource);    try {        return inflate(parser, root, attachToRoot);    } finally {        parser.close();    }}

注意:
我们从PhoneWindow#installDecor()流程看下来,第一个参数为系统布局(如R.layout.screen_title_icons),第二个参数root为null,第三个参数attachToRoot为false。

4.3 LayoutInflater#inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {    // 对mConstructorArgs加锁    synchronized (mConstructorArgs) {        ...        final Context inflaterContext = mContext;        // 根据给定的parser返回一个AttributeSet的接口实现。如果parser已经实现了AttributeSet接口,就直接返回;        // 否则在parser上封装一个XmlPullAttributes封装类,作为检索属性的代理。        final AttributeSet attrs = Xml.asAttributeSet(parser);        ...        View result = root;        try {            ...            // 获取根节点的字符串            final String name = parser.getName();            // 判断根节点是否以"merge"开头            if (TAG_MERGE.equals(name)) {                ...            } else {                // 创建根视图temp                final View temp = createViewFromTag(root, name, inflaterContext, attrs);                ViewGroup.LayoutParams params = null;                // 第二个参数root不为空                if (root != null) {                    // 获取LayoutParams                    params = root.generateLayoutParams(attrs);                    // 如果attachToRoot为false                    if (!attachToRoot) {                        // 将LayoutParams设置给根视图temp                        temp.setLayoutParams(params);                    }                }                // 遍历解析子View,并添加到根视图temp中                rInflateChildren(parser, temp, attrs, true);                ...                // 如果root不为空并且attachToRoot为true,则将temp添加到root中                // *****************************                // 在PhoneWindow#setContentView中,传递进来的root不为空且attachToRoot为true,则会将temp添加到root                // *****************************                if (root != null && attachToRoot) {                    root.addView(temp, params);                }                // 如果root为空并且attachToRoot为false,则将temp赋值给result并返回result。                if (root == null || !attachToRoot) {                    result = temp;                }            }        } catch (XmlPullParserException e) {            ...        } catch (Exception e) {            ...        } finally {            ...        }        return result;    }}

可以看到,inflate中有两个重点方法,分别为createViewFromTag()和rInflateChildren()。

4.4 LayoutInflater#createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr)

首先看下createViewFromTag()。第一个参数parent为逐层传递下来的参数,这里为null;第二个参数为根节点名称;第三个参数为Context;第四个参数为AttributeSet;最后一个参数ignoreThemeAttr此处为false。

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {    // 如果name为"view",则从attrs中获取属性为"class"的属性值    if (name.equals("view")) {        name = attrs.getAttributeValue(null, "class");    }    // 如果允许并定义了一个theme封装,需要对context进行包装,将theme信息加入context包装类ContextThemeWrapper    if (!ignoreThemeAttr) {        // ATTRS_THEME为int[] {com.android.internal.R.attr.theme}        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);        final int themeResId = ta.getResourceId(0, 0);        if (themeResId != 0) {            context = new ContextThemeWrapper(context, themeResId);        }        ta.recycle();    }    // 如果name为blink,则返回一个BlinkLayout(一闪一闪layout)    if (name.equals(TAG_1995)) {        return new BlinkLayout(context, attrs);    }    try {        View view;        // 如果设置了mPrivateFactory、mFactory、mFactory2,则使用这些工厂类生成View        if (mFactory2 != null) {            view = mFactory2.onCreateView(parent, name, context, attrs);        } else if (mFactory != null) {            view = mFactory.onCreateView(name, context, attrs);        } else {            view = null;        }        if (view == null && mPrivateFactory != null) {            view = mPrivateFactory.onCreateView(parent, name, context, attrs);        }        // 如果没有设置mPrivateFactory、mFactory、mFactory2,则使用默认方法生成View        if (view == null) {            final Object lastContext = mConstructorArgs[0];            mConstructorArgs[0] = context;            try {                if (-1 == name.indexOf('.')) {                    view = onCreateView(parent, name, attrs);                } else {                    view = createView(name, null, attrs);                }            } finally {                mConstructorArgs[0] = lastContext;            }        }        return view;    } catch (InflateException e) {        ...    } catch (ClassNotFoundException e) {        ...    } catch (Exception e) {        ...    }}

从以上源码可以看出,createViewFromTag分为三步:

  • 1. 对一些特殊情况做处理,这一步对三种情况做了处理
    • ❶ 处理view标签
      如果标签的名称是view,使用如下:
      <view    class="RelativeLayout"    android:layout_width="match_parent"    android:layout_height="match_parent">view>
    • ❷ 如果该节点与theme相关,则需要特殊处理
      如果该节点与theme相关,需要将context与theme信息包装至ContextWrapper类。
    • ❸ 处理TAG_1995标签
      这个标签最后会被解析成BlinkLayout,BlinkLayout其实就是一个FrameLayout,这个控件最后会将包裹的内容一直闪烁,例如:
      <blink    android:layout_width="wrap_content"    android:layout_height="wrap_content">    <TextView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="Test"/>blink>
  • 2. 如果设置了mPrivateFactory、mFactory、mFactory2,则使用这些工厂类生成View
    这三个变量定义如下:
    private Factory mFactory;private Factory2 mFactory2;private Factory2 mPrivateFactory;
    Factory与Factory2都是接口,Factory2继承自Factory,拓展出了View parent这个参数。
        public interface Factory {        public View onCreateView(String name, Context context, AttributeSet attrs);    }    public interface Factory2 extends Factory {        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);    }
    这两个接口需要自己实现,从而拓展了View的解析,设置Factory和Factory需要通过setFactory()和setFactory2()来实现。Factory和Factory2只能够设置一次,并且Factory和Factory2二者互斥,只能存在一个。
  • 3. 如果没有设置mPrivateFactory、mFactory、mFactory2,则使用默认方法生成View
    如果Factory或者Factory2没有设置,或者返回View为null,才会使用默认解析方式。
    if (-1 == name.indexOf('.')) {    // 如果名称中不包含'.',为原生View,例如:LinearLayout    view = onCreateView(parent, name, attrs);} else {    // 如果名称中包含'.',为自定义View,例如:com.demo.demoStyle.CustomView    view = createView(name, null, attrs);}
    先来看下onCreateView()
    protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException {    return onCreateView(name, attrs);}protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {    // 此处传递了"andorid.view."作为前缀    return createView(name, "android.view.", attrs);}
    原来,onCreateView最终也是调用的createView(),真是殊途同归。接下来就只需要重点看下createView()。

4.5 LayoutInflater#createView(String name, String prefix, AttributeSet attrs)

public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException {    // sConstructorMap是一个HashMap,用来保存名称和对应的构造器,定义为:HashMap> sConstructorMap    // 第一次执行,获取到的构造器必为空    Constructor<? extends View> constructor = sConstructorMap.get(name);    if (constructor != null && !verifyClassLoader(constructor)) {        constructor = null;        sConstructorMap.remove(name);    }    Class<? extends View> clazz = null;    try {        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);        // 如果构造器不存在,说明Class之前未被加载过,        if (constructor == null) {            // 通过前缀+name的方式去加载class            clazz = mContext.getClassLoader().loadClass(                    prefix != null ? (prefix + name) : name).asSubclass(View.class);            // 通过过滤器去设置一些不需要加载的对象            if (mFilter != null && clazz != null) {                boolean allowed = mFilter.onLoadClass(clazz);                if (!allowed) {                    failNotAllowed(name, prefix, attrs);                }            }            // 获取到class对应的构造器            constructor = clazz.getConstructor(mConstructorSignature);            // 将构造器设置为可访问的            constructor.setAccessible(true);            // 将构造器缓存到sConstructorMap            sConstructorMap.put(name, constructor);        } else {            // 判断过滤器是否为空            if (mFilter != null) {                // mFilterMap是一个HashMap,用来存储name对应View的过滤状态                Boolean allowedState = mFilterMap.get(name);                // 判断allowedState是否为空,为空则需要加载class                if (allowedState == null) {                    // 加载class对象                    clazz = mContext.getClassLoader().loadClass(                            prefix != null ? (prefix + name) : name).asSubclass(View.class);                    // 判断class是否允许被加载,并将其值写入mFilterMap                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);                    mFilterMap.put(name, allowed);                    if (!allowed) {                        // 如果不被允许加载,则执行failNotAllowed,该函数用来抛出一个异常                        failNotAllowed(name, prefix, attrs);                    }                } else if (allowedState.equals(Boolean.FALSE)) {                    // 如果不被允许加载,则执行failNotAllowed,该函数用来抛出一个异常                    failNotAllowed(name, prefix, attrs);                }            }        }        ...        // 将attrs作为第二个参数赋值给mConstructorArgs[1]        Object[] args = mConstructorArgs;        args[1] = attrs;        // 使用构造器实例化视图,此处会调用native方法进行实例化。将mConstructorArgs传递给constructor,它会按照数组顺序将参数传递给View的构造函数。由于mConstructorArgs固定长度为2,所以View的构造函数调用第二个。        final View view = constructor.newInstance(args);        if (view instanceof ViewStub) {            // 如果View属于ViewStub那么需要给ViewStub设置一个克隆过的LayoutInflater            final ViewStub viewStub = (ViewStub) view;            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));        }        mConstructorArgs[0] = lastContext;        return view;    } catch (NoSuchMethodException e) {        ...    } catch (ClassCastException e) {        ...    } catch (ClassNotFoundException e) {        ...    } catch (Exception e) {        ...    } finally {        Trace.traceEnd(Trace.TRACE_TAG_VIEW);    }}

以上代码的基本流程为:

  • 首先依据根视图的名字,例如LinearLayout去查找缓存的构造器,如果是第一次执行,肯定返回null。如果返回为null,则通过native方法得到constructor,并强制设置可访问;
  • 之后存进sConstructorMap中。如果缓存中有constructor,则需要判断一下视图是否被过滤;
  • 最后调用newInstance得到根视图实例。得到根视图实例之后,接着设置属性,最后调用rInflateChildren()遍历创建子View。下面看看rInflateChildren()。

4.6 LayoutInflater#rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate)

加载子View,第一个参数为传递进来的XmlPullParser对象;第二个参数为通过createViewFromTag()实例化的根节点View;第三个参数为通过Xml.asAttributeSet(parser)解析出来的AttributeSet对象;第四个参数固定为true。

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);}void rInflate(XmlPullParser parser, View parent, Context context,        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {    // 获取到当前单元的深度    final int depth = parser.getDepth();    int type;    boolean pendingRequestFocus = false;    // while循环,用来逐层遍历子节点View,例如RelativeLayout,TextView等    while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {        // XML类型不是START_TAG,继续循环        if (type != XmlPullParser.START_TAG) {            continue;        }        // 获取到当前单元的名称,仅在当前事件是START_TAG, END_TAG或ENTITY_REF才有返回值。        final String name = parser.getName();        if (TAG_REQUEST_FOCUS.equals(name)) {            // 名称为"requestFocus"            pendingRequestFocus = true;            consumeChildElements(parser);        } else if (TAG_TAG.equals(name)) {            // 名称为"tag"            parseViewTag(parser, parent, attrs);        } else if (TAG_INCLUDE.equals(name)) {            // 名称为"include"            if (parser.getDepth() == 0) {                throw new InflateException(" cannot be the root element");            }            parseInclude(parser, context, parent, attrs);        } else if (TAG_MERGE.equals(name)) {            // 名称为"merge"            throw new InflateException(" must be the root element");        } else {            // 名称为其他,一般的视图都会走到这里            // 使用createViewFromTag()创建子节点View            final View view = createViewFromTag(parent, name, context, attrs);            // 设置子节点View的属性            final ViewGroup viewGroup = (ViewGroup) parent;            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);            // *** 递归调用来遍历子节点View的子节点 ***            rInflateChildren(parser, view, attrs, true);            // 将view添加到父节点布局            viewGroup.addView(view, params);        }    }    if (pendingRequestFocus) {        parent.restoreDefaultFocus();    }    if (finishInflate) {        parent.onFinishInflate();    }}

至此,以上便为一个较为完整创建View的流程。

根据以上流程,绘制顺序图如下:

5 findViewById()流程

有了以上的认识,我们来看看findViewById(int id)的流程,这块流程就很简单了。

5.1 frameworks/base/core/java/com/android/app/Activity.java

public <T extends View> T findViewById(@IdRes int id) {    return getWindow().findViewById(id);}

其中getWindow()返回的为Window的具体实现类PhoneWindow,findViewById(int id)这个函数的实现在Window中。

5.2 frameworks/base/core/java/com/android/view/Window.java

public <T extends View> T findViewById(@IdRes int id) {    return getDecorView().findViewById(id);}

这里getDecorView()方法在Window中为抽象方法,PhoneWindow实现了这个方法。我们到PhoneWindow来看具体的实现。

5.3 frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

@Overridepublic final View getDecorView() {    if (mDecor == null || mForceDecorInstall) {        installDecor();    }    return mDecor;}

PhoneWindow返回DecorView的实例mDecor,DecorView继承自FrameLayout,FrameLayout继承自ViewGroup,ViewGroup又继承自View。findViewById(int id)这个方法仅在View中有定义

5.4 frameworks/base/core/java/com/android/view/View.java

public final <T extends View> T findViewById(@IdRes int id) {    if (id == NO_ID) {        return null;    }    return findViewTraversal(id);}protected <T extends View> T findViewTraversal(@IdRes int id) {    // 如果id与View的mID相等,就返回自己,否则返回null    if (id == mID) {        return (T) this;    }    return null;}

ViewGroup复写了findViewTraversal(int id),进入ViewGroup查看。

5.5 frameworks/base/core/java/com/android/view/ViewGroup.java

// 存储ViewGroup中的子View数组,通过addInArray()添加private View[] mChildren;@Overrideprotected <T extends View> T findViewTraversal(@IdRes int id) {    if (id == mID) {        return (T) this;    }    final View[] where = mChildren;    final int len = mChildrenCount;    for (int i = 0; i < len; i++) {        View v = where[i];        if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {            v = v.findViewById(id);            if (v != null) {                return (T) v;            }        }    }    return null;}

可以看到,这里通过遍历ViewGroup中的子View,调用子View的findViewById(int id)来递归查找。

更多相关文章

  1. android jetpack Navigation 获取当前激活的fragment
  2. Android8.0在Setting中添加Led指示灯闪烁开关
  3. Android实现程序之间的跳转
  4. Android(安卓)TabLayout设置选中状态标题字体大小,粗细
  5. Android(安卓)开发中,pullToRefreshListView 的刷新,加载
  6. 浅谈Java中Collections.sort对List排序的两种方法
  7. mybatisplus的坑 insert标签insert into select无参数问题的解决
  8. Python技巧匿名函数、回调函数和高阶函数
  9. Python list sort方法的具体使用

随机推荐

  1. Android Softap启动分析
  2. Grade 编译 Android 解决 Error:more tha
  3. android maven 签名
  4. 【Android】播放提示音
  5. 2011.08.12(3)——— android AudioTrack
  6. android 设置textview边框以及点击效果
  7. [Android] 环境配置之正式版Android Stud
  8. 从android 里面读取配置文件
  9. android软键盘隐藏总结
  10. Android resources