Android显示框架:Activity应用视图的创建流程
关于作者
郭孝星,非著名程序员,主要从事Android平台基础架构与中间件方面的工作,欢迎交流技术方面的问题,可以去我的Github提交Issue或者发邮件至[email protected]与我联系。
文章目录
- 一 创建Context对象
- 二 创建Window对象
- 三 创建View对象
- 四 创建WindowState对象
- 五 创建Surface对象
Android应用在运行的过程中需要访问一些特定的资源和类,这些特定的资源或者类构成了Android应用运行的上下文环境,即Context。Context是一个抽象类,ContextImpl继承了Context,
并实现它的抽象方法。
因此,每个Activity组件关联的是ContextImpl对象,它们的类图关系如下:
Context家族相关类采用装饰模式设计而成,ContextWrapper与ContextThemeWrapper继承于Context,是它的包装类,用于完成更多的功能。ContextWrapper与ContextThemeWrapper背部都通过
成员变量mBasae引用了一个ContextImpl对象,Activity正是通过这个ContextImpl对象执行一些具体的操作,例如:启动Activity、启动Service等。
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
比较有意思的是,ContextImpl内部也有一个mOuterContext对象,它在自己初始化的时候传入,它引用的正是与它关联的Activity,这样它也可以把一些操作转交给Activity。
private Context mOuterContext;
ContextImpl() {
mOuterContext = this;
}
一 创建Context对象
我们之前分析过Activity的启动流程,可以得知这个流程的最后一步是调用ActivityThread.perforLaunchActivity()方法在应用进程中创建一个Activity实例,并为它蛇者一个
上下文环境,即创建一个ContexImpl对象。
ContexImpl的创建流程如下所示:
主要角色:
- Instrumenttation:记录应用与系统的交互过程
- Contextrapper: ContextImpl的代理类,包装了ContextImpl里的相关操作。
- ContextThemeWrapper:用来维护一个应用的窗口主题
整个流程还是比较简单清晰的,我们着重分析里面的关键点。
关键点1:ActivityThread.performLaunchActivity(ActivityClientRecord r, Intent customIntent)
这个方法完成了Activity启动以及ContextImpl创建的主要流程,它完成的工作有:
- 1 从Intent中获取Activity的组件名ComponentName,调用对应的类加载器进行加载,调用Activity的默认构造方法进行实例创建。
- 2 调用ContextImpl的构造方法创建ContextImpl,并调用ContextImpl.setOuterContext()方法将已经创建完成的Activity关了给ContextImpl。
- 3 调用Activity.attach()关联上下文信息、Activity信息、Intent信息等Activity运行所需要的信息。
- 4 调用InstrumentationcallActivityOnCreate(),通知Activity你已经被创建,相关环境与信息也已经准备好,可以执行你的onCreate()方法辣,接着Activity就去执行它的onCreate()方法了。
public final class ActivityThread {
private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo,
Context.CONTEXT_INCLUDE_CODE);
}
//1 从Intent中获取Activity的组件名ComponentName,调用对应的类加载器进行加载,调用
//Activity的默认构造方法进行实例创建。
ComponentName component = r.intent.getComponent();
if (component == null) {
component = r.intent.resolveActivity(
mInitialApplication.getPackageManager());
r.intent.setComponent(component);
}
if (r.activityInfo.targetActivity != null) {
component = new ComponentName(r.activityInfo.packageName,
r.activityInfo.targetActivity);
}
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
r.intent.setExtrasClassLoader(cl);
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 (localLOGV) Slog.v(TAG, "Performing launch of " + r);
if (localLOGV) Slog.v(
TAG, r + ": app=" + app
+ ", appName=" + app.getPackageName()
+ ", pkg=" + r.packageInfo.getPackageName()
+ ", comp=" + r.intent.getComponent().toShortString()
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
//2 调用ContextImpl的构造方法创建ContextImpl,并调用ContextImpl.setOuterContext()方法将已经创建完成的Activity关了给ContextImpl。
ContextImpl appContext = new ContextImpl();
appContext.init(r.packageInfo, r.token, this);
appContext.setOuterContext(activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
//3 调用Activity.attach()关联上下文信息、Activity信息、Intent信息等Activity运行所需要的信息。
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstance,
r.lastNonConfigurationChildInstances, config);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstance = null;
r.lastNonConfigurationChildInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
mInstrumentation.callActivityOnCreate(activity, r.state);
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
if (!r.activity.mFinished) {
if (r.state != null) {
//4 调用InstrumentationcallActivityOnCreate(),通知Activity你已经被创建,相关环境与信息也已经准备好,可以执行
// 你的onCreate()方法辣,接着Activity就去执行它的onCreate()方法了。
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
if (!r.activity.mFinished) {
activity.mCalled = false;
mInstrumentation.callActivityOnPostCreate(activity, r.state);
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onPostCreate()");
}
}
}
r.paused = true;
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
}
}
return activity;
}
}
关键点2:Activity.attach()
Activity在被类加载器加载时调用的是默认的构造方法,这个方法什么都没有做,只是创建了个实例,真正的初始化流程在attach()方法里完成。
你可以看到attach()方法会调用ContextWrapper.attachBaseContext(context)进一步设置Context信息,这个方法就是将创建的ContextImpl赋值
给它的成员变量mBase。
除此之外,它还做了两件事:
- 1 调用PolicyManager.makeNewWindow(this)创建了应用窗口Window,它实际是个PhoneWindow对象,它会接收一些事件,例如:键盘、触摸事件,它会
转发这些事件给它关联的Activity,转发操作通过Window.Callback接口实现。 - 2 将Activity运行的一些关键信息带入Activity。
后续的UI绘制就砸Window上完成,并被Window设置了WindowManager。
public class Activity extends ContextThemeWrapper{
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,
Object lastNonConfigurationInstance,
HashMap<String,Object> lastNonConfigurationChildInstances,
Configuration config) {
attachBaseContext(context);
//1 调用PolicyManager.makeNewWindow(this)创建了应用窗口Window,它实际是个PhoneWindow对象,它会接收一些事
// 件,例如:键盘、触摸事件,它会转发这些事件给它关联的Activity,转发操作通过Window.Callback接口实现。
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
mUiThread = Thread.currentThread();
//2 将Activity运行的一些关键信息带入Activity。
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstance = lastNonConfigurationInstance;
mLastNonConfigurationChildInstances = lastNonConfigurationChildInstances;
mWindow.setWindowManager(null, mToken, mComponent.flattenToString());
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
}
以上便是ContextImpl对象创建过程的一些关键点,还是比较简单的,我们再来总结一下。
1 一个Android应用窗口的运行上下文环境是使用一个ContextImpl对象来描述的,这个ContextImpl对象会分别保存在Activity类的
父类ContextThemeWrapper和ContextWrapper的成员变量mBase中,即ContextThemeWrapper类和ContextWrapper类的成员变量
mBase指向的是一个ContextImpl对象。
2 Activity组件在创建过程中,即在它的成员函数attach被调用的时候,会创建一个PhoneWindow对象,并且保存在成员变量mWindow
中,用来描述一个具体的Android应用程序窗口。
3 Activity组件在创建的最后,即在它的子类所重写的成员函数onCreate中,会调用父类Activity的成员函数setContentView来创建
一个Android应用程序窗口的视图。
二 创建Window对象
从上面的Activity.attach()方法的分析我们得知了ContextImpl的创建流程,我们发现它不仅创建了上下文环境Context,它还创建了Window对象,用来描述一个具体的应用窗口,可以看出
Activity只不过是一个高度抽象的UI组件,它的具体UI实现是由它的一系列对象来完成的,它们的类图关系如下所示:
从上文的描述我们可以知道,Windows是在Activity的attach()方法中开始创建的,我们来看下它的创建流程。
主要角色:
- PhoneWindow:Window的子类,应用视图窗口。
- WindowManagerImpl:实现了WIndowManager接口,用来管理窗口。
关键点1:PhoneWindow(Context context)
PolicyManager.makeNewWindow(this)用来创建Window对象,该函数通过反射最终调用Policy.makeNewWindow(Context context),在这个
方法里调用了PhoneWindow的构造函数,返回了一个PhoneWindow对象。
在PhoneWindow的构造函数里,我们很惊奇的发现它返回了一个LayoutInflater对象。这货就是我们用来绘制xml里面UI的东西。
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
PhoneWindow其实就是我们最终要用的视图窗口了,除了mLayoutInflater,它里面还有两个重要的成员变量:
- private DecorView mDecor:顶级View视图,它由mLayoutInflater来创建。
- private ViewGroup mContentParent:视图容器。
关键点2:Window.setCallback(this)
Activity实现了Window.Callback接口,将Activity关联给Window,Window就可以将一些事件交由Activity处理,具体有哪些事情呢?
public interface Callback {
//键盘事件分发
public boolean dispatchKeyEvent(KeyEvent event);
//触摸事件分发
public boolean dispatchTouchEvent(MotionEvent event);
//轨迹球事件分发
public boolean dispatchTrackballEvent(MotionEvent event);
//可见性事件分发
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
//创建Panel View
public View onCreatePanelView(int featureId);
//创建menu
public boolean onCreatePanelMenu(int featureId, Menu menu);
//画板准备好时回调
public boolean onPreparePanel(int featureId, View view, Menu menu);
//menu打开时回调
public boolean onMenuOpened(int featureId, Menu menu);
//menu item被选择时回调
public boolean onMenuItemSelected(int featureId, MenuItem item);
//Window Attributes发生变化时回调
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs);
//Content View发生变化时回调
public void onContentChanged();
//窗口焦点发生变化时回调
public void onWindowFocusChanged(boolean hasFocus);
//Window被添加到WIndowManager时回调
public void onAttachedToWindow();
//Window被从WIndowManager中移除时回调
public void onDetachedFromWindow();
*/
//画板关闭时回调
public void onPanelClosed(int featureId, Menu menu);
//用户开始执行搜索操作时回调
public boolean onSearchRequested();
}
关键点3:Window.setSoftInputMode(int mode)
这个我们就比较熟悉了,我们会在AndroidManifest.xml里Activity的标签下设置android:windowSoftInputMode=”adjustNothing”,来控制输入键盘显示行为。
可选的有6个参数,源码里也有6个值与之对应:
- SOFT_INPUT_STATE_UNSPECIFIED:没有指定软键盘输入区域的显示状态。
- SOFT_INPUT_STATE_UNCHANGED:不要改变软键盘输入区域的显示状态。
- SOFT_INPUT_STATE_HIDDEN:在合适的时候隐藏软键盘输入区域,例如,当用户导航到当前窗口时。
- SOFT_INPUT_STATE_ALWAYS_HIDDEN:当窗口获得焦点时,总是隐藏软键盘输入区域。
- SOFT_INPUT_STATE_VISIBLE:在合适的时候显示软键盘输入区域,例如,当用户导航到当前窗口时。
- SOFT_INPUT_STATE_ALWAYS_VISIBLE:当窗口获得焦点时,总是显示软键盘输入区域。
关键点4: Window.setWindowManager(WindowManager wm, IBinder appToken, String appName)
public void setWindowManager(WindowManager wm,
IBinder appToken, String appName) {
mAppToken = appToken;
mAppName = appName;
if (wm == null) {
wm = WindowManagerImpl.getDefault();
}
mWind