安卓源码-setContentView()源码分析

setContentView源码分析:
大致架构:


image

我们来对上图做出简单解释:DecorView是一个应用窗口的根容器,继承FrameLayoutDecorView只有一个布局,它是一个垂直LinearLayout,包含两个子元素,一个是TitleView(ActionBar的容器),另一个是ContentView(窗口内容的容器)。关于ContentView,它是一个FrameLayout(id为android.R.id.content),我们用的setContentView就是设置到ContentView。上图还表达了每个Activity都与一个Window(具体来说是PhoneWindow)相关联,用户界面则由Window所承载。

window

window是一个抽象类,phoneWindow是其唯一的继承类。

image.png

这个抽象类包含了三个核心组件:

WindowManager.LayoutParams: 窗口的布局参数;

Callback: 窗口的回调接口,通常由Activity实现;

ViewTree: 窗口所承载的控件树。

实际上,窗口是一个宏观的思想,它是屏幕上用于绘制各种UI元素及响应用户输入事件的一个矩形区域。通常具备以下两个特点:

1.独立绘制,不与其它界面相互影响;

2.不会触发其它界面的输入事件;

在Android系统中,窗口是独占一个Surface实例的显示区域,每个窗口的Surface由WindowManagerService分配。我们可以把Surface看作一块画布,应用可以通过Canvas或OpenGL在其上面作画。画好之后,通过SurfaceFlinger将多块Surface按照特定的顺序(即Z-order)进行混合,而后输出到FrameBuffer中,这样用户界面就得以显示。

PhoneWindow

PhoneWindow
这个类是Framework为我们提供的Android窗口的具体实现。我们平时调用setContentView()方法设置Activity的用户界面时,实际上就完成了对所关联的PhoneWindow的ViewTree的设置。我们还可以通过Activity类的requestWindowFeature()方法来定制Activity关联PhoneWindow的外观,这个方法实际上做的是把我们所请求的窗口外观特性存储到了PhoneWindow的mFeatures成员中,在窗口绘制阶段生成外观模板时,会根据mFeatures的值绘制特定外观。

image.png

Window#getWindowStyle

通过Window的getWindowStyle方法从style.xml中获取此应用程序窗口主题的属性:

synchronized (this) {
    if (mWindowStyle == null) {
        mWindowStyle = mContext.obtainStyledAttributes(
                com.android.internal.R.styleable.Window);
    }
    return mWindowStyle;
}

Window#findViewById

这个方法是我们最常用的方法之一,在Activity中调用findViewById方法,内部会调用Window的findViewById方法,最终调用的是View中的findViewById方法,这里不做深入研究。

return getDecorView().findViewById(id);

Window#setContentView(int)

在Window中该方法是抽象方法,查看它的唯一子类PhoneWindow中的实现。

Activity.setContentView()源码分析

public void setContentView(@LayoutRes int layoutResID) {
      // 获取Window 调用window的setContentView方法,发现是抽象类,所以需要找具体的实现类PhoneWindow
      getWindow().setContentView(layoutResID);
  }

  // PhoneWindow 中的 setContentView方法
  @Override
  public void setContentView(int layoutResID) {
      // 如果mContentParent 等于空,调用installDecor();
     //mContentParent就是我们自己写的文件布局,installDecor()就是加载DecorView,DecorView是一个继承FrameLayout的布局文件。
      if (mContentParent == null) {
          //此方法的作用 1.加载一个decorview确定window最基本的布局
         //2.从decorView的布局中选择一个控件给mContentParent
          installDecor();
      } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
          mContentParent.removeAllViews();
      }

      // 把我们自己的布局layoutId加载到mContentParen。
      mLayoutInflater.inflate(layoutResID, mContentParent);
  }

installDecor就是加载一个decorView(相应的布局文件),并从布局文件中的id为android.R.id.content的FrameLayout设置给mContentParent:

private DecorView mDecor;

  private void installDecor() {    
      if (mDecor == null) {
         // 先去创建一个  DecorView 
         mDecor = generateDecor(-1);
      }
      // ......
      // 省略调一些代码,看着晕,不过这也太省了。
      if (mContentParent == null) {
         mContentParent = generateLayout(mDecor);
      }
  }
  
  // generateDecor 方法
  protected DecorView generateDecor(int featureId) {
      // 就是new一个DecorView ,DecorView extends FrameLayout 不同版本的源码有稍微的区别,
      // 低版本DecorView 是PhoneWindow的内部类,高版本是一个单独的类,不过这不影响。
      return new DecorView(context, featureId, this, getAttributes());
  }

  protected ViewGroup generateLayout(DecorView decor) {
      // Inflate the window decor.
      int layoutResource;  //系统布局文件的Id
      // 都是一些判断,发现 layoutResource = 系统的一个资源文件,
    //根据获得的主题(style.xml),调用不同的requestFeature参数,从而生成不同的布局。所以requestFeature要在setContentView之前。
    TypedArray a = getWindowStyle();
    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
          requestFeature(FEATURE_NO_TITLE);
        }
      if(){}else if(){}else if(){
          // Embedded, so no decoration is needed.
          layoutResource = R.layout.screen_simple;
          // System.out.println("Simple!");
      }
      
      mDecor.startChanging();
      // 把系统布局解析加载到 DecorView 
      // 某些源码是 addView() 其实是一样的
      mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

      //  ID_ANDROID_CONTENT 是 android.R.id.content,这个View是从DecorView里面去找的,
      //  也就是    从系统的layoutResource里面找一个id是android.R.id.content的一个FrameLayout
      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);     
      // 把这个ViewGroup返回给我们mContentParent
      return contentParent;
  }

好了,我们文字总结一下:

  • setContentView()通过getwindow()获取PhoneWindow实例,phonewindow获取decorView的实例。
  • 然后通过一系列的判断要加载哪个系统布局文件的id,然后把布局文件解析加载到decorView,再从decorView布局文件中找到一个id为android.R.id.content的控件给mContentParent
  • 最后通过inflate把我们传过来的布局id加载到mContentParent

兼容包AppCompatActivity的setContentView

@Override
  public void setContentView(@LayoutRes int layoutResID) {
      getDelegate().setContentView(layoutResID);
  }

public AppCompatDelegate getDelegate() {
      if (mDelegate == null) {
          mDelegate = AppCompatDelegate.create(this, this);
      }
      return mDelegate;
  }

  // window 还是那个window ,留意一下就行 , 返回 AppCompatDelegateImpl,但是都是相互继承

 public static AppCompatDelegate create(@NonNull Activity activity,
          @Nullable AppCompatCallback callback) {
      return new AppCompatDelegateImpl(activity, callback);
  }
 //重载方法
  public static AppCompatDelegate create(@NonNull Dialog dialog,
          @Nullable AppCompatCallback callback) 
  public static AppCompatDelegate create(@NonNull Context context, @NonNull Window window,
          @Nullable AppCompatCallback callback) 
  public static AppCompatDelegate create(@NonNull Context context, @NonNull Activity activity,
          @Nullable AppCompatCallback callback) 


  // 下面其实就没啥好看的了,一个一个点进去,仔细看看就好了。
 //与Activity没啥区别了,都是先创建一个decorView实例,然后加载系统布局文件,最后在decorView找到id为android.R.id.content的
  @Override
  public void setContentView(int resId) {
      ensureSubDecor();
      ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
      contentParent.removeAllViews();
      LayoutInflater.from(mContext).inflate(resId, contentParent);
      mOriginalWindowCallback.onContentChanged();
  }

  private void ensureSubDecor() {
      mSubDecor = createSubDecor();
  }

LayoutInflater源码

Inflater的三种使用方法:

View layoutView = View.inflate(this,R.layout.activity_main,null);
      layoutView = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
      layoutView = LayoutInflater.from(this).inflate(R.layout.activity_main,null,false);

//上面的第一个方法调用的是第二种方法
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
      LayoutInflater factory = LayoutInflater.from(context);
      return factory.inflate(resource, root);
  }
//第二种方法调用的是第三种方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
      return inflate(resource, root, root != null);
  }

先看看LayoutInflate.from(this)如何实例化一个LayoutInflate

// LayoutInflater 方法里面,是一个静态的方法
public static LayoutInflater from(Context context) {
   // 通过context获取系统的服务
      LayoutInflater LayoutInflater =
              // context.getSystemService()是一个抽象类,所以我们必须找到实现类ContextImpl
              (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      if (LayoutInflater == null) {
          throw new AssertionError("LayoutInflater not found.");
      }
      return LayoutInflater;
  }

// ContextImpl 里面的实现方法
  @Override
  public Object getSystemService(String name) {
      return SystemServiceRegistry.getSystemService(this, name);
  }

// SystemServiceRegistry 里面的getSystemService方法
  public static Object getSystemService(ContextImpl ctx, String name) {
      ServiceFetcher fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
      return fetcher != null ? fetcher.getService(ctx) : null;
  }
  
  // 这是一个静态的HashMap集合
  private static final HashMap> SYSTEM_SERVICE_FETCHERS =
          new HashMap>();
  
  // 静态的代码块中
  static{
       // 注册LayoutInflater服务
       registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
              new CachedServiceFetcher() {
          @Override
          public LayoutInflater createService(ContextImpl ctx) {
              return new PhoneLayoutInflater(ctx.getOuterContext());
          }});
       }
       // 注册很多的其他服务......
  }

文字总结:通过context的实例化contextImpl的获取的,contextImpl#getSystemService调用了StsyemServiceRegister.getSystemService方法,这个方法通过一个静态map获得我们的LayoutInflate,初始化是在静态代码块中通过registerService注册了很多服务。这是一个单例模式,全局只有一个LayoutInflate对象。

