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

7.Android内核 创建窗口的过程(二)

2015-08-19 23:25 579 查看

创建应用窗口

1.每个应用类窗口都对应一个Activity对象,所以,创建应用类窗口首先需要创建一个Activity对象。

当AmS决定启动某个Activity时,会通知客户端进程,而每个客户端进程都对应一个ActivityThread

类,任何Activity都必须隶属于一个应用程序,因此,启动Activity的任务最终由ActivityThread 完成。

启动某个Activity的代码本质是构造一个Activity对象,看看ActivityThread里的

performLaunchActivity()方法源码

private 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, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}

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);
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 (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) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.voiceInteractor);

if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}

activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
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.isPersistable()) {
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
if (!r.activity.mFinished) {
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnPostCreate(activity, r.state,
r.persistentState);
} else {
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;
}


以上,使用了ClassLoader从程序文件中装载指定的Activity对应的class文件

2.构造好指定的Activity对象后,接着调用Activity的attach()方法:

   attach()的作用是为刚刚构造好的Activity设置内部变量,这些变量是以后进行Activity调度所必

   须的。这些重要的变量包含:

        appContext:该对象将作为Activity的BaseContext。Activity的本质就是一个Context,而同时Activity

        有时继承于ContextWrapper,该类需要一个真正的Context对象,而这就是appContext,该对象使用

        new ContextImpl()方法创建。

        this:这就是指的是当前的ActivityThread对象,Activity对象内部可能会需要主程序的引用。

        r.token:r是一个ActivityRecord对象,其内部变量token的含义是AmS中的一个HistoryRecord对象。

        r.parent:一个Activity可以有一个父Activity,这种理念是为了允许把一个Activity嵌入到另一个Activity内部

        执行。在应用程序使用时,常用ActivityGroup类,而ActivityGroup功能的内部支持的正是该变量。

3.在attach()方法内部,除了进行重要变量赋值外,另一件重要的事情就是为该Activity创建Window对

,这是通过调用PolicyManager的静态方法makeNewWindow()完成的。

PolicyManager会根据com.android.intemal.policy.impLPolicy的配置创建不同产品类型的窗口。这仅

仅是一种程序设计的灵活性,其代码归根到底是创建了一个PhoneWindow对象而已。当前的Framework

中仅仅定义有两种Window的具体实现,一种是MidWindow类,另一种是PhoneWindow类,而前者基本

上没有使用。从名称不难看出,Android的设计之初所设想的两种应用,其中一种是手机,另一种是便携上

网设备(Mobile Internet Device)。

***创建好Window对象后,将其赋值给Activity的内部变量mWindow,并设置该Window的

Callback接口为当前的Activity对象,这就是为什么用户消息能够传递到Activity中的原因。那么

我们来看一看Activity中的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);

mWindow = PolicyManager.makeNewWindow(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;
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;
}


4.创建好Window对象后,需要给Window对象中的mWindowMananger变量赋值,该变量的类型

是WindowManager类。每一个Window内部都有一个WindowManager对象,WindowManager

类是一个重量级的类,如果每一个Window中都包含一个WindowManager的引用会不会是一种浪费?

但是实际上,WindowManager类仅仅是一个interface类而已,真正实现该接口的有两个类,一个是

Window.LocalWindowManager子类,另一个是WindowManagerImpl类LocalWindowManager

仅仅是一个壳而已,这就跟ContextWrapper类似,本身虽然提供了WindowManager接口的全部功

能,然而真正实现这些功能的却是壳里面的WindowManager对象这就是WindowManagerImpl类



也正是因为以上关系,所以,每个Window内部才包含一个WindowManager的壳以便进行其他操

作,在Activity里的attach()给mWindowManager赋值了:

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();


这里调用了Window.setWindowManager方法去设置该Activity对应的WindowManager,那我们

接着来看看Window.setWindowManager的源代码

/**
* Set the window manager for use by this Window to, for example,
* display panels.  This is <em>not</em> used for displaying the
* Window itself -- that must be done by the client.
*
* @param wm The window manager for adding new windows.
*/
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}


