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将被包含在窗口修饰中,称之为窗口内容。窗口修饰及其内部的窗口内容就是我们通常说的窗口。如图:

9D8B1BC0-EBB3-4871-A44C-8F84D777D9F2.png

创建完后,就是把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事件,如按键,触屏等事件
posted @ 2017-07-17 20:05  Vonnie_Jade  阅读(4899)  评论(0编辑  收藏  举报