Activity 原理剖析

3,617 阅读10分钟

作为 Android 四大组件之一的 Activity 我的印象中就是用来展示界面的,在很长一段时间里,只要提及界面、UI、View 我脑子里第一个闪过的就是 Activity ,我的理解中一直认为 界面、UI、View == Activity。其实呢非也!如果你曾经和我一样,说起 Actvity 就是聊他的生命周期,各个方法背的滚瓜烂熟,但也仅限于此而已并没有真正的去认识 Activity 的话,那么请跟我一起从【 Activity 的组成】、【启动】、【显示】来重新认识一下 Activity

一 Activity 的本质

首先明确一下 Activity 的基本概念,我们先来看下 Google 官方对 Activity 给出的定义:

Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。

读完了好像也没整明白。

我们抛开之前对 Activity 的印象,从代码层面上来看他就是一个普通的 Java 类,本质上并不是一个 View 或 ViewGroup,他没有继承任何 View 和 ViewGroup 相关类。

image-activity-1

从源码中可以看到 Activity 实现了 Window 、KeyEvent 等一系列的 Callback 以及 Listener 。看到这些大概可以知道 Activity 的职责相当于一个控制器了,接收各种回调以及处理监听事件。

基于 Google 官方的定义和 Activity 源码,Activity 的大概职责可以总结成一张图:

image

1.1 小结

  1. Activity 本身并不具备 View 任何属性,从代码层面看就是普通的类。
  2. 本身不是 View ,所以 Activity 本身并不做和 UI、视图控制相关的事,控制 UI、视图的另有其人。
  3. 在整体架构中扮演控制器角色,负责控制生命周期和事件处理。

二 Activity 组成剖析

是时候来揭开 Activity 的庐山真面目了。一图胜千言!先来看一张图。

image

上图描述了 Activity 从外到内大概的一个层级结构

2.1 DecorView

角色

顶层 View
当前 Activity 所有 View 的祖先, 即当前 Activity 视图树根节点。

作用

  1. 显示 & 加载布局。但其本身并不做任何 View 呈现。
  2. 分发 View 层事件,View 层事件由 ViewRoot 分发至 DecorView,再由 DecorView 分发给具体 View。

简介

  1. DecorView 本质上是一个 FrameLayout ( DecorView 类继承自 FrameLayout )。

  2. 其内部包含一个竖直布局的 LinearLayout ,分为2部分:

    id 为 title_container 的 TitleBar 。

    id 为 content 的 Content 。在Activity中通过 setContentView()所设置的布局文件最终就是被添加到此处的 Content 中。

  3. Content 内部的视图树对应的就是 Activity 的布局文件。

2.2 PhoneWindow

角色

视图承载器

作用

承载视图 View 的显示。

简介

PhoneWindow 为 Window 的实现类。每个 Activity 均会创建一个 Window 用以承载 View ,是视图真正的控制者。

2.3 ViewRoot

角色

连接器

作用

  1. 连接 WindowManagerDecorView 的纽带。
  2. 完成 View 绘制的三大流程: measurelayoutdraw
  3. 向 DecorView 分发用户发起的 View 层事件,如 KeyEvent,TouchEvent。

简介

对应实现类为 ViewRootImpl ,实现了 ViewParent 接口。作为 WindowManagerGlobal 大部分内部实现的实际实现者,需负责与 WindowManagerService 交互通信,以调整窗口的位置大小,以及对来自 WindowManagerService 的事件(如窗口尺寸改变等)作出相应的处理。

  1. WindowManagerGlobal 方法实际实现如:
 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    ......
    ViewRootImpl root;
    ......
    root = new ViewRootImpl(view.getContext(), display);
     try {
    //最终实现,调用 ViewRootImpl setView 方法。
        root.setView(view, wparams, panelParentView);
      } catch (RuntimeException e) {
        ......
      }
  }
  1. 与 WindowManagerService 交互通信:
 public ViewRootImpl(Context context, Display display) {
    ......
    mWindowSession = WindowManagerGlobal.getWindowSession();
    ......
  }
  //与 WindowManagerService 建立远程连接
  public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService();
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowSession;
    }
}

三 Activity 显示

在简单分析了 Activity 各个组成元件之后,接下来就是看这些元件是如何完成完整的 Activity 组装并将 View 呈现到用户面前。

一图胜千言:

image

下面跟随上图的流程走一遍源码(为避免篇幅过长只展示重点代码)。

3.1 Activity 启动

这部分主要做的是一些初始化设置的工作,为 Actvity 最终呈现在用户面前做准备。

1.初始化 Looper 创建消息循环

做为 Android 应用程序的入口类,先来看下 ActivityTread 做了什么工作。

源码路径:ActivityTread 类 main 方法。

