什么是Activity?

Activity是 用户操作的可视化界面;它为用户提供了一个放置视图和交互操作的窗口。采用setContentView的方法提供。因此,可以理解Activity、Window、View三者关系为。Activity提供Window ,View被添加到Window中。

以刷墙举例:

Activity可以理解为房间,Window就是房间内的墙面, 我们在墙面上可以刷各种不同的图案,这些图案就是View。

Activity View的加载流程

1、Activity在被创建之初,调用了attach方法,这个时候,为Activity创建了一个PhoneWindow, 并且为PhoneWindow设置了事件交互的回掉。

2、紧接着Activity的onCreate()方法被回掉。这里也就到了我们经常复写方法,我们在OnCreate()之中,调用setContentView(id)。

3、在setContentView(), PhoneWindow 创建了一个顶级视图 DecorView (FrameLayout)的子类。

4、紧接着,DecorView会依据一些feature(类似NO_ACTICON_BAR)来,添加一个layout。这个Layout中包含了Title、content。其中content也是FrameLayout,也就是我们在setContentView(id),将视图添加的父容器。

所以我们必须要在setContentView之前设置实现全屏

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,  WindowManager.LayoutParams.FLAG_FULLSCREEN);
实现无标题栏(但有系统自带的任务栏):
requestWindowFeature(Window.FEATURE_NO_TITLE); 

之前一直不明白为什么android的view层次是这么个布局,现在终于有所了解了。。其实上图还有点问题,其实在DecorWindow中还有一个id为statusBarBackgroud和一个id为navigationBarBackground的View,分别表示手机的顶部的状态栏和手机底部的导航栏。。

Activity中加载布局,都是通过在onCreate中调用setContentView方法开始:

    @Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
ButterKnife.bind(this);
}

Activity中有setContentView重载方法,一个是layoutid,一个直接传入view

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

实际上调用的都是PhoneWindow的setContentView方法。initWindowDecorActionBar一看就知道是初始化actionbar的

    @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();//如果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 {
//可以看到mContentParent这个view作为layoutResID的parent,所以layoutResID根width/height参数有效  
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
} @Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));//所以始终是MATCH_PARENT,所以该view的width/height无效
} @Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);//把这个view加入到mContentParent中了,同时layoutparam为MATCH_PARENT
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}

setContentView(View view)   所以view设置的layout_width/layout_height无效,始终是MATCH_PARENT

setContentView(int layoutResID)  因为是通过inflate(id,mParent)  所以设置的layout_width/layout_height有效。。。

inflate方法的解析,可以查看Android LayoutInflater原理分析

既然mContentParent是在installDecor方法中初始化的,那么

      private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();//初始化mDecor这个View
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//通过mDecor对象初始化mContentParent
.....
}
.....
}

可以看到通过generateDecor方法生成了DecorView,这个DecorView其实也是所有应用窗口的根View

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

然后在generateLayout方法中初始化了mContentParent对象

    protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
......... if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { //获取theme中的设置,如果是NoTitle
requestFeature(FEATURE_NO_TITLE);//调用该方法,表示无标题
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { //获取theme中的设置,如果是ActionBar
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);//调用该方法,表示actionbar
}
......... int layoutResource;
int features = getLocalFeatures();//记得上文中的requestFeature,然后就可以通过getLocalFeatures方法获取了。。其实在activity中可以调用requestFeature
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
}
..........
else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
............ View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));//把上述得到的layoutResource添加到decor中
mContentRoot = (ViewGroup) in; ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//获取id=content的view作为contenparent
............
}

可以看见上面方法主要作用就是根据窗口的风格修饰类型为该窗口选择不同的窗口根布局文件。mDecor做为根视图将该窗口根布局添加进去,然后获取id为content的FrameLayout返回给mContentParent对象。所以installDecor方法实质就是产生mDecor和mContentParent对象。

另一方面如果要设置窗口风格,必须放在setContentView的前面

requestWindowFeature(Window.FEATURE_NO_TITLE);//getLocalFeatures方法中被获取

setContentView(R.layout.test_layout);

