Android APP启动到View显示流程

Q:从这篇文章你能学到什么?
A:这篇文章从桌面上点开一个APP图标开始分析,一直到里面各个view也就是控件显示到我们眼睛的整个过程。让你了解整个APP启动流程和View绘制流程。

开始思考:当我们点击手机桌面上的一个软件时,从点击到完整显示这个APP整个过程中发生了什么???

先忍受一下枯燥,理论知识还是得先在脑海中留下印象的

一、从Activity启动流程中理论知识入门

1.Launcher

  Launcher其实就是一个app,从功能上说,是对手机上其他app的一个管理和启动,从代码上说比其他app多了个属性,就是在AndroidManifest.xml文件中多了个“<categoryandroid:name="android.intent.category.HOME" />”属性,这个属性就是在启动系统或者按Home键时会过滤这个属性,如果系统中只有一个应用具有这个属性,就会直接跳转到这个界面,也就是这个launcher,如果有多个,会弹出选择框让用户选择并且提示用户是否选择默认设置。
  (其实Launcher就是我们玩手机时按Home键时跳转到桌面一个道理,说白了,Launcher就是我们的桌面,他就是个系统的App)我们点击的每个程序图标就是他的一个item,会触发onclick事件,接受时间后然后Launcher会打开我们的应用。

2.zygote

  zygote意为“受精卵“。Android是基于Linux系统的,而在Linux中,所有的进程都是由init进程直接或者是间接fork出来的,zygote进程也不例外。在Android系统里面,zygote是一个进程的名字。Android是基于Linux System的,当你的手机开机的时候,Linux的内核加载完成之后就会启动一个叫“init“的进程。在Linux System里面,所有的进程都是由init进程fork出来的,我们的zygote进程也不例外。我们都知道,每一个App其实都是

● 一个单独的dalvik虚拟机

● 一个单独的进程

  所以当系统里面的第一个zygote进程运行之后,在这之后再开启App,就相当于开启一个新的进程。而为了实现资源共用和更快的启动速度,Android系统开启新进程的方式,是通过fork第一个zygote进程实现的。所以说,除了第一个zygote进程,其他应用所在的进程都是zygote的子进程。这下你明白为什么这个进程叫“受精卵”了吧?因为就像是一个受精卵一样,它能快速的分裂,并且产生遗传物质一样的细胞!

3.SystemServer

SystemServer也是一个进程,而且是由zygote进程fork出来的。
知道了SystemServer的本质,我们对它就不算太陌生了,这个进程是Android Framework里面两大非常重要的进程之一,另外一个进程就是上面的zygote进程。为什么说SystemServer非常重要呢?因为系统里面重要的服务都是在这个进程里面开启的,比如ActivityManagerService、PackageManagerService、WindowManagerService等等。

4.ActivityManagerService

AMS是Android中最核心的服务之一,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块相类似,因此它在Android中非常重要,它本身也是一个Binder的实现类。
ActivityManagerService进行初始化的时机很明确,就是在SystemServer进程开启的时候,就会初始化。

5.Instrumentation和ActivityThread

  每个Activity都持有Instrumentation对象的一个引用,但是整个进程只会存在一个Instrumentation对象。
  Instrumentation这个类里面的方法大多数和Application和Activity有关,这个类就是完成对Application和Activity初始化和生命周期的工具类。Instrumentation这个类很重要,对Activity生命周期方法的调用根本就离不开他,他可以说是一个大管家。
  ActivityThread,就是UI线程,应用的入口类,通过调用main方法,开启消息循环队列。App和AMS是通过Binder传递信息的,那么ActivityThread就是专门与AMS的外交工作的。

坚持,理论知识快没了

二、Activity启动整体流程

1.基础类介绍

1)ApplicationThread

提供Binder通讯接口,AMS则通过代理调用此App进程的本地方法

2)ActivityManagerProxy

AMS服务在当前进程的代理类,负责与AMS通信。

3)ApplicationThreadProxy

ApplicationThread在AMS服务中的代理类,负责与ApplicationThread通信。

2.整体流程图

3.流程整体分析--(参考上图分析)

1.点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求;
2.system_server进程接收到请求后,向zygote进程发送创建进程的请求;
3.Zygote进程fork出新的子进程,即App进程;
4.App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;
5.AMS向ApplicationThreadProxy发送realStartActivityLocked请求。
6.system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;
7.App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;
8.主线程在收到Message后,通过调用handleLaunchActivity创建目标Activity,并回调Activity.onCreate()等方法。
9.到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。

