本文基于Android 7.0
窗口Window是个抽象的概念,window都是由view来呈现,每一个activity都对应着一个window。
先看下activity和window关系的类图
我们知道一个Android app的入口是在ActivityThread,即应用主线程,在该线程对activity进行调度管理。
在startActivity的过程中,会调用到ActivityThread的handleLaunchActivity方法
frameworks/base/core/java/android/app/ActivityThread.java
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
Activity a = performLaunchActivity(r, customIntent);
...
}
handleLaunchActivity方法调用了performLaunchActivity方法
frameworks/base/core/java/android/app/Activity.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
...
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
}
...
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
...
}
return activity;
}
这里主要做了三件事情,一是通过ClassLoader创建activity对象,二是调用activity的attach方法,三是callActivityOnCreate开始activity的生命周期。
先看下activity的attach方法
frameworks/base/core/java/android/app/Activity.java
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
回到performLaunchActivity方法做的第三件事callActivityOnCreate
frameworks/base/core/java/android/app/Instrumentation.java
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
frameworks/base/core/java/android/app/Activity.java
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
这里直接调用到了activity的onCreate方法,生命周期开始了。
一般我们在写activity时,在oncreate方法中都会通过setcontentView方法去设置我们的布局,如下
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
下面看下setContentView的过程
frameworks/base/core/java/android/app/Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
setContentView实际上是通过PhoneWindow的setContentView实现
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
这里主要做了三件事,1是创建decorview,而是通过mLayoutInflater去加载布局到mContentParent容器,三是回调activity的onContentChanged
先看下创建decorview
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
...
return new DecorView(context, featureId, this, getAttributes());
}
该方法比较简单就是在PhoneWindow中new了一个DecorView,这样最外层的DecorView就创建好了,接着继续加载content部分
回到PhoneWindow中setContentView,第一件事创建DecorView完成之后,接着第二件事情通过mLayoutInflater去加载布局到mContentParent容器
frameworks/base/core/java/android/view/LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
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) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
frameworks/base/core/java/android/view/LayoutInflater.java
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
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(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
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 against its context.
rInflateChildren(parser, temp, attrs, 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) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
这里面的root对象是我们的mContentParent,可以看到解析出一个view之后,mContentParent就调用addview方法把view添加进来,这样content部分就创建完了。
到这里我们看到,最外层的decorview和content部分的view都创建好了,decorview是什么时候显示出来呢?
显示的时机是在activity onresume之后。
回到handleLaunchActivity方法
frameworks/base/core/java/android/app/ActivityThread.java
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
...
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
...
} else {
...
}
}
frameworks/base/core/java/android/app/ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
...
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
...
} else {
...
}
}
这里首先调用performResumeActivity执行activity的onresume方法,然后通过windowmanger的addview方法把decorview添加到window中。这样view就能显示出来了,
到这里activity的window就创建完成了。
另外这里我们也可以看到,这里我们的window设置了TYPE_BASE_APPLICATION的属性,实际上Android中总共包含3种window:APPLICATION_WINDOW、SUB_WINDOW和SYSTEM_WINDOW
frameworks/base/core/java/android/view/WindowManager.java
public interface WindowManager extends ViewManager {
...........
public static class LayoutParams extends ViewGroup.LayoutParams
implements Parcelable {
//应用窗口
public static final int FIRST_APPLICATION_WINDOW = 1;
public static final int TYPE_BASE_APPLICATION = 1;
public static final int TYPE_APPLICATION = 2;
public static final int TYPE_APPLICATION_STARTING = 3;
public static final int LAST_APPLICATION_WINDOW = 99;
//子窗口
public static final int FIRST_SUB_WINDOW = 1000;
// 面板窗口,显示于宿主窗口的上层
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
// 媒体窗口(例如视频),显示于宿主窗口下层
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;
// 应用程序窗口的子面板,显示于所有面板窗口的上层
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
//对话框窗口
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4;
public static final int LAST_SUB_WINDOW = 1999;
//系统窗口
public static final int FIRST_SYSTEM_WINDOW = 2000;
//状态栏,只能有一个状态栏,位于屏幕顶端,其他窗口都位于它下方
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
//搜索栏,只能有一个搜索栏,位于屏幕上方
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
//电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
//系统警告提示窗口,出现在应用程序窗口之上
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
//锁屏窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
//信息窗口,用于显示Toast
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
//系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
//电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
//系统对话框窗口
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
//锁屏时显示的对话框
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
//系统内部错误提示,显示在任何窗口之上
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
//内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
//内部输入法对话框,显示于当前输入法窗口之上
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
//墙纸窗口
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
//状态栏的滑动面板
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
//安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘
public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
//最后一个系统窗口
public static final int LAST_SYSTEM_WINDOW = 2999;
........
}
}