结合以上两段源代码来分析,setWindowManager()方法的第一个参数

(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),显而易见的

是,这里直接通过Context.getSystemService(String systemName)的方式取得了

WindowManager第二个参数是mToken,这正是AmS中Activity里对应的HistoryRecord的

Binder引用该变量将会作为Window中的mAppToken的值被保存下来

到了Window.setWindowManager中,还进行了一次if(wm==null)的判断,如果为空,则

继续用Context.getSystemService(String systemName)的方式再取一次WindowManager

但是通过mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this)

可以知道,传进来的无论是否是null的WindowManager引用,其实都是备胎都会转换成

WindowManagerImpl然后再去构造一个LocalWindowManager对象,最后保存在

mWindowManager中

*** 所以每一个Activity内部都有一个mWindowManager对象,这就是LocalWindowManager,和

Window类中的同名变量相同。

5.配置好了Activity和Window对象后,接下来就需要给该窗口添加真正的显示元素View或者

ViewGroup。这是从Activity.performLaunchActivity()内部调用callActivityOnCreate()开始的 

并会辗转到我们熟悉的Activity.onCreate()中。

我们都可以知道给Activity添加界面是在onCreate()方法中调用setContentView()方法,而该方

实际上却又调用到了其所对应的Window对象的setContentView方法,对此我们可以看看

Activity.setContentView()的源代码

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


这里,可以看到我们传进去的Layout对应的Id又被对应的Window拿去执行了setContentView了。

6.PhoneWindow.setContentView()源代码:

@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}


首先调用installDecor()方法Window类安装一个窗口装饰,所谓的窗口修饰就是界面上常见的

标题栏,程序中指定的layout.xml界面将被包含在窗口修饰中,称为窗口内容。窗口修饰也是一个

ViewGroup,窗口修饰及其内部的窗口内容就是我们所说的窗口,或者叫做Window的界面。



Framework中定义了多种窗口装饰,installDecor()源代码

private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);

mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
}
}
}

使用generateDecor()创建一个DecorView对象,并赋值给mDecor变量。该变量并不完全等同于窗口

修饰,窗口修饰是mDecor内部的唯一一个子视图。

根据用户指定的参数选择不同的窗口修饰,并把窗口修饰作为mDecor的子窗口,这是在

generateLayout()中调用mDecor.addView()完成的。

不同窗口的修饰的区别不大,比如是否有标题栏,是否显示左右进度条等,这些修饰窗口共同的

特点是其内部必须包含一个id=content的FrameLayout,因为内容窗口正是被包含在该FrameLayout

之中。常见的窗口修饰对应的XML文件如下,其路径为frameworks/base/core/res/res/layout。

   R.layout.dialog_title_icons

R.layout.screen_title_icons

R.layout.screen_progress

R.layout.dialog_custom_title

R.layout.screen_custom_title

R.layout.dialog_title

R.layout.screen_title

R.layout.screen_simple

安装完窗口修饰后,就可以把用户界面layout.xml文件添加到窗口修饰中,这是通过在

setContentView()中调用inflate()方法完成的,该方法的第二个参数正是mContentParent

即id=content的FrameLayout。

我们再看一看PhoneWindow.setContentView()源代码

@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}

最后,回调cb.onContentChanged()方法,通知应用程序窗口内容发生了改变,因为从无到

有了。

再来特别说明一点,所谓的“根据用户指定的参数”中“用户指定”两个地方可以指定:

第一个地方是在Activity的onCreate()方法中调用得到当前Window,然后调用requestFeature()指

定。generateLayout()方法中使用getLocalFeature()获取feature值,并根据这些值选择不同的窗口

修饰。

另一个地方是在AndroidMainfest.xml中Activity元素内部使用android:theme="XXX"指定。

generateLayout()方法中使用getWindowStyle()方法获取这些值,调用流程如下:

getWindowStyle():PhoneWindow中generateLayout()

->obtainStyleAttributes():Window中

->getTheme.obtainStyledAttributes():Context中

流程中的最后一个是调用getTheme(),而这正是使用android:theme赋值的结果

"用户指定"generateLayout()方法内部的流程可以总结如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: