您的位置:首页 > 移动开发 > Android开发

android中布局和View创建的源码分析---setContentView

2017-05-19 12:56 791 查看

android中布局和View创建的源码分析

因为我们使用的是拦截view创建的过程来实现插件换肤的功能,所以首先要熟悉android中创建视图的过程,下面让我们一起来分析下源码,这里我们从两个方面来分析。

一.设置布局分析

1.继承至Activity时,设置布局的情况

setContentView()的源码

public void setContentView(@LayoutRes int layoutResID) {
//重点看Window中setContentView方法
getWindow().setContentView(layoutResID);
...
}


我们知道getWindow()获取的是PhoneWindow的实例,所以我们来看下PhoneWindow中setContentView()的代码:

@Override
public void setContentView(int layoutResID) {

if (mContentParent == null) {
//1.初始化decorView,给它添加布局,初始化mContentParent
installDecor();
}

if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
//2.将我们自定义的布局添加到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}


我们从PhoneWindow代码中可以看到,mLayoutInflater.inflate(layoutResID, mContentParent)只是将我们自定义的布局添加到系统提供的布局中,而installDecor()完成了所有初始化布局的工作,所以我们的核心是分析installDecor()中的代码,

private void installDecor() {
...

if (mDecor == null) {
//1.初始化decorView,很简单
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//2.初始化mContentParent
mContentParent = generateLayout(mDecor);

...
}

...

}
}


installDecor其实也很简单,就是初始化decorView,然后在初始化mContentParent,我们先来看下初始化decorView的过程:

//其实就是实例化DecorView的对象
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}


那么DecorView又是什么呢?

public class DecorView extends FrameLayout ... {
...
}


我们看到DecorView其实就是一个FrameLayout,就这么简单,下面我们一起看下初始化mContentParent的过程:

protected ViewGroup generateLayout(DecorView decor) {

TypedArray a = getWindowStyle();

//根据当前主题初始化数据
...

//根据当前版本设置属性
...

//重点!!!:填充DecorView

int layoutResource;

int features = getLocalFeatures();

if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
//根据不同的特征,填充不同的布局(比如,有title的)
...
} else {
//默认最简单的布局,我们就分析这个布局
layoutResource = R.layout.screen_simple;
}

...

//将上面得到的布局添加到DecorView中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

//获得contentView,这个就是mContentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

...

return contentParent;
}


让我们在来看下screen_simple.xml 文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
//添加我们自定义布局的地方
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>


看到这里我们应该心中有个大概的过程了,window包裹着DecorView,DecorView添加的是系统的布局(比如,screen_simple.xml),系统的布局中mContentParent又添加我们自定义的布局,用一个图表示就是:



2.继承AppCompatActivity时,设置布局的情况

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

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

private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (BuildCompat.isAtLeastN()) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV9(context, window, callback);
}
}


可以看到AppCompatActivity中setContentView()兼容很多版本。

我们看源码就知道AppCompatDelegateImplN,AppCompatDelegateImplV23..最终继承的是AppCompatDelegateImplV9,所以来看下AppCompatDelegateImplV9源码:

@Override
public void setContentView(int resId) {
//1.初始化mSubDecor
ensureSubDecor();
//2.初始化contentParent
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//3.添加我们的布局到    contentParent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}

private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();

...
onSubDecorInstalled(mSubDecor);

...
}
}

private ViewGroup createSubDecor() {

final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;

//初始化subDecor
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);

final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);

// Now set the Window's content view with the decor
//将subDecor添加到decorView中
mWindow.setContentView(subDecor);

return subDecor;
}


我们看到AppCompatDelegateImplV9中的setContentView()的做的事情和PhoneWindow中是一样的,只是多了一层subDecor而已。

通过上面的分析,我们发现AppCompatActivity只是多了兼容的功能和布局多了subDecor,最后也是通过LayoutInflater.from(mContext).inflate(resId, contentParent);来添加我们自定义的布局,那么为什么继承Activity和AppCompatActivity显示的界面不一样?会不会是inflate()方法引起的呢?我们继续来分析。

二.创建View代码分析

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

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 = root;

try {

//xml解析
...

//重点:创建view

c24e
final View temp = createViewFromTag(root, name, inflaterContext, attrs);

ViewGroup.LayoutParams params = null;

// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}

// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}

} catch (Exception e) {

} finally {

}

return result;
}
}


我们看到创建View的地方就在createViewFromTag中,这边也是导致界面显示不一样的原因所在。

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {

...

try {
View view;
if (mFactory2 != null) {
//第一个创建view的地方
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
//第二个创建view的地方
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}

if (view == null && mPrivateFactory != null) {
//第三个创建view的地方
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}

if (view == null) {

try {
//第四个创建view的地方
if (-1 == name.indexOf('.')) {
//创建系统的view
view = onCreateView(parent, name, attrs);
} else {
//创建自定义的view
view = createView(name, null, attrs);
}
}
}

return view;
}
}


大家可以看到,上面有四种方式创建View,分别是mFactory2,mFactory,mPrivateFactory,默认方式,

这边就是导致Activity和AppCompatActivity显示不同的原因:

1. 如果是继承Activity,那么就使用默认的创建View的方式。

2. 如果是继承AppCompatActivity,那么就使用mFactory创建View的方式。

下面我们来证明上面的结论。

首先来看下Activity,因为Activity中没有设置mFactory2,mFactory,mPrivateFactory,所以它们都为null,所以直接调用默认创建View的方式。

再来看下AppCompatActivity是怎么给mFactory赋值的:

AppCompatActivity->onCreate

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
//这边就是设置mFactory的地方
delegate.installViewFactory();

...

}


上面注释的地方就是设置mFactory的地方,我们往下看:

AppCompatDelegateImplV9->installViewFactory

@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
//设置mFactory的地方
LayoutInflaterCompat.setFactory(layoutInflater, this);
} else {
...
}
}


我们找到最终设置mFactory的地方在LayoutInflaterCompatBase中

static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
}


LayoutInflater->setFactory

public void setFactory(Factory factory) {

if (mFactory == null) {
mFactory = factory;
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}


我们看到在这边设置了mFactory.

class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
implements MenuBuilder.Callback, LayoutInflaterFactory{

@Override
public final View onCreateView(View parent, String name,
Context context, AttributeSet attrs) {

return createView(parent, name, context, attrs);
}
}


在看上面的LayoutInflaterCompat.setFactory(layoutInflater, this);

可以看到mFactory.onCreateView()其实调用的就是AppCompatDelegateImplV9中的onCreateView()方法。

我们再来分析createView()方法:

public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;

if (inheritContext && parent != null) {
context = parent.getContext();
}

View view = null;
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
case "EditText":
view = new AppCompatEditText(context, attrs);
break;
case "Spinner":
view = new AppCompatSpinner(context, attrs);
break;
case "ImageButton":
view = new AppCompatImageButton(context, attrs);
break;
case "CheckBox":
view = new AppCompatCheckBox(context, attrs);
break;
case "RadioButton":
view = new AppCompatRadioButton(context, attrs);
break;
case "CheckedTextView":
view = new AppCompatCheckedTextView(context, attrs);
break;
case "AutoCompleteTextView":
view = new AppCompatAutoCompleteTextView(context, attrs);
break;
case "MultiAutoCompleteTextView":
view = new AppCompatMultiAutoCompleteTextView(context, attrs);
break;
case "RatingBar":
view = new AppCompatRatingBar(context, attrs);
break;
case "SeekBar":
view = new AppCompatSeekBar(context, attrs);
break;
}

if (view == null && originalContext != context) {

view = createViewFromTag(context, name, attrs);
}

if (view != null) {

checkOnClickListener(view, attrs);
}

return view;
}


可以看到AppCompatActivity显示的是view的兼容类(AppCompatTextView),这就导致了Activity和AppCompatActivity显示界面的不一样。

下面我们来看下Activity中创建View的代码:

LayoutInflater->createView

public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//1.获取view的构造函数(反射) ,为了提高性能,这边做了缓存
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);

if (constructor == null) {
//创建构造函数(反射)
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);

constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
}

...

Object[] args = mConstructorArgs;
args[1] = attrs;
//反射调用构造函数,创建view实例
final View view = constructor.newInstance(args);

return view;

}
} catch (Exception e) {

} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}


就边就是通过反射实例化View对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 布局 源码 插件
相关文章推荐