Activity 原理剖析

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

一 Activity 的本质

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

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

读完了好像也没整明白。架构

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

image-activity-1

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

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

image

1.1 小结

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

二 Activity 组成剖析

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

image

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

2.1 DecorView

角色

顶层 View
当前 Activity 全部 View 的祖先, 即当前 Activity 视图树根节点。ui

做用

  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启动过程探究

相关文章
相关标签/搜索