多了解一点Activity

Activity和普通类的重要区别在于其有生命周期的回调方法,本文意在通过其回调方法的调用,揭开其神秘面纱

1.几个重要的类

(1)ActivityThread

应用启动的时候会创建一个独立的进程,在这个进程里面会有一个主线程,主线程首先做的事就是调用ActivityThread的main方法,也就是说ActivityThread的main方法是应用程序的入口,相当于java的main方法.
注意:ActivityThread不是线程!!!

(2)ApplicationThread

是ActivityThread的私有内部类,继承自ApplicationThreadNative,ApplicationThreadNative继承了Binder并实现了IApplicationThread接口,说明了ApplicationThread具有跨进程通信的能力.

IApplicationThread是一个接口,我们看看它的介绍:

/**
 * System private API for communicating with the application.  This is given to
 * the activity manager by an application  when it starts up, for the activity
 * manager to tell the application about things it needs to do.
 * <p>
 * {@hide}
 */

翻译:这是一个用于和应用通信的系统私有API,当应用启动的时候会被应用交给ActivityManager,让ActivityManager告诉应用要做什么.
我们看看这个接口里面的一些方法:



这只是一部分方法,通过上面的部分方法我们能看出这个接口是AMS控制Activity,Service等生命周期的一个接口,ApplicationThread实现了该接口,也就是说AMS通过ApplicationThread实现对Activity,Service等的声明周期的控制.在进程间通信中这个时候AMS是Client,ApplicationThread是Server(实际中AMS持有的是ApplicationThread的代理对象ApplicationThreadProxy)

(3)H

H是ActivityThread的内部类,继承自Handler,简单说H就是一个处理消息的类.
我们在上面已经知道AMS通过ApplicationThread来调用Activity的生命周期方法,那ApplicationThread又是怎么调用的呢? ApplicationThread是通过H来发送Message,然后再由H来处理消息

(4)ActivityClientRecord,ActivityRecord

这两者都是用来描述Activity的,比如Activity的window,配置信息等等,二者有不同,但大体上一样.ActivityClientRecord是在我们应用程序端使用的,ActivityRecord是AMS使用的

(5)Instrumentation

是一个辅助类,用于创建application,开启Activity,调用Activity的各个生命周期的方法.

(6)ViewRoot

ViewRoot可能比较陌生,但是其作用非常重大。所有View的绘制以及事件分发等交互都是通过它来执行或传递的。
ViewRoot对应ViewRootImpl类,它是连接WindowManagerService和DecorView的纽带,View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRoot来完成。
ViewRoot并不属于View树的一份子。从源码实现上来看,它既非View的子类,也非View的父类,但是,它实现了ViewParent接口,这让它可以作为View的名义上的父视图。RootView继承了Handler类,可以接收事件并分发,Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRoot进行分发的。同时持有WindowSession通过Binder与WMS通信,同时持有IWindow作为WSM的回调接口,用于例如touch事件的回调。

(7)Window

Window是视图的承载器,内部持有一个 DecorView,而这个DecorView才是 view 的根布局。Window是一个抽象类,实际在Activity中持有的是其子类PhoneWindow。PhoneWindow中有个内部类DecorView,通过创建DecorView来加载Activity中设置的布局R.layout.activity_main。Window 通过WindowManager将DecorView加载其中,并将DecorView交给ViewRoot,进行视图绘制以及其他交互。

(8)DecorView

DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下三个部分,上面是个ViewStub,延迟加载的视图(应该是设置ActionBar,根据Theme设置),中间的是标题栏(根据Theme设置,有的布局没有),下面的是内容栏。具体情况和Android版本及主体有关,以其中一个布局为例,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

下面通过代码把上面这些类串起来,看看Activity是怎么显示出我们设置的布局来的.

2.代码分析

我们只关注过程,不关注细节

