Activity的View界面是如何绘制出来的--Activity的Window创建过程

#####前言
有一定的Android开发经验后,我们逐渐了解到,Activity内部有一个DecorView,它是我们布局View的根View, 那么这个View是怎么呈现出来的呢,我们今天来简单的讲解一下。

在Android中, Window表示一个窗口。事实上,我们Activity的DecorView就是附加在这么一个Window窗口上的。从Android的事件传递机制中,就可以体现出来。当我们在屏幕上按下,事件的传递顺序为:

Activity --> Window --> DecorView --> 我们的布局View

中间需要先传递给Window,这是必不可少的。其实DecorView就是PhoneWindow的一个内部类, 而PhoneWindow又是Window的唯一实现类。所以了解一下Window的创建过程, 非常有必要。

不过在这之前, 我们需要先简单知道Window的添加流程, 不太了解的同学可以参考我另一篇博客**从Activity的添加过程理解Window和WindowManager**.

Activity的Window创建过程

通过前一篇博客分析, 我们知道, View是Android中的视图呈现形式, 但是View不能单独存在, 他必须依附在Window这个抽象概念上, 因此有视图的地方就有Window。哪些地方有视图呢? 比如:Activity、Dialog、Toast、PopUpWindow、菜单等。 下面我们将介绍Activity的Window创建过程。

要了解Activity中的Window创建过程,首先要对Activity的启动流程有一定了解,这也是作为一个中高级开发人员必备的。

这里我们先简单了解一下,以便于整体阅读,想仔细的朋友可以参考下我的另一篇详细介绍的博客**一张图看懂Activity启动流程 Activity启动流程详解**, 我在这篇博客中整理了一张Activity启动流程图, 看图做一个大概的了解也可以。 Activity的启动过程比较复杂, 最终会由ActivityThread中的performLaunchActivity()来完成整个启动过程, 在这个方法中通过类加载器通过反射创建Activity的实例,然后调用attach方法为其关联所依赖的上下文环境变量, 代码如下:

...

 try {
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        ...
        
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                "Unable to instantiate activity " + component
                + ": " + e.toString(), e);
        }
    }
    
 ...

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

            if (customIntent != null) {
                activity.mIntent = customIntent;
            }
    ...
 }

在Activity的attach()方法中, 系统会创建Activity所属的Window对象并为其设置回调接口。Window对象是PolicyManager.MakeNewWindow()方法创建的。Activity实现了Window的Callback接口, 因此当Window接收到外界的状态改变时, 就会回调给Activity,常用的接口有onAttachedToWindow(), onDetachedFromWindow, dispathTouchEvent 等等, 创建window的代码如下:

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

从上面的分析看出, Activity的Window是通过PolicyManager的一个工厂方法makeNewWindow创建的。 我们看看这个方法:

public static Window makeNewWindow(Context context) {
    return sPolicy.makeNewWindow(context);
}

这里又交给了IPolicy接口, 看看这个接口的定义:

/* The implementation of this interface must be called Policy and contained
* within the com.android.internal.policy.impl package */
public interface IPolicy {
    public Window makeNewWindow(Context context);

    public LayoutInflater makeNewLayoutInflater(Context context);

    public WindowManagerPolicy makeNewWindowManager();

    public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}

在实际的调用中, Ipolicy的真正实现类是Policy, 我们进去看看这个方法的具体实现:

public Window makeNewWindow(Context context) {
    return new PhoneWindow(context);
}

看到了没,返回了一个PhoneWindow, 由此也可以说明, Window的实现类确实是PhoneWindow。关于策略类PolicyManager是如何关联到Policy上去的, 从刚才我们一步一步的源码中没有发现具体的调用关系,这里猜测是由编译环节动态控制的。不过从类名上看,PolicyManager和Policy的关系,确实非同一般。

到这里, Activity的Window就已经完成创建了。简单梳理下创建流程,氛分为四个步骤:

  • 1.调用ActivityThread的PerformLaunchActivity,开始启动Activity
  • 2.Instrunment的newActivity() 创建出Activity对象
  • 3.Activity的attach()方法,会有一些参数的初始化
  • 4.在attach()方法中 会通过PolicyManager.makeNewWindow(this)去真正创建PhonwWindow
