Android创建窗口(一)创建应用窗口
所谓的窗口(Window)就是一个显示在手机屏幕上可视化视图的一片区域。在Android中窗口是一个抽象的概念,每一个Activity就对应着一个窗口,而所有的窗口都是由视图(View)来呈现,而我们知道View构成的一个树形结构的视图就组成了一个Activity的界面了。在android系统中窗口分为三个类型:
- 应用窗口:所谓应用窗口指的就是该窗口对应一个Activity,因此,要创建应用窗口就必须在Activity中完成了。
- 子窗口:所谓子窗口指的是必须依附在某个父窗口之上。
- 系统窗口:所谓系统窗口指的是由系统进程创建,不依赖于任何应用或者不依附在任何父窗口之上。
android是如何创建应用窗口的呢,下面来逐步分析:
startActivty的启动过程这里就不分析了,具体可以参考http://gityuan.com/2016/03/12/start-activity/,
这里从创建Activity来分析。
1.每个应用窗口都对应一个Activity对象,因此,要创建一个应用窗口,都必须创建一个activity对象。当ActivityManagerService(下面简称AMS)准备启动一个Activty时,会去通知app进程,每个app进程都会对应一个ActivtyThread类,任何Activty都隶属于App进程的,于是,启动Activty的任务就交给了ActivtyThread,
启动一个Activty,首先得创建一个Activty的对象,这个工作是在ActivtyThread.performLaunchActivity中完成的,下面看一下源码:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
//创建Activity对象
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);
}
}
...
return activity;
}
删除了一些我不是很关注的代码。可以看出android是通过ClassLoader来加载所要启动的Activty类的,这就是为什么我们要在manifest文件中配置Activty的原因(ClassLoader加载class时,必须要知道class文件的名字)。下面是newActivity的实现,通过classname去程序文件中加载对应的activty类。
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
2.得到activty对象后,接着调研activity的attach方法,这个方法还是在ActivtyThread.performLaunchActivity中完成的:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
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);
...
return activity;
}
attach的作用是为创建好的activity设置内部变量,这些变量是以后对activity调度所必需的。
3.在attach内部除了设置变量外,还有另一个任务,就是创建一个PhoneWindow对象,
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*/);
//创建PhoneWindow
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
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;
}
创建PhoneWindow后调运 mWindow.setCallback(this),将window的callback设置为当前activty,这就是为什么用户操作能传给activty。
4.创建完Window后,就是给Window对象中的mWindowManager赋值。mWindowManager是Window类中一个类型为WindowManager的对象,WindowManager是一个接口,它的实现类有WindowManagerImpl。通过(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)来设置这个对象。
5.配置好activty和window后,就开始给窗口添加view了,还是在ActivtyThread.performLaunchActivity中,在以上步骤完成之后,调运callActivityOnCreate方法,
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
//Instrumentation.java
public void callActivityOnCreate(Activity activity, Bundle icicle,
PersistableBundle persistentState) {
prePerformCreate(activity);
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}
//Activty.java
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
可以看出这里就会调用Activty的onCreate,这个我们应该很熟悉了。后面会接着调:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
setContenView实际上会调运
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
其实质上就是调运PhoneWindow的setContentView。
6.接着分析PhoneWindow的setContentView
@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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
首先执行的是installDecor(),installDecor()为Window创建一个窗口修饰,所谓的窗口修饰就是界面上常见的标题栏,代码指点的layout.xml将被包含在窗口修饰中,称之为窗口内容。窗口修饰及其内部的窗口内容就是我们通常说的窗口。如图:

创建完后,就是把layout.xml设置个mContentParent,即id=content的FrameLayout。最后,调运cb.onContentChanged()来通知Activty窗口的内容发生了变化。cb是在之前提到的给Window设置的callback。
7.接下来ActivityThread调用了handleResumeActivity(),首先调用performResumeActivity(),辗转调用了Activity的onResume(),接着会调用Activity的makeVisible()。该方法及后续的各种调用将完成真正的把窗口添加进WmS之中。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
8.之前说到WindowManager只是一个接口类,真正的实现是WindowManagerImpl,因此真正的addView操作就在此类中。然而它又交给了WindowManagerGlobal处理。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Overridepublic
void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
9.WindowManagerGlobal的addView过程,一个应用程序内部无论有多少个Activity, 但只有一个WindowManagerGlobal对象在 WindowManagerGlobal类中维护三个集合,用于保存该应用程序中所拥有的窗口的状态。而后调用ViewRootImpl.setView(),完成最后的、真正意义上的添加工作。
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
throw e;
}
}
10.ViewRootImpl.setView方法篇幅相对较长,其主要执行的操作有
- 给 ViewRoot的重要变量赋值;
- 调用requestLayout(),发出界面重绘请求;进而执行scheduleTraversals(),进行View的绘制工作。
- 调用sWindowSession.addToDisplay(),通知WmS显示View。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是说Window的添加过程是一次IPC调用。添加请求交给WmS去处理了。
到此为止,从客户端的角度来讲,已经完成了窗口创建的全部工作。从而界面才会呈现到用户面前。可见在onCreate的时候View并没有做测量、布局、绘制操作,因此无法获取到宽高数据。
ViewRootImpl
- 链接WindowManager和DecorView的纽带,更广一点可以说是Window和View之间的纽带。
- 完成View的绘制过程,包括measure、layout、draw过程。
- 向DecorView分发收到的用户发起的event事件,如按键,触屏等事件
Android创建窗口(一)创建应用窗口的更多相关文章
- 创建一个弹出DIV窗口
创建一个弹出DIV窗口 摘自: http://www.cnblogs.com/TivonStone/archive/2012/03/20/2407919.html 创建一个弹出DIV窗口可能是现在 ...
- Duilib创建窗口双击标题栏禁止窗口最大化
使用Duilib创建窗口并禁止窗口最大化 第一步: XXXFrame.Create(NULL, _T("XXXFrame"), UI_WNDSTYLE_EX_FRAME, WS_E ...
- uCGUI窗口的创建过程分析
一.相关结构体和变量 窗口管理结构体 /* 窗口管理结构体 共30个字节 */ struct WM_Obj { GUI_RECT Rect; //窗口尺寸(x0,y0,x1,y1) 8个字节 GUI_ ...
- Cinder-2 窗口的创建过程
通过TinderBox生成的代码很简单,整个代码如下: #include "cinder/app/AppNative.h" #include "cinder/gl/gl. ...
- win32 api Windows窗口的创建
windows窗口的创建有以下几个步骤: 1.创建注册窗口类 2.创建窗口句柄 3.显示更新窗口 4.消息循环 1.创建注册窗口类 所谓创建窗口类就是定义一个WNDCLASS类对象,并将该对象进行初始 ...
- vc++窗口的创建过程(MFC消息机制的经典文章)
一.什么是窗口类 在Windows中运行的程序,大多数都有一个或几个可以看得见的窗口,而在这些窗口被创建起来之前,操作系统怎么知道该怎样创建该窗口,以及用户操作该窗口的各种消息交给谁处理呢?所以VC ...
- Windows窗口的创建
Windows窗口创建的基本代码: #include <Windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); i ...
- 有谁知道Delphi中"窗口"的创建过程?
求助:有谁知道Delphi中窗口的创建过程,此“窗口”不仅仅指 TForm 类型, 还包括一般的窗口控件,如TButton,TEdit等等,希望有能够十分详细的运作 过程,比如说CreatPara ...
- Qt学习之对话框与主窗口的创建
Qt中的信号与槽机制 qt中槽和普通的C++成员函数几乎是一样的--可以是虚函数,可以被重载,可以是共有的,保护的或者私有的. 槽可以和信号连接在一起,在这种情况下,每当发射这个信号的信号,就会自动调 ...
- 【MFC】如何在MFC创建的程序中更改主窗口的属性 与 父窗口 WS_CLIPCHILDREN 样式 对子窗口刷新的影响 与 窗体区域绘制问题WS_CLIPCHILDREN与WS_CLIPSIBLINGS
如何在MFC创建的程序中更改主窗口的属性 摘自:http://blog.sina.com.cn/s/blog_4bebc4830100aq1m.html 在MFC创建的单文档界面中: (基于对话框的, ...
随机推荐
- 【WPF】如何获取命令行参数
应用程序以 Main 方法为入口点,如果为 Main 方法指定一个 string[] 类型的参数,在程序运行时,这个参数会获取到传递给应用程序的命令行参数. 而 WPF 应用程序比较特殊,因为在VS中 ...
- 在Visual Studio for Mac中使用fastlane管理iOS的provision
Xamarin开发中,最烦的就是provision的管理了. 全手工的话,要先创建一个key,上传后生成cert文件,再创建provision.如果在手机上调试,还要把手机加到provision中去. ...
- SQL SERVER 自动生成 MySQL 表结构及索引 的建表SQL
SQL SERVER的表结构及索引转换为MySQL的表结构及索引,其实在很多第三方工具中有提供,比如navicat.sqlyog等,但是,在处理某些数据类型.默认值及索引转换的时候,总有些 ...
- 快速压缩PNG文件在线工具
https://tinypng.com/ 直接拖移要压缩文件即可
- Java模拟http请求调用远程接口工具类
package ln; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamRea ...
- python 自定义回调函数
回调函数用起来比较爽.特别是在js中,满世界全是回调,那么在python中,怎么来优雅地实现自己的回调函数呢 下面贴一个我写的例子 class BaseHandler(object): def cra ...
- tomcat抬头有“选择”或“选定”,导致tomcat无法运行问题
2. 遇到tomcat抬头有"选择"或"选定",导致tomcat无法运行问题 解决:在tomcat抬头右键--属性,去掉"快速编辑模式"勾选 ...
- mongodb入门笔记
mongodb作为nosql中排名第一的数据库,近年来使用的人数越来越多,作为开发人员,非常有必要了解下mongodb数据库.下面就给大家介绍下mongodb数据库的基本知识,有不对的地方欢迎指正,Q ...
- C#实现手机发送验证码
之前不怎么了解这个,一直以为做起来很复杂. 直到前两天公司要求要做这个功能. 做了之后才发现 这不过就是一个POST请求就能实现的东西.现在给大家分享一下,有不足之处还请多多指教. 废话不多说 直接上 ...
- 将百度坐标转换的javascript api官方示例改写成传统的回调函数形式
改写前: 百度地图中坐标转换的JavaScript API示例官方示例如下: var points = [new BMap.Point(116.3786889372559,39.90762965106 ...