Android 源码分析 - Activity的结构分析

  在很早之前,楼主简单的学习过Activity的结构,可是当时介于各类缘由,只是浅尝辄止,并无进行深刻的学习。同时,我发现本身今年在毕业以后有点颓废,再也不有去年那股学习劲儿。通过屡次的自省,发现本身是由于找不到学习的方向而颓废的。bash

  通过深入反思自我以后,为了改变如今的情况,也为了弥补弥补当初学习Activity的遗憾,还为了提高自身的技术水平,我决定从Activity开始入手,学习并记录Android framework的知识。app

  本文打算介绍并分析Activity的结构设计,包含:ActivityWindowView相关知识。本文参考资料:异步

  1. Activity、View、Window的理解一篇文章就够了
  2. 反思|Android LayoutInflater机制的设计与实现
  3. 从Activity建立到View呈现中间发生了什么?

1. 结构介绍

  大伙儿应该都知道,Activity的结构分为三层,分别是:ActivityWindowView,不一样层承担着不一样的责任。 ide

  上面的图简单的描述了 Activity整个结构的构建流程。这里我再简单的介绍一下这几个部分的做用。

  1. ActivityThread:每一个流程调用起始地点,至于ActivityThread的内容不是本文的重点,因此本文不会过多介绍,后续有相关的文章来介绍。
  2. Activity:至关因而一个管理者,负责建立WindowManagerWindow,同时初始化View
  3. Window:承载着View,同时代Activity处理一切View的事务。
  4. WindowManager:从字面意思来理解是Window的管理,实际上是管理Window上的View,包括addViewremove

  而这几个角色的对应关系是:ActivityThread全局惟一,整个App进程中只一个实例。ActivityThread能够对应多个Activity,一个Activity对应一个Window(这里不考虑Dialog之类的),一个Window对应一个WindowManageroop

  本文打算参考上面的流程图,分别分析Activity的attach过程、Activity的onCreateView过程和WindowManager的addView过程。布局

2. Activity的attach

  Activity的attach过程就是一个初始化的过程,分别对Window进行初始化、WindowManager进行初始化。咱们先来看看ActivityThreadhandleLaunchActivity方法:post

public Activity handleLaunchActivity(ActivityClientRecord r,
       // ······

       final Activity a = performLaunchActivity(r, customIntent);
        // ······
   }
复制代码

  handleLaunchActivity方法主要调用performLaunchActivity来启动一个Activity,再来看看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, r.configCallback);
   // ·······
}
复制代码

  在这里,咱们看到了Activityattach方法的调用,咱们来看看attach方法里面作了哪些事情。ui

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 = new PhoneWindow(this, window, activityConfigCallback);
       // ······
       mWindow.setWindowManager(
               (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
               mToken, mComponent.flattenToString(),
               (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
       // ······
   }
复制代码

  attach方法一共作了两件事情:this

  1. 初始化Window,其中Window的构造方法还作了一些事,好比说,咱们比较关心的LayoutInflater的初始化。
  2. 初始化WindowManager,而且set到Window里面去。

3. Activity的onCreate

  当初,咱们刚入门Android的时候就知道,在ActivityonCreate方法调用setContentView能够给当前页面设置一个布局。当时有没有以为这个很是的神奇,而且对其充满了好奇,惋惜的是当时本身对整个Android的设计还很陌生,不敢去探究。今天咱们能够正式进入setContentView的内部了,去一探究竟🤪。

  其实ActivityonCreate过程不只作setContentView操做,其实还作了其余的事情。从源码中咱们也能够看到,好比说,初始化AppCompatDelegate(这里以AppCompatActivity为例),设置Theme等。不过这些不是本文的重点,后面我会专门的文章来分析这些过程,本文的重点是setContentView方法。

  ActivitysetContentView没有作啥事,实际作事的是它的Window。咱们来看看WindowsetContentView方法:

@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;
  }
复制代码

  setContentView方法里面主要是作了以下几件事:

  1. 经过installDecor方法建立DecorViewmContentParentDecorView的建立在generateDecor方法;mContentParent的建立在generateLayout方法里面,generateLayout方法里面有一个重要地方根据属性的配置设置了Windowflagsfeatures。这里flags的生效是在DecorViewupdateColorViews方法,会根据flags来计算DecorView的大小;features将那种布局加载到DecorView,这将影响到Activity默认的布局样式。不过我有一点疑惑至今无解,从mContentParent的注释了解到,mContentParent也有多是DecorView,可是我看了installDecor以及内部调用的generateLayout方法,都没有找到相关可能性。
  2. ContentView加载到mContentParent上去。

  针对上面设置flag生效的解释我还想补充一下,大伙儿应该都知道,在Android中,咱们能够经过setFlags或者addFlags来改变一些属性,可是有没有想过是怎么生效的呢?其实过程是很是的简单,咱们就不一一的追踪源码了,直接来看一下调用流程:

  Window#setFlags -> PhoneWindow#dispatchWindowAttributesChanged -> DecorView#updateColorViews

  最终,会根据设置好的Flag来计算DecorView的位置和大小,例如说,咱们设置了FLAG_FULLSCREEN让界面全屏,最终在updateColorViews方法这么来计算DecorView的高度:

// If we didn't request fullscreen layout, but we still got it because of the // mForceWindowDrawsStatusBarBackground flag, also consume top inset. boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 && (sysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 && mForceWindowDrawsStatusBarBackground && mLastTopInset != 0; 复制代码

  上面的代码表达的意思很是简单,就是判断是否消费状态栏的高度。至于其余Flag也是如此,有兴趣的同窗能够看一看。

4. WindowManager的addView

  咱们知道,DecrorViewViewParentViewRootImpl,而View最重要的三大流程就是由ViewRootImpl触发的。在正式介绍这一块的知识以前,咱们先来看一个简单的结构图:

  这个图相信你们已经熟悉的不能再熟悉了,不过这里我仍是将它贴出来。参考上面的图,咱们能够知道,通过上面的两个流程,咱们将 DecorView部分建立完成,如今还须要两件事须要作:

  1. 初始化ViewRootImpl,而且将ViewRootImplWindow绑定。
  2. 将以前建立好的DecorView添加到ViewRootImpl里面去。

  咱们先来看看ActivityThreadhandleResumeActivity方法:

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        // ······
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        // ·······
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            // ······
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
                // ·······
            }
        }
        // ······

    }