(1)从ActivityThread的main方法开始
//ActivityThread.java
public static void main(String[] args) {
          ......
        //就是为主线程创建一个looper,用于处理消息
        Looper.prepareMainLooper();
        //这里记住一点,在创建ActivityThread的时候也创建了ApplicationThread
        ActivityThread thread = new ActivityThread();
        //这里是重点,主要是与ActivityManagerService交互
        thread.attach(false);

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

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

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        //开始轮询,处理消息
        Looper.loop();

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

在这个方法里面涉及到了Handler,具体Handler的原理可以参考:
https://www.jianshu.com/p/d4415033349d
我们这里着重关注thread.attach(false)方法

private void attach(boolean system) {
       ...
            //AMS运行在一个单独的进程中,与我们的app进程不一样,这里我们通过ActivityManagerNative
            //获得其远程代理对象
            final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
            //mAppThread就是ApplicationThread
            //通过上面对ApplicationThread的介绍,我们知道ApplicationThread和AMS关系密切,这里就是二者的交互
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        ...
}
(2)第一步中,该关联的都关联了.下面该启动一个Activity了,启动Activity是由AMS调用的,具体反映就是AMS远程调用ApplicationThread的scheduleLaunchActivity方法(实际是调用ApplicationThreadProxy相关的方法).然后该方法通过H 的sendMessage方法发送了一个消息,我们看看H是怎么处理这个消息的.
            case LAUNCH_ACTIVITY: {
                 
                    //这个obj是远程的AMS发送过来的,告诉app要启动哪个Activity
                    //AMS怎么知道要启动哪个Activity呢,这是在启动该Activity传入的intent参数中获得的
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                } break;
(3)接着看handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");方法
//ActivityThread.java
 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
      
        //先看这个方法做了什么,看完这个方法的解析再往下看
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
          //performLaunchActivity看完之后就看这里
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

            if (!r.activity.mFinished && r.startsNotResumed) {
                
                performPauseActivityIfNeeded(r, reason);

                if (r.isPreHoneycomb()) {
                    r.state = oldState;
                }
            }
        } else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

performLaunchActivity()方法的主要逻辑(这里只贴出部分主要代码):
①通过反射创建Activity

            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            //从这里我们看到Activity的创建用的是Instrumentation辅助类,这里用到了反射,我们不去细究
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);

②创建application对象

            //创建application对象,每个应用都有一个application,原来是在这里创建的.
            //具体的创建者也是Instrumentation
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

③创建window对象

 //创建Window对象,对Activity内部成员变量进行赋值,把Activity和Window进行关联
                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);

④执行onCreate()方法

            //从中我们看出来是用到了Instrumentation来调用Activity的onCreate方法
             if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }

⑤执行onStart()方法

              if (!r.activity.mFinished) {
                    //这个方法点进去,发现是Instrumentation最终调用了Activity的onStart()方法
                    activity.performStart();
                    r.stopped = false;
                }

我们着重看下第④个方法
这个方法我们点进去看发现就是回调了我们创建Activity时的onCreate()方法.我们在这个方法里面通过setContentView()来设置页面布局,下面我们看看这个布局是怎么显示出来的

//Activity.java
public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

原来是调用window的setContentView(),通过本文开头的解释,我们知道这个Window就是PhoneWindow.

//PhoneWindow.java
 @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) {
            //初始化DecorView,这个view前面已经介绍
            //在这个方法里面除了创建decorview,也创建了mContentParent
            //mContentParent是我们设置的布局的父布局
            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 {
            //没有转场动画就直接把我们设置的layout添加到mContentParent
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

注意:执行完PhoneWindow.setContentView()之后,这时view还没有显示,只是添加到DecorView.
上面五个主要方法执行完之后,我们再回到handleLaunchActivity()方法,我们接着看handleResumeActivity()方法

(4)handleResumeActivity()

从这个方法的名字可以看出来,这是要调用onResume方法了,也就是到了要显示我们设置的layout的地方了
我们只看这个方法最核心的部分:
显示view

//ActivityThread.java
final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {

            //这个时候,Activity.onResume()已经调用了,但是现在界面还是不可见的
            ActivityClientRecord r = performResumeActivity(token, clearHide);

            if (r != null) {
                final Activity a = r.activity;
                  if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                //decor对用户不可见
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;

                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //被添加进WindowManager了,但是这个时候,还是不可见的
                    wm.addView(decor, l);
                }

                if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
                     //在这里,执行了重要的操作,使得DecorView可见
                     if (r.activity.mVisibleFromClient) {
                            r.activity.makeVisible();
                        }
                    }
            }

看看Activity的makeVisible()方法

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

到此DecorView便可见,显示在屏幕中。但是在这其中,wm.addView(mDecor, getWindow().getAttributes());起到了重要的作用,因为其内部创建了一个ViewRootImpl对象,负责绘制显示各个子View。
具体来看addView()方法,因为WindowManager是个接口,具体是交给WindowManagerImpl来实现的。由于这里使用了桥接模式,WindowManagerImpl又交给WindowManagerGlobal 的addView()方法去实现.

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

                 ViewRootImpl root;
                  //实例化一个ViewRootImpl对象
                 root = new ViewRootImpl(view.getContext(), display);
                 view.setLayoutParams(wparams);

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

             try {
                //将DecorView交给ViewRootImpl 
                root.setView(view, wparams, panelParentView);
            }catch (RuntimeException e) {
            }

            }
 }

看到其中实例化了ViewRootImpl对象,然后调用其setView()方法。其中setView()方法经过一些列折腾,最终调用了performTraversals()方法,然后依照下图流程层层调用,完成绘制,最终界面才显示出来。


View绘制流程

至此,结束,长舒一口气~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容