当窗口风格是NoTitle,同时没有actionbar的时候,那么窗口根布局就是screen_title.xml.xml

<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>

@android:id/content  这个其实就是mContentParent,同时还有@+id/action_mode_bar_stub的ViewStub,都被添加到了DecorView中去了

为了验证这个猜测,打开Android\android-sdk\tools\hierarchyviewer工具验证

在我的代码中,既没有设置title也没有设置actionbar

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"> <Button
android:id="@+id/test"
android:text="Test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/startAnim"
android:layout_marginTop="100dp"
android:background="@android:color/holo_red_dark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start"/> </LinearLayout>
    <style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>

所以最后整个view视图层次如下图

DecorView果然作为根View,其下有三个子view,id为statusBarBackgroud和id为navigationBarBackground的View,分别表示手机的顶部的状态栏和手机底部的导航栏。然后是一个LinearLayout,很明显这个就是上面提到的screen_title.xml.xml中的根LinearLayout。然后包括一个@+id/action_mode_bar_stub的ViewStub,一个@android:id/content的FrameLayout,,,然后因为在setContentView中,下面两种方式都把这个FrameLayout作为了main_activity.xml的根视图。。

mLayoutInflater.inflate(layoutResID, mContentParent);

mContentParent.addView(view, params);

最后inflate内部其实还是调用addview,然后一直如果该view是viewgroup,那么viewgroup又会把其中所有的子view都add进去,所以最后view就形成了一个视图层次。

inflate原理可以参考Android LayoutInflater原理分析

那最后知道了所有的view的根viewgroup就是上面的decorview,那么这个decorview又是在哪里被添加的呢?

handler机制我们知道了启动Activity其实都会启动activityThread的main方法,这个方法里面会创建主线程的looper。

启动Activity调用完ActivityThread的main方法之后,接着调用ActivityThread类performLaunchActivity来创建要启动的Activity组件,
在创建Activity组件的过程中,还会为该Activity组件创建窗口对象和视图对象;接着Activity组件创建完成之后,通过调用ActivityThread类的handleResumeActivity将它激活。

(在onCreate中调用了setContentView,所以把除了decorView之外的所有的view都已经添加进去了。)handleResumeActivity中把decorView添加进去了

handleResumeActivity方法中调用了r.activity.makeVisible();

    final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
......
// TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) {
......
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
......
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
......
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
......
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
......
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
......
}
}

Activity中makeVisible方法,把这个DecorView添加到wm中。。

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

另一方面,wm.addView实际上调用的是WindowManagerGlobal中addView方法,此时创建了ViewRootImpl对象。。ViewRootImpl有木有很熟悉,在绘制View过程中,就是从ViewRootImpl的performTraversals方法开始的,然后依次经过测量,布局,绘制过程。。invalidate其实最后也是调用了ViewRootImpl的performTraversals方法

    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);
}

总结一下:

1.创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。
2.依据Feature等style theme创建不同的窗口修饰布局文件,并且通过findViewById获取Activity布局文件该存放的地方(窗口修饰布局文件中id为content的FrameLayout)。
3.将Activity的布局文件添加至id为content的FrameLayout内。

 

Android View的加载流程的更多相关文章

  1. Android View的加载过程

    大家都知道Android中加载view是从Activity的onCreate方法调用setContentView开始的,那么View的具体加载过程又是怎么的呢?这一节我们做一下分析. 首先追踪一下代码 ...

  2. android源码解析(十七)-->Activity布局加载流程

    版权声明:本文为博主原创文章,未经博主允许不得转载. 好吧,终于要开始讲讲Activity的布局加载流程了,大家都知道在Android体系中Activity扮演了一个界面展示的角色,这也是它与andr ...

  3. View的呈现(二)加载流程

    这块涉及到Code+Razor模板=>html[output流] 而这块的问题在于Razor最后生成了什么?--对象:一个类文件:eg:index.cshtml  => index_cst ...

  4. 8. Android加载流程(打包与启动)

    移动安全的学习离不开对Android加载流程的分析,包括Android虚拟机,Android打包,启动流程等... 这篇文章  就对Android的一些基本加载进行学习. Android虚拟机 And ...

  5. Android UI之View的加载机制(二)

    转载请标明出处:http://blog.csdn.net/sk719887916/article/details/39961201,作者:skay 对于接触安卓开不到一年的自己来说,总结下view的生 ...

  6. Android 高清加载巨图方案 拒绝压缩图片

    Android 高清加载巨图方案 拒绝压缩图片 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/49300989: 本文出自:[张 ...

  7. CI加载流程小结

    无聊,决定水一把. CI(CodeIgniter)是我最早接触的一个框架,到现在也只是用了其中一点零碎的方法.一直想对其流程做个小结,却总是因各种各样的“理由”挨着.看见别人图表齐上阵,没那耐心,就从 ...

  8. 实现Android ListView 自动加载更多内容

    研究了几个小时终于实现了Android ListView 自动加载的效果. 说说我是怎样实现的.分享给大家. 1.给ListView增加一个FooterView,调用addFooterView(foo ...

  9. Cocos Creator 资源加载流程剖析【一】——cc.loader与加载管线

    这系列文章会对Cocos Creator的资源加载和管理进行深入的剖析.主要包含以下内容: cc.loader与加载管线 Download部分 Load部分 额外流程(MD5 Pipe) 从编辑器到运 ...

随机推荐

  1. 一种无法被Dump的jar包加密保护解决方案

    作者: 我是小三 博客: http://www.cnblogs.com/2014asm/ 由于时间和水平有限,本文会存在诸多不足,希望得到您的及时反馈与指正,多谢! 工具环境: windwos10.I ...

  2. 阻塞IO和非阻塞IO

    1 TCP协议 每一个TCP通信的的socket的内核里面都会有一个发送缓冲区和接收缓冲区 发送端 : send 报文 -- TCP发送缓冲区 -- 接收端 :TCP接收缓冲区 -- receive ...

  3. 为什么有了uwsgi还要nginx这个“前端”服务器

    相信每一个使用nginx+uwsgi+django部署过的人,都感到非常复杂.到底为什么一个项目的发布要经过这么多层级,他们每一层有什么理由存在?这就带大家宏观地看待一下 首先nginx 是对外的服务 ...

  4. 第五章、Django之模型层---单表操作

    目录 第五章.Django之模型层---单表操作 一.ORM查询 二.Django测试环境搭建 三.单表查询 1. 增 2. 改 3. 删 4. 查 第五章.Django之模型层---单表操作 一.O ...

  5. Python3简易接口自动化测试框架设计与实现(中)

    目录 7.Excel数据读取 7.1.读取配置文件 7.1.编写Excel操作类 8.用例组装 9.用例运行结果校验 10.运行用例 11 .小结 上一篇:Python3简易接口自动化测试框架设计与实 ...

  6. es中的相关知识一(基本知识和id的定义)

    一.es中文档的元数据包括: 1._index: 索引(index)类似于关系型数据库里的数据库(database),事实上,我们的数据被存储和索引在分片(shards)中,索引知识把一个或多个分片分 ...

  7. PHP 提取数组中奇数或偶数的元素array_filter

    //提取奇数 $filter = array_filter($ql,function($var){ return($var & 1); },ARRAY_FILTER_USE_KEY); pri ...

  8. Switch按钮

    使用CSS+HTML5修改原生checkbox为Switch Button .switch { width: 45px; height: 15px; position: relative; borde ...

  9. BZOJ4886 [Lydsy1705月赛]叠塔游戏[基环树]

    很妙的一道题. 由于本人过于zz,不会这道题,通过厚颜无耻翻阅题解无数终于懂了这道题,所以这里转载一位神仙的blog. 没有看懂?没事,再来一篇. 这题个人认为主要在于转化题意和建图,这两点想通了应该 ...

  10. BZOJ 2982 combination 脑子+组合数学

    可以发现,整个数列构成一个树形结构,并且是个完全二叉堆(小根堆). 并且这个堆的形态在给定$n$后是固定的,第1个位置上显然只能放1. 对子树的根来说,他自己是所分得的数集中最小的那个,所以从剩下$s ...