在坚持坚持,我们看会源码,先知道大概流程在看源码会轻松很多的,不看源码很非常容易忘记的

4.流程源码分析

1.创建进程

①先从Launcher的startActivity()方法,通过Binder通信,调用ActivityManagerService的startActivity方法。

②一系列折腾,最后调用startProcessLocked()方法来创建新的进程。

③该方法会通过前面讲到的socket通道传递参数给Zygote进程。Zygote孵化自身。调用ZygoteInit.main()方法来实例化ActivityThread对象并最终返回新进程的pid。

④调用ActivityThread.main()方法,ActivityThread随后依次调用Looper.prepareLoop()和Looper.loop()来开启消息循环。

2.绑定Application

①上面创建进程后,执行ActivityThread.main()方法,随后调用attach()方法。

②将进程和指定的Application绑定起来。通过Binder IPC向sytem_server进程发起attachApplication请求。这个是通过上节的ActivityThread对象中调用bindApplication()方法完成的。该方法发送一个BIND_APPLICATION的消息到消息队列中, 最终通过handleBindApplication()方法处理该消息. 然后调用makeApplication()方法来加载App的classes到内存中。

3.显示Activity界面

经过前两个步骤之后, 系统已经拥有了该application的进程。 后面的调用顺序就是普通的从一个已经存在的进程中启动一个新进程的activity了。

实际调用方法是realStartActivity(), 它会调用application线程对象中的scheduleLaunchActivity()发送一个LAUNCH_ACTIVITY消息到消息队列中, 通过 handleLaunchActivity()来处理该消息。在 handleLaunchActivity()通过performLaunchActiivty()方法回调Activity的onCreate()方法和onStart()方法,然后通过handleResumeActivity()方法,回调Activity的onResume()方法,最终显示Activity界面。

醒醒醒醒,划重点啦
到此为止,从桌面点击APP到APP的Activity界面显示的整个流程已经分析完了。但这只是把Activity显示出来了,Activity里面还包含很多的控件,也就是View,这些View才是最终显示给我们看到的,Activity只是管理这些View的生命周期而已。
所以,你懂的,不要犯困,俺们在坚持坚持!接下来分析的是Activity之后自定义View是如何从绘制测量布局等一步步显示到我们眼睛的!

三、View显示过程中常见名词解释

1.Activity

Activity并不负责视图控制,它只是控制生命周期和处理事件。真正控制视图的是Window。一个Activity包含了一个Window,Window才是真正代表一个窗口。Activity就像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与Window、以及View进行交互。

2.Window

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

3.DecorView

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <!-- Popout bar for action modes -->
    <ViewStub
        android:id="@+id/action_mode_bar_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inflatedId="@+id/action_mode_bar"
        android:layout="@layout/action_mode_bar"
        android:theme="?attr/actionBarTheme" />

    <FrameLayout
        style="?android:attr/windowTitleBackgroundStyle"
        android:layout_width="match_parent"
        android:layout_height="?android:attr/windowTitleSize">

        <TextView
            android:id="@android:id/title"
            style="?android:attr/windowTitleStyle"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical" />
    </FrameLayout>

    <FrameLayout
        android:id="@android:id/content"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foreground="?android:attr/windowContentOverlay"
        android:foregroundGravity="fill_horizontal|top" />
</LinearLayout>

在Activity中通过setContentView所设置的布局文件其实就是被加到内容栏之中的,成为其唯一子View,就是上面的id为content的FrameLayout中。所以我们每天在Activity里自动生成的setContentView(R.layout.activity_main)加载的布局就是到DecorView中的内容栏的。

4.ViewRoot

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

四、DecorView的创建

这部分内容主要讲DecorView是怎么一层层嵌套在Actvity,PhoneWindow中的,以及DecorView如何加载内部布局。

先是从Activity中的setContentView()开始。

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

可以看到实际是交给Window装载视图。下面来看看Activity是怎么获得Window对象的?

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) {
        ..................................................................
        mWindow = new PhoneWindow(this, window);//创建一个Window对象
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);//设置回调,向Activity分发点击或状态改变等事件
        mWindow.setOnWindowDismissedCallback(this);
        .................................................................
        mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//给Window设置WindowManager对象
        ....................................................................
}

在Activity中的attach()方法中,生成了PhoneWindow实例。既然有了Window对象,那么我们就可以设置DecorView给Window对象了。

