您的位置:首页 > 其它

Activity setContentView() 方法浅析

2017-03-22 14:34 330 查看

Activity setContentView() 方法浅析

Android开发中,众所周知在新创建一个Activity都会覆写Activity的生命周期里面的onCreate(Bundle savedInstanceState),在onCreate方法里调用setContentView(int layoutResID)方法加载xml布局文件并显示出来,接下来从源码去分析xml文件是如何被加载显示的。此次分析的源码是Android5.0的源码,源码查看的工具是Source Insight。

Android5.0源码分享地址:https://pan.baidu.com/s/1o79x4ka

Source Insight 下载地址:https://www.sourceinsight.com/download/

Source Insight的安装使用可参考http://www.2cto.com/kf/201601/486897.html这篇博客。

首先我们从Activity里面开始分析,在Activity里面查看setContentView()方法。源码

frameworks\base\core\java\android\app\Activity.java 里面提供的方法如下:

/**
* Set the activity content from a layout resource.  The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
// 初始化ActionBar
initWindowDecorActionBar();
}


从上面的代码片段里面可以看到Activity类里面setContentView共调用了两个方法,我们先看

initWindowDecorActionBar()
方法,代码片段如下:

/**
* Creates a new ActionBar, locates the inflated ActionBarView,
* initializes the ActionBar with the view, and sets mActionBar.
*/
private void initWindowDecorActionBar() {
Window window = getWindow();

// Initializing the window decor can change w
4000
indow feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView();

if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
//初始化ActionBar
mActionBar = new WindowDecorActionBar(this);
//ActionBar 左上角图标的左边加上一个返回的图标 。对应ActionBar.DISPLAY_HOME_AS_UP
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}


从上面的代码里面我们都看到了一个重要的方法getWindow(),下面我们看一下这个方法:

/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
*         visual.
*/
public Window getWindow() {
return mWindow;
}


至此我们看到了一个重要的变量mWindow,我们知道Window是个抽象类,所以我们需要找到这个window的子类,下面我们看看这个变量是在什么地方赋值的,接下来我们在Activity里面搜索attach()方法,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, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);

mFragments.attachActivity(this, mContainer, null);
//初始化window对象
mWindow = PolicyManager.makeNewWindow(this);

...省略其他代码

mUiThread = Thread.currentThread();
//ActivityThread对象赋值
mMainThread = aThread;
mInstrumentation = instr;

...省略其他代码

mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}


上面这段代码我们可以看到mWindow被初始化了通过PolicyManager.makeNewWindow(this)获得Window对象,我们跟踪PolicyManager.makeNewWindow(this)这个方法,找到

frameworks\base\core\java\com\android\internal\policy\PolicyManager.java,可以看到:

public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";

private static final IPolicy sPolicy;

static {
// Pull in the actual implementation of the policy at run-time
try {
//sPolicy 对象初始化
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
} catch (InstantiationException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
}
}

// Cannot instantiate this class
private PolicyManager() {}

// The static methods to spawn new policy-specific objects
public static Window makeNewWindow(Context context) {
//通过sPolicy.makeNewWindow创建一个window对象。
//sPolicy则在上面的静态代码块中被实例化
return sPolicy.makeNewWindow(context);
}

public static LayoutInflater makeNewLayoutInflater(Context context) {
return sPolicy.makeNewLayoutInflater(context);
}

public static WindowManagerPolicy makeNewWindowManager() {
return sPolicy.makeNewWindowManager();
}

public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
return sPolicy.makeNewFallbackEventHandler(context);
}


上面的代码中我们可以看到PolicyManager 类里面通过sPolicy.makeNewWindow(context)创建一个window,而IPolicy 这个接口的对象sPolicy则在静态代码块中被实例化,代码如下:

private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";

private static final IPolicy sPolicy;

static {
// Pull in the actual implementation of the policy at run-time
try {
//sPolicy 对象初始化
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
} catch (InstantiationException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
}
}


根据POLICY_IMPL_CLASS_NAME创建Policy类的对象,Policy实现了IPolicy接口即获取了IPolicy的对象sPolicy 我们接下来再看Policy 这个类。frameworks\base\core\java\com\android\internal\policy\impl\Policy.java, 源码如下:

public class Policy implements IPolicy {
private static final String TAG = "PhonePolicy";

private static final String[] preload_classes = {
"com.android.internal.policy.impl.PhoneLayoutInflater",
"com.android.internal.policy.impl.PhoneWindow",
"com.android.internal.policy.impl.PhoneWindow$1",
"com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback",
"com.android.internal.policy.impl.PhoneWindow$DecorView",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
};

static {
// For performance reasons, preload some policy specific classes when
// the policy gets loaded.
for (String s : preload_classes) {
try {
Class.forName(s);
} catch (ClassNotFoundException ex) {
Log.e(TAG, "Could not preload class for phone policy: " + s);
}
}
}

public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}