复制代码

  handleResumeActivity方法里面主要作了两件事:

  1. 调用performResumeActivity进而回调ActivityonResume方法。
  2. 调用WindowManageraddView方法,将DecorView添加到ViewRootImpl中去。

  调用的addView方法最终是进入WindowManager的实现类WindowManagerImpl中去,而在WindowManagerImpladdView方法中调用了WindowManagerGlobaladdView方法。   从名字中,咱们就能够看出来,WindowManagerGlobal是属于全局的。咱们再来看看WindowManagerGlobaladdView方法:

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) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
复制代码

  addView方法主要是作了两件事:

  1. 初始化ViewRootImpl,而且将该Window相关信息保存起来,包括:ViewRootImplDecorViewLayoutParams
  2. 调用ViewRootImplsetView方法,将DecorView添加到ViewRooImpl,而且触发View的三大流程。

  咱们都知道,每一个Window都对应着一个DecorView,而从这里咱们能够发现,每一个DecorView都对应着一个ViewRootImpl,从而得知,若是是一个Dialog或者其余新Window的界面,一定有一个新的ViewRootImpl来触发View的三大流程,而不是由宿主WindowViwRootImpl触发的。

(1). 为何在Activity的onResume方法调用Handler的post不能获取View的宽高呢?

  在回答这个问题以前,咱们应该须要注意的是,这里是Handler的post方法,而不是View的post方法。ps:View的post方法能拿到View的宽高。

  咱们知道ActivityonResume方法的执行是在ViewRootImpl触发测量过程以前,同时ViewRootImpl是经过以下的方式来触发测量过程的:

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
复制代码

  咱们发现这里经过Handler post了一个异步消息来进行测量。但是,尽管post的是异步消息,在onResume方法post的消息也先于它执行,由于它在其后post的。因此,在Activity的onResume方法调用Handler的post不能获取View的宽高。

5. 总结

  到此位置,对Activity的结构分析也差很少,在这里,咱们作一个简单的总结。

  1. Activity的结构分为三层,分别是:ActivityWindowView
  2. 结构的建立过程分为三步:1. 建立Activity,而且建立与其相关的WindowManagerWindow,对应着是Activityattach方法的调用;2. 初始化DecorViewContentView,对应着的是ActivityonCreate方法;3. 建立ViewRootImpl,而且将DecorView添加到ViewRootImpl中,同时触发View树的三大流程。
  3. generateLayout方法里面,会根据设置不一样的flags来计算DecorView的大小和位置,计算逻辑在updateColorViews方法里面;仍是根据设置不一样的features方法来选择默认加载到DecorView中,好比说设置了NO_ACTION_BARfeatures,就会加载不带ActionBar的布局。
相关文章
相关标签/搜索