public void setContentView(int layoutResID) {
    if (mContentParent == null) {//mContentParent为空,创建一个DecroView
        installDecor();
    } else {
        mContentParent.removeAllViews();//mContentParent不为空,删除其中的View
    }
    mLayoutInflater.inflate(layoutResID, mContentParent);//为mContentParent添加子View,即Activity中设置的布局文件
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();//回调通知,内容改变
    }
}

看了下来,可能有一个疑惑:mContentParent到底是什么? 就是前面布局中@android:id/content所对应的FrameLayout。

通过上面的流程我们大致可以了解先在PhoneWindow中创建了一个DecroView,其中创建的过程中可能根据Theme不同,加载不同的布局格式,例如有没有Title,或有没有ActionBar等,然后再向mContentParent中加入子View,即Activity中设置的布局。到此位置,视图一层层嵌套添加上了。

五、DecorView的显示

以上仅仅是将DecorView建立起来。通过setContentView()设置的界面,为什么在onResume()之后才对用户可见呢?

这就要从ActivityThread开始说起。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    //就是在这里调用了Activity.attach()呀,接着调用了Activity.onCreate()和Activity.onStart()生命周期,
    //但是由于只是初始化了mDecor,添加了布局文件,还没有把
    //mDecor添加到负责UI显示的PhoneWindow中,所以这时候对用户来说,是不可见的
    Activity a = performLaunchActivity(r, customIntent);

    ......

    if (a != null) {
    //这里面执行了Activity.onResume()
    handleResumeActivity(r.token, false, r.isForward,
                        !r.activity.mFinished && !r.startsNotResumed);

    if (!r.activity.mFinished && r.startsNotResumed) {
        try {
                r.activity.mCalled = false;
                //执行Activity.onPause()
                mInstrumentation.callActivityOnPause(r.activity);
                }
        }
    }
}

重点看下handleResumeActivity(),在这其中,DecorView将会显示出来,同时重要的一个角色:ViewRoot也将登场。

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()方法之后,界面才对我们是可见的。

void makeVisible() {
   if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());//将DecorView添加到WindowManager
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);//DecorView可见
}

到此DecorView便可见,显示在屏幕中。但是在这其中,wm.addView(mDecor, getWindow().getAttributes());起到了重要的作用,因为其内部创建了一个ViewRootImpl对象,负责绘制显示各个子View。

具体来看addView()方法,因为WindowManager是个接口,具体是交给WindowManagerImpl来实现的。

public final class WindowManagerImpl implements WindowManager {    
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
}

交给WindowManagerGlobal 的addView()方法去实现

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

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

    ......

    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显示过程

其实ViewRootImpl的作用不止如此,还有许多功能,如事件分发。

要知道,当用户点击屏幕产生一个触摸行为,这个触摸行为则是通过底层硬件来传递捕获,然后交给ViewRootImpl,接着将事件传递给DecorView,而DecorView再交给PhoneWindow,PhoneWindow再交给Activity,然后接下来就是我们常见的View事件分发了。

硬件 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity

终于熬到头了!!!!作者,你这个骗子!老是骗我说在坚持坚持!其实没骗你,看到这,你肯定不想骂我了,你肯定受益匪浅!

六、从Activity初始化到显示各个View到界面过程总结

1.从Activity的setContentView(R.id.layout)开始,加载一个布局id到DecorView的内容栏中。
2.在Activity中的attach方法中初始化Window抽象类的实例PhoneWindow对象。
3.把加载进来的布局id也就是DecorView添加到PhoneWindow中。到处视图就嵌套上来了。
4.在ActivityThread类中的handleLaunchActivity方法中调用了Activity.attach()方法,然后开始Activity生命周期的创建。
5.调用里面的handleResumeActivity()方法中的makeVisible()显示布局,也就是DecorView.
6.在makeVisible()过程中创建了一个ViewRoot抽象类的对象ViewRootImpl。
7.WindowManager交给WindowManagerImpl实现,再交给WindowManagerGlobal 的addView()方法。
里面实例化了ViewRootImpl对象,然后调用其setView()方法。
8.setView()方法经过各种调用最终调用performTraversals()方法,然后就是自定义View的显示流程了
9.先调用peformMeasure,再measure,再onMeasure完成测量
10.同理完成布局Layout
11.在同理完成绘制Draw
12.最终显示出来

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值