Activity的视图附属到Window的过程

上面讲述的是Activity以及Window的创建过程,接下来, 我们看Activity的xml布局这个View是如何附属懂啊Window上面的。我们知道,Activity启动后, 在onCreate()这个生命周期方法里面会调用setContentView(),在这里会传入我们的布局xml文件, 跟一下这个方法:

 public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
 }

从这里可以看出, Activity将具体实现交给了Window处理,而Window的具体实现是PhoneWindow, 我们到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 && !isDestroyed()) {
        cb.onContentChanged();
    }
}

从上面的代码可以出,首次启动Acctivity会调用installDecor(),我们进去看一下:

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

在这个方法内部主要功能有以下及部分

第一步.创建DecorView

首先 mDecor = generateDecor() 会创建一个DecorView。DecorView是一个FrameLayout, 是Activity中的顶级View,或者说根View, 一般来说它的内部包含了标题栏和内容栏,标题栏会随着主题的变化变化, 但内容栏是一定要存在的,并且其id固定为android.R.id.content。

第二步 创建mContentParent, 并将Activity布局文件填充到这个view

然后就会创建 mContentParent, 我们到这个generateLayout(mDecor)内部看看:

protected ViewGroup generateLayout(DecorView decor) {

//源码不同版本细节会不一样,但是整体流程是相同的
...
    //填充view
    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    //到上面填充好了的view里面去找到这个content, 
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  
...      
    
 return  contentParent;
    
}

我们只看关键部分,最终我们通过findViewById(ID_ANDROID_CONTENT) 创建了contentParent这个View并返回回去,其中ID_ANDROID_CONTENT的值定义如下:

 public static final int ID_ANDROID_CONTENT =com.android.internal.R.id.content;

其实这个mContentParent就是我们的布局文件的View啦, 所以为什么我们在onCreate里面添加布局时, 用的setContentView(),因为它的id就是content吖。

第三步 回调Activity的 onContentChanged方法通知Activity 视图已经发生改变

到这一步就简单了, 由于Activity实现了Window的Callback接口,这里布局文件已经添加到DecorView的mContentParent了,于是通知Activity,使Activity可以做一些相关操作。这里会回调到onContentChanged()方法中。这个方法在Activity中是一个空方法,我们开发时如果想在布局添加完成后做操作, 就可以在这个方法处理。 这里也算是用到了常用设计模式:模板方法模式。看一下这里回调的代码:

final Callback cb = getCallback();//这里去找实现了callback接口的实例,其实就是activity
if(cb != null && !isDestoryed()) {
	cb.onContentChanged();//这里就回调到了onContentChanged方法
}

经过上面三步骤, DecorView已经被创建并初始化完毕, 而且Activity的布局文件也已经加载到了DecorView中的mContentParent中。但是这个时候, 我们的DecorView还没有被WindowManagerService正式添加到Window中。

通过前一篇文章的了解, 其实我们添加一个View就是添加一个Window, 这个过程是需要IPC跨进程通信的, 通知我们的WindowManagerService去真正的添加Window。 所以这个这个DecorView虽然已经创建好了, 但是还没有被WindowManager所识别, 这时候这个Window也就无法提供具体的功能, 因为它还无法接受外界的输入信息。那DecorView什么时候添加呢?

在Activity启动流程中,我们可以在源码看到在ActivityThread的handleResumeActivity中首先调用了Activity的 onResume方法, 接着调用makeVisible, 正是在这里完成的DecorView的添加和显示这两个过程, 之前的都是创建过程, 这样Activity的视图才能被我们看到,代码如下:

  void makeVisible() {
     if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
     }
     mDecor.setVisibility(View.VISIBLE);
  }

到这里, Activity的Window创建过程就结束了。
接下来我们将继续分析:
- 从Dialog的Window创建过程理解–Dialog的View界面是如何呈现出来的
- 从Toast的Window创建过程理解–Toast的View界面是如何呈现出来的

喜欢的朋友麻烦点个赞把_

参考资料:

《Android开发艺术探索》

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值