public LayoutInflater makeNewLayoutInflater(Context context) {
return new PhoneLayoutInflater(context);
}

public WindowManagerPolicy makeNewWindowManager() {
return new PhoneWindowManager();
}

public FallbackEventHandler makeNewFallbackEventHandler(Context context) {
return new PhoneFallbackEventHandler(context);
}


代码里面makeNewWindow方法里面返回了PhoneWindow对象,这里我们就清楚的明白了Activity类里面的mWindow对象就是PhoneWindow的实例,getWindow()方法返回的就是PhoneWindow,Activity里面setContentView里面最终是通过getWindow().setContentView()方法去加载xml布局文件,所以接下我们就去看看PhoneWindow类里面的setContentView()是怎么实现的。在

frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindow.java这个类,可看到如下源码:

@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}

if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//inflate 解析xml布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}


上面代码中我们可以看到mLayoutInflater.inflate(layoutResID, mContentParent)方法,通过inflate()去解析xml布局文件,接下来我们就要去看看我们常用的LayoutInflater这个类,看看这个类里面inflate()方法是如何解析xml布局文件的。frameworks\base\core\java\android\view\LayoutInflater.java,我们看看这个类

public View inflate(int resource, ViewGroup root)

public View inflate(XmlPullParser parser, ViewGroup root)

public View inflate(int resource, ViewGroup root, boolean attachToRoot)

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)


4个inflate()方法我们看最后一个的具体实现

/**
* Inflate a new view hierarchy from the specified XML node. Throws
* {@link InflateException} if there is an error.
* <p>
* <em><strong>Important</strong></em>   For performance
* reasons, view inflation relies heavily on pre-processing of XML f
101b8
iles
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
*
* @param parser XML dom node containing the description of the view
*        hierarchy.
* @param root Optional view to be the parent of the generated hierarchy (if
*        <em>attachToRoot</em> is true), or else simply an object that
*        provides a set of LayoutParams values for root of the returned
*        hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
*        the root parameter? If false, root is only used to create the
*        correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
*         attachToRoot is true, this is root; otherwise it is the root of
*         the inflated XML file.
*/
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
View result = root;

try {
// Look for the root node.
int type;
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!");
}

final String name = parser.getName();

if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}

if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}

rInflate(parser, root, attrs, false, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, attrs, false);

ViewGroup.LayoutParams params = null;

if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}

if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp
rInflate(parser, temp, attrs, true, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}

// 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 (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (IOException e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

return result;
}
}


上面的代码中可看到,通过XmlPullParser的parser获取xml中的TAG名称name,然后判断name,

if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, attrs, false, false);
}


如果name等于merge标签则代用rInflate(parser, root, attrs, false, false)方法,否则

else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, attrs, false);

....省略其他代码

// 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);
}
}


看到这里我们大概明白了通过createViewFromTag创建View,然后root.addView()将view添加进去。

接下来我们看看createViewFromTag是如何创建View的,

/**
* Creates a view from a tag name using the supplied attribute set.
* <p>
* If {@code inheritContext} is true and the parent is non-null, the view
* will be inflated in parent view's context. If the view specifies a
* <theme> attribute, the inflation context will be wrapped with the
* specified theme.
* <p>
* Note: Default visibility so the BridgeInflater can override it.
*/
View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}

...省略其他代码

try {
View view;

...省略其他代码

if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
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) {
throw e;

} catch (ClassNotFoundException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;

} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
}
}


在createViewFromTag方法里面可以看到view是通过createView(name, null, attrs)方法创建的

view = createView(name, null, attrs);


看看createView方法的实现

/**
* Low-level function for instantiating a view by name. This attempts to
* instantiate a view class of the given <var>name</var> found in this
* LayoutInflater's ClassLoader.
*
* <p>
* There are two things that can happen in an error case: either the
* exception describing the error will be thrown, or a null will be
* returned. You must deal with both possibilities -- the former will happen
* the first time createView() is called for a class of a particular name,
* the latter every time there-after for that class name.
*
* @param name The full name of the class to be instantiated.
* @param attrs The XML attributes supplied for this instance.
*
* @return View The newly instantiated view, or null.
*/
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;

try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
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);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);

boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}

Object[] args = mConstructorArgs;
args[1] = attrs;

constructor.setAccessible(true);
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;

} catch (NoSuchMethodException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;

} catch (ClassCastException e) {
// If loaded class is not a View subclass
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()));
ie.initCause(e);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}


createView方法最终创建对应的View控件。到来这里整个View的解析加载就完成了。

总结一下,Activity里面xml文件的加载是通过setContentView方法,Activity的setContentView方法调用了PhoneWindow的setContentView方法,PhoneWindow里面调用LayoutInflate类里面的inflate()方法通过XmlPullParser解析xml布局文件,获取布局文件里面的TAG标签,根据TAG标签新建相应的View,再将view添加到DecorView的ViewGroup里面显示。写的不好请多担待。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: