关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android中Activity启动过程探究

    Android中Activity启动过程探究

    2014-10-15 00:00:00 出处:swordsman
    分享

    首先追溯到Activity的启动,随便启动一个自己写的demo项目,使用DDMS进行debug标记,然后在Debug中把主线程暂停,可以看到调用栈。如下图所示:

    于是我们先看android.app.ActivityThread的main()方法。

    android.app.ActivityThread.main()

    main()方法中对一个Looper对象进行初始化,形成一个消息循环,那么任何主线程的操作都会发送到这个Looper对应的Handler中去。通过源码,辗转反侧找到Handler的定义,它是ActivityThread中定义的一个内部类,名为H,继承自Handler。

    观察它的handleMessage()方法,发现了其中有一个what值为LAUNCH_ACTIVITY的switch分支,其中调用了handleLaunchActivity()方法。

    接下来看android.app.ActivityThread.handleLaunchActivity()方法。

    android.app.ActivityThread.handleLaunchActivity()

    如上图所示,该方法中执行了两个比较关键的步骤,一个是performLaunchActivity(),另一个是handleResumeActivity()。

    先来看performLaunchActivity()做了什么。

    android.app.ActivityThread.performLaunchActivity()

    以下是部分源码,我做了一些省略。

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    
        ...
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
    
        try {
            ...
            if (activity != null) {
                ...
    
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config);
                ...
                activity.mCalled = false;
                mInstrumentation.callActivityOnCreate(activity, r.state);
                ...
            }
            ...
        }
        ...
    }

    重点关注红色加粗的部分:

    7, 8行:通过Activity的类名构建一个Activity对象。

    27行:调用了Activity.attach()方法。

    32行:通过Instrumentation对象执行Activity的onCreate()方法,Activity的生命周期方法都是由Instrumentation对象来调用的。

    其中attach()方法里面也做了很重要的事情。

    android.app.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) {
        attachBaseContext(context);
    
        mFragments.attachActivity(this, mContainer, null);
    
        mWindow = PolicyManager.makeNewWindow(this);
    
        ... //将各种参数赋给Activity的成员变量
    
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }

    首先给Activity.mWindow成员变量赋值,然后给mWindow变量设置WindowManager,然后给Activity.mWindowManager赋值。

    mWindow是一个Window类型的变量,但实际上它是一个PhoneWindow对象,与Activity的内容显示相关。

    上面的attach()方法调用完成后,就自然而然的调用了Activity的onCreate()方法了。

    按一般的情况,Activity中的onCreate()方法调用了setContentView()方法,而setContentView()方法并不是由Activity实现的,以下是android.app.Activity中的一段代码:

    1 public void setContentView(View view, ViewGroup.LayoutParams params) {
    2  getWindow().setContentView(view, params);
    3     initActionBar();
    4 }

    而getWindow()返回的是一个android.app.Window对象,这个对象就是刚刚在attach()中赋值的mWindow成员变量。

    android.app.Window是一个抽象类,其中setContentView()方法并没有具体的实现,而这个方法的真正实现是com.android.internal.policy.impl.PhoneWindow类。使用类图表示:

    以下是PhoneWindow中的setContentView()的代码。

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mContentParent.addView(view, params);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

    PhoneWindow类中有两个和视图相关的成员变量,一个是DecorView mDecor,另一个是ViewGroup mContentParent。

    来看官方文档的描述:

    再回到PhoneWindow.setContentView(View, ViewGroup.LayoutParams)中的installDecor()方法做了什么。

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            ...
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
        }
    }

    mDecor是通过一个generateDecor()方法来获取的。而generateDecor()就是new了一个DecorView而已。

    1 protected DecorView generateDecor() {
    2     return new DecorView(getContext(), -1);
    3 }

    DecorView是PhoneWindow类中定义的一个内部类,它继承了FrameLayout,用来作为整个PhoneWindow的根视图。

    再来看generateLayout()做了什么事情。

    protected ViewGroup generateLayout(DecorView decor) {
    
        //...以上省去,大致上是与样式,主题,版本相关的对视图的设置。
    
        //以下开始填充decor
    
        // Inflate the window decor.
        int layoutResource;    //这个是用来inflate的id
    
        ...    //这里省去,内容是根据Window的各种特性(feature)选择一个合适的视图id赋给layoutResource
    
        mDecor.startChanging();
    
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);    //注意这个地方
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
    
        ... //省去,内容是设置背景,设置ActionBar的一些属性。
    
        mDecor.finishChanging();
    
        return contentParent;
    }

    可以看出,这个方法(generateLayout())做了如下几件比较关键的事情:

    1.根据各种FLAG值设置了LayoutParam参数,以上代码省略了这部分。

    2.根据各种FEATURE值,选择了一个合适的类似于R.layout.something这样的布局id赋值给layoutResource。

    3.将layoutResource填充为一个View对象,加入到DecorView中。

    4.【不能确定,猜测】layoutResource所指示的布局文件中有一个id为ID_ANDROID_CONTENT的ViewGroup对象,上述代码中第17行,源码“非常自信”的findViewById获取到这个ViewGroup。

    5.最后将contentParent返回给PhoneWindow的成员变量mContentParent。

    这样,我们就知道了成员变量mDecor、mContentParent的来历。在学习的过程中,我还同时了解到,mDecor管理着一个ActionBar。

    综合以上的探究,加上自己的一些思考和猜测。对PhoneWindow做一下小小的总结:

    1.一个Activity对应着一个PhoneWindow对象,是一对一的关系,假如从Activity A启动到Activity B,那么Activity B会创建一个自己的PhoneWindow对象。

    2.PhoneWindow管理着整个屏幕的内容,不包括屏幕最顶部的系统状态条。所以,PhoneWindow或者Window是与应用的一个页面所相关联。

    3.PhoneWindow同时管理着ActionBar和下面的内容主题,setContentView()方法是用来设置内容主体的,而setTitle()等其他方法就是操作ActionBar的,Window中定义的requestFeature()等方法,有很多与ActionBar属性相关的设置。另外这些方法都是公有方法,显然是为了给客户端程序员调用的,也进一步佐证了这些操作的意义与作用。

    4.PhoneWindow自己并不是一个视图(View),它的成员变量mDecor才是整个界面的视图,mDecor是在generateLayout()的时候被填充出来的,而actionBar和contentParent两个视图都是通过findViewById()直接从mDecor中获取出来的。

    以上讲了这么多,其实只是把installDector()方法给执行完了。接下来是mContentParent.removeAllViews()。这个好理解,假如setContentView()被调用两次,第二次肯定要把里面的内容都给清空移除了。移除后就是添加,mContentParent.addView(view, params)。

    这个方法是ViewGroup中的一个方法,贴上源代码:

    首先调用的是requestLayout(),这里的这个方法呢实际上没有起到作用的。

    ViewGourp.requestLayout()实际上是调用的View.requestLayout()方法,而View.requestLayout()方法中,除去做了一些View本身的操作外,实际上调用的是View中的mParent成员变量的requestLayout()方法,而mParent这个成员变量是一个ViewParent类型的对象,ViewParent是一个接口,View中的mParent对象是通过View.assignParent(ViewParent)方法来赋值的,而assignParent()方法是由ViewRootImpl.setView()方法调用的……暂时就不考虑它了,要明确的一点,就是requestLayout()并没有起到具体的作用。

    接下来观察addViewInner()方法,这个方法其实就是将child加入到自己的一个View数组中保存起来,然后在把这个child的parent标记为自己。

    到此为止,setContentView()方法基本就执行完毕了,这个时候界面还没有显示出任何东西来,而仅仅是将mDecor->mContentParent->(customer layout)一个这样的树状结构给搭建好了而已。

    假设setContentView()方法是onCreate()方法中唯一一个方法调用的话,那么onCreate()方法也执行完了,调用栈继续回退,就回到了android.app.ActivityThread.handleLaunchActivity()中,以上的所以就是刚刚执行完了android.app.ActivityThread.performLaunchActivity()。

    接下来执行第二个关键性的方法handleResumeActivity()。

    android.app.ActivityThread.handleResumeActivity()

    贴上省略后的代码:

    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
            boolean reallyResume) {
        ...
    
        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.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
                ...
            }
        ...
        }
    }

    注意红色加粗的部分:

    首先是performResumeActivity()方法,这个方法内就是通过Instrumentation调用Activity的onResume()方法。

    下面的wm.addView()方法非常关键,wm是上面a.getWindowManager()获取到的,a是Activity,getWindowManager()就是返回它的mWindowManger对象,而这个对象是WindowManagerImpl,它的内部方法大部分是代理的WindowManagerGlobal,这个在上面的内容中已经提到了。

    然而,这个WindowManger的addView()是干了什么呢?

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
    
        ...
            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) {
            ...
            throw e;
        }
    }

    从上面的代码可以看出,addView方法中,new了一个ViewRootImpl对象,然后调用ViewRootImpl.setView()方法。

    android.view.ViewRootImpl.setView()

    /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
    
                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
    
                ...
    
                view.assignParent(this);
                ...
            }
        }
    }

    省略后的代码如上所示,首先将传进来的参数view赋值给mView,这里有一点要明确ViewRootImpl其实并不是一个View的子类……因此我认为,mView将是这个对象所认识的root节点,也是整个Activity的root的节点。

    接下来调用了requestLayout()方法,这个方法是有效的!

    最后将view的父亲注册为自己。终于终于,mDecor知道了自己父亲是谁,或者说,整个Activity设置了一个根节点,在此之前,我们setContentView()将自己的layout布局add到PhoneWindow.mContentParent的时候,mDecor都不知道自己的parent是哪个,现在整个view的树形结构中有了根节点,也就是ViewRootImpl,那么requestLayout()就有效了,就可以进行后面的measure,layout,draw三步操作了。

    android.view.ViewRootImpl.requestLayout()

    该方法首先检查了是否在主线程,然后就执行了scheduleTraversals()方法。看这个方法的名字,就知道是执行一次遍历,遍历的对象就是根节点开始的view tree。

    android.view.ViewRootImpl.scheduleTraversals()

    注意标记出来的Runnable对象,这个Runnable的run()方法中,调用了doTraversal()方法。而doTraversal()方法又调用了performTraversals()方法,这个方法非常长,依次调用了performMeasure(),performLayout(),performDraw()三个方法,终于开始了控件层的测量,布局,绘制三个步骤。对于这些的探究就留到下一篇博客中好了,这篇已经写的够长了。

    小结:

    花了两天时间在grepcode上看源代码,感觉还是有点收获,学习到了一些以前从未了解的东西,最大的感触就是,只要源码给的全,慢慢看还是可以琢磨出来的。另外,关于这些内容与实际应用之间的联系,还有待进一步的探究和经验的积累。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    IT头条

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    严重缩水!NVIDIA将推中国特供RTX 5090 DD:只剩24GB显存

    00:17

    无线路由大厂 TP-Link突然大裁员:补偿N+3

    02:39

    Meta 千万美金招募AI高级人才

    00:22

    技术热点

    Spring基础知识汇总 Java开发必看

    SQL Server索引与其性能的描述

    SQL Server 2008数据格式修改时应注意什么?

    如何禁止windows 7网络搜索驱动?windows 7禁止网络搜索驱动的方

    SQL Server系统表中的sysconfigures表

    如何恢复windows 7、windows 8图片预览功能详细图解

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。