.inflate(LayoutRes,parent,attachToRoot)生成View

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
      final Resources res = getContext().getResources();
      if (DEBUG) {
          Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                  + Integer.toHexString(resource) + ")");
      }
      // 获取一个 XmlResourceParser 解析器,这个应该并不陌生,就是待会需要去解析我们的layoutId.xml文件
      final XmlResourceParser parser = res.getLayout(resource);
      try {
          return inflate(parser, root, attachToRoot);
      } finally {
          parser.close();
      }
  }

  public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
  synchronized (mConstructorArgs) {
      ......
      //保存传进来的这个view,并最终返回result
      View result = root;

      try {
          // Look for the root node.
          int type;
          //在这里找到xml标签
          while ((type = parser.next()) != XmlPullParser.START_TAG &&
                  type != XmlPullParser.END_DOCUMENT) {
              // Empty
          }

          if (type != XmlPullParser.START_TAG) {
              throw new InflateException(parser.getPositionDescription()
                      + ": No start tag found!");
          }
          //获取这个xml标签的名字
          final String name = parser.getName();
           ......

          //判断根节点是否merge标签
          if (TAG_MERGE.equals(name)) {
              if (root == null || !attachToRoot) {
                  throw new InflateException(" can be used only with a valid "
                          + "ViewGroup root and attachToRoot=true");
              }
              //xml根为merge调用
              rInflate(parser, root, inflaterContext, attrs, false);
          } else {
             
              //根据view名称构造view.
              final View temp = createViewFromTag(root, name, inflaterContext, attrs);

              //布局参数          
              ViewGroup.LayoutParams params = null;

           
              if (root != null) {
                  params = root.generateLayoutParams(attrs);
                  if (!attachToRoot) {
                      //temp设置布局参数
                      temp.setLayoutParams(params);
                  }
              }
                ......
          
              //在这里,先获取到了temp,再把temp当做root传进去rInflateChildren
              //进行加载temp后面的子所有view
              rInflateChildren(parser, temp, attrs, true);
                ......

             
              if (root != null && attachToRoot) {
                  //把view添加到root中并设置布局参数
                  root.addView(temp, params);
              }

             //返回我们的xml布局文件
              if (root == null || !attachToRoot) {
                  result = temp;
              }
          }

      } catch (XmlPullParserException e) {
          ......
      } catch (Exception e) {
          ......
      } finally {
          ......
      }

      return result;
     }
  }
  // 创建View
  View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
          boolean ignoreThemeAttr) {
      // ......
      try {
          // 创建我们的View
          View view;
          if (mFactory2 != null) {
              // 先通过mFactory2 创建,其实在 AppCompatActivity里面会走这个方法,也就会去替换某些控件
              // 所以我们就 看到了上面的内容
              view = mFactory2.onCreateView(parent, name, context, attrs);
          } else if (mFactory != null) {
              // 走mFactory 
              view = mFactory.onCreateView(name, context, attrs);
          } else {
              view = null;
          }
          // ......省略
          if (view == null) {
              final Object lastContext = mConstructorArgs[0];
              mConstructorArgs[0] = context;
              try {
                  // 判断是不是自定义View,自定义View在布局文件中com.hc.BannerView是个全类名,
                  // 而系统的View在布局文件中不是全类名 TextView
                  if (-1 == name.indexOf('.')) {
                      view = onCreateView(parent, name, attrs);
                  } else {
                      view = createView(name, null, attrs);
                  }
              } finally {
                  mConstructorArgs[0] = lastContext;
              }
          }

          return view;
      } catch (InflateException e) {
          // ........
      }
  }
  // 创建View
  public final View createView(String name, String prefix, AttributeSet attrs)
          throws ClassNotFoundException, InflateException {
      // 做一些反射的性能优化

      try {
          // 先从缓存中拿,这是没拿到的情况
          if (constructor == null) {
              // Class not found in the cache, see if it's real, and try to add it
              // 加载 clazz
              clazz = mContext.getClassLoader().loadClass(
                      prefix != null ? (prefix + name) : name).asSubclass(View.class);
              
              // 创建View的构造函数
              constructor = clazz.getConstructor(mConstructorSignature);
              constructor.setAccessible(true);
              // 加入缓存集合集合
              sConstructorMap.put(name, constructor);
          } else {
              
          }
          // 通过反射创建View
          final View view = constructor.newInstance(args);
          return view;

      } catch (NoSuchMethodException e) {
          // ......省略部分代码
      }
  }

setContentView并没有调用了控件的测量 onMeature,只是创建了decorView

Activity的启动流程: performLaunchActivity -> Activity.onCreate()。
handleResumeActivity() -> performResumeActivity() -> Activity的onResume()方法
-> wm.addView(decor, l); 才开始把我们的 DecorView 加载到 WindowManager, -> View的绘制流程在这个时候才开始 measure() layout() draw() 。

你可能感兴趣的:(安卓源码-setContentView()源码分析)