public static void main(String[] args) {
 
    .......

    Looper.prepareMainLooper();

    .......
    
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    .......

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

方法解析:

ActivityTread 的 main 方法初始化了一个 Looper消息循环用以接收主线程的操作消息。

关于Looper消息循环详细介绍可以查看 Android Handler浅析一文,这里就不再展开。

2.接收 LAUNCH_ACTIVITY 消息

Application 启动后,主线程会收到 LAUNCH_ACTIVITY 消息,该消息由 ActivityTread 内部类 H (继承自 Handler) 接收。

源码路径:ActivityTread/H 类 handleMessage 方法。

public void handleMessage(Message msg) {
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

            r.packageInfo = getPackageInfoNoCheck(
                    r.activityInfo.applicationInfo, r.compatInfo);
            //重点看这个方法
            handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
}

接收消息后会调用 handleLaunchActivity() 方法,下面重点都在 handleLaunchActivity() 方法里面了。

源码路径:ActivityTread 类 handleLaunchActivity 方法。

  private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
      
        .......
        
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
        
            ......
            
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
        } 
        ......
    }

方法解析:

次方法内部重点看 performLaunchActivity()handleResumeActivity() 方法的调用。

先来看 performLaunchActivity() 方法,此方法就开始了我们上图的第 3~9步的流程,直到流程走完,方法调用栈会回退至 handleLaunchActivity() 中继续往下执行,调用 handleResumeActivity()

3. 创建 Activity 对象

源码路径:ActivityTread 类 performLaunchActivity 方法。

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        
        .......
        
        Activity activity = null;
        try {
            //通过 Activity 类名构建 Actvity 对象
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            
           ......
            
        } catch (Exception e) {
            .......
        }

        try {
            
            ......
            
            if (activity != null) {
                
                ......
                //为当前 Activity 初始一个 Window 并设置 WindowManager
                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, r.configCallback);

               ......
                
                //通过 Instrumentation 调用 Activity 的 onCreate 方法
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                
               ......
                
        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
           ......
        }

        return activity;
    }

方法解析:

performLaunchActivity 方法做了三件重要的事:

  1. 通过当前 Activity 的类名为当前的 Activity 创建一个对象。
  2. 调用 activity.attach 方法为当前 Activity 初始化一个 Window 对象。
  3. 通过 Instrumentation 调用 Activity 的 onCreate 方法。

5.创建 PhoneWindow对象

performLaunchActivity 方法中调用 activity.attach 方法为当前 Activity 初始化一个 Window 对象,我们来看下 activity.attach 方法的具体实现。

源码路径: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, String referrer, IVoiceInteractor voiceInteractor,
                      Window window, ActivityConfigCallback activityConfigCallback) {
        
        ......
        //为 mWindow 变量赋值,实例化一个 PhoneWindow 
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        
        ......
        //为 mWindow 设置 WindowManager 
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        ......
        //为 mWindowManager 变量赋值
        mWindowManager = mWindow.getWindowManager();
        
        .......
    }

attach 方法中为 Activity 的 mWindow 变量实例化了一个 PhoneWindow 对象,PhoneWindow 是 Window 的一个具体实现,这在上文中已经说过,就不再赘述了。

6. 执行 Activity onCreate()

performLaunchActivity 方法中通过 Instrumentation 调用 Activity 的 onCreate 方法,该方法的具体实现在 Activity 中。 一般来说我们都会重写 Activity 的 onCreate 方法并在里面调用 setContentView。下面来看下我们熟悉的 setContentView 里面做了些什么。

7. 调用 setContentView()

源码路径:Activity 类 setContentView 方法。

public void setContentView(@LayoutRes int layoutResID) {
    //调用 PhoneWindow 的 setContentView
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

Activity 的 setContentView 实际的实现来自 PhoneWindow,可以看下 getWindow() 方法返回的其实就是步骤5attach 方法赋值的 mWindow 对象。

源码路径:Activity 类 getWindow 方法。

 public Window getWindow() {
    return mWindow;
 }

既然这样我们不妨跟进 PhoneWindow 中一探究竟:

源码路径:PhoneWindow 类 setContentView 方法。

@Override
public void setContentView(int layoutResID) {
    // 如mContentParent为空,创建一个DecroView
    if (mContentParent == null) {
        //初始化 DecroView
        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 {
        //为 mContentParent 添加布局文件
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Window.Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        //内容改变回调
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

方法解析:

  1. mContentParent 这个变量就是我们上文在介绍 DecroView 时提到的 FrameLayout 对应的布局部分。
  2. installDecor() 方法为 PhoneWindow 初始化一个 DecroView 对象。
  3. 一番骚操作之后将当前 Activity 的布局添加至 mContentParent 中。

8. 初始化 DecorView

PhoneWindow 的 setContentView 方法会为当前的 Window 初始化一个 DecorView。

源码路径:PhoneWindow 类 installDecor 方法。

private void installDecor() {
    if (mDecor == null) {
        // 创建 DecorView 对象
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        // 为 DecorView 的 content 设置布局格式
        mContentParent = generateLayout(mDecor);

        ......
    }
}

方法解析:

1.调用 generateDecor 方法创建 DecorView 对象,generateDecor 实现很简单直接。

源码路径:PhoneWindow 类 generateDecor 方法。

protected DecorView generateDecor(int featureId) {
    ......
    return new DecorView(context, featureId, this, getAttributes());
}
  1. 调用 generateLayout 方法为 DecorView 的 content 设置布局格式。

源码路径:PhoneWindow 类 generateLayout 方法。

protected ViewGroup generateLayout(DecorView decor) {
    //获取窗口样式信息
    TypedArray a = getWindowStyle();

    ......

    // Inflate the window decor.
    //根据主题样式,加载窗口布局
    int layoutResource;
    int features = getLocalFeatures();

    ......

    mDecor.startChanging();
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    // 获取 Decorview 对应的 content 的 FrameLayout
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    ......
    
    mDecor.finishChanging();

    return contentParent;
}

9. 添加布局到 DecorVIew Content,设置布局格式

这个步骤的源码步骤7中已经贴出来了,在 setContentView 方法中:

//为 mContentParent 添加布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);

到这里 handleLaunchActivity 方法中的 performLaunchActivity 方法这条线就差不多走完了,现在回到 handleLaunchActivity 方法中继续往下执行,下面的关键方法是 handleResumeActivity

3.2 Activity 显示

先看 handleResumeActivity 方法的重点代码。

源码路径:ActivityThread 类 handleResumeActivity 方法。

 final void handleResumeActivity(IBinder token,
                                    boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ......

    // TODO Push resumeArgs into the activity for consideration
    // 调用Activity的onResume()
    r = performResumeActivity(token, clearHide, reason);

    if (r != null) {
        final Activity a = r.activity;

        ......

        if (r.window == null && !a.mFinished && willBeVisible) {
         .......
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //添加 DecorView 到 Window
                    wm.addView(decor, l);
                } 
                ......
        }
        ......
    }
}

10. 执行 Activity onResume()

handleResumeActivity 方法中通过调用 performResumeActivity 方法来执行 Activity onResume() 方法。具体实现有兴趣的可以自己跟进源码进去看下,这里就不再贴源码了,文章篇幅已经长的头皮发麻了。

11. 添加 DecorView 到 WindowManager

handleResumeActivity 方法中通过调用 WindowManager 的 addView 方法将之前流程中已赋值的 DecorView 对象添加至当前 Window。

12. 初始化创建 ViewRootImpl

初始化 ViewRootImpl 工作就是在 WindowManager 的 addView 方法中进行的。而 WindowManager 是一个接口,对应的实现类是 WindowManagerImpl,那就去 WindowManagerImpl 中找 addView 的具体实现咯。

源码路径:WindowManagerImpl 类 addView 方法。

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

方法解析:

该方法内部直接调用 WindowManagerGlobaladdView 方法,WindowManager 的 addView 方法由 WindowManagerGlobal 代理了。

源码路径:WindowManagerGlobal 类 addView 方法。

public void addView(View view, ViewGroup.LayoutParams params,
                    Display display, Window parentWindow) {
   .......

    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
       ......
        //创建ViewRootImpl对象
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        try {
            //WindowManager将DecorView实例对象交给ViewRootImpl 绘制View
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
           ......
            throw e;
        }
    }
}

13. DecorView 对象传给 ViewRootImpl

WindowManagerGlobal 类 addView 方法在创建 ViewRootImpl 对象后调用其 setView 方法将将 DecorView 实例对象交给 ViewRootImpl 用以绘制 View 。

14. 开始 View 绘制流程,呈现布局

所有一切准备就绪之后我们终于开始对我们 Activity 的布局进行绘制了。最终将调用 ViewRootImpl 的 performTraversals方法(此方法贼长)开始 View 绘制的三大流程,measure、layout、draw

源码路径:ViewRootImpl 类 performTraversals 方法。

private void performTraversals() {
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    //执行测量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //执行布局流程
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    //执行绘制流程
    performDraw();
}

OK到此 Activity 的部件组装以及显示流程就梳理了一个大概,文章篇幅有点长,但是内容不算多,贴了很多代码,有兴趣的可以跟着我的流程自己跟进源码看下加深印象。

参考文献:

Android:手把手带你清晰梳理自定义View的工作全流程!

Android View的绘制流程

Android中Activity启动过程探究