【自定义View系列】android的UI结构

417人阅读 评论(0) 收藏 举报
分类:

一.androidUI相关理论

  当ActivityThread接收到AMS发送start某个Activity后,就会创建指定的Activity对象。Activity又会创建PhoneWindow类-DecorView类-创建相应的View或者ViewGroup。创建完成后,Activity需要把创建好的界面显示到屏幕上,于是调用WindowManager类,后者于是创建一个ViewRoot对象,改对象实际上创建了ViewRoot类和W类,创建ViewRoot对象后,WindowManager再调用WMS提供的运程接口完成添加一个窗口并显示到屏幕上。

二.我们直接根据栈帧来分析

这里写图片描述

1.当ActivityThread接受到AMS发送start某个Activity后,就会创建指定的Activity对象

具体代码见:ActivityThread的handleMessage方法

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    ActivityRecord r = (ActivityRecord)msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo);
                    handleLaunchActivity(r, null);
                } break;
                case RELAUNCH_ACTIVITY: {
                    ActivityRecord r = (ActivityRecord)msg.obj;
                    handleRelaunchActivity(r, msg.arg1);

MainActivity启动走的是这个分支:
我们看一下handleLaunchActivity方法,其中最核心的一句代码如下:

Activity a = performLaunchActivity(r, customIntent);

我们再跟踪下去看performLaunchActivity这个方法:
其中最核心的一句代码如下:

// 通过反射初始化了一个Activity
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...
// 调用Activity的onCreate方法
mInstrumentation.callActivityOnCreate(activity, r.state);
...
// 调用Activity的onStart方法
activity.performStart();  

然后就走到了Activity的onCreate方法

2.Activity的setContentView方法

  Activity是在onCreate方法中,主要使用setContentView方法来加载布局的,那么它内部的源码是怎么实现的呢?

Activity中的setContentView方法的具体源码:

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

getWindow()得到一个Window对象 mWindow。它的具体初始化的地方在:

mWindow = PolicyManager.makeNewWindow(this);

继续看看 PolicyManager类

public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";

    private static final IPolicy sPolicy;

    static {
        // Pull in the actual implementation of the policy at run-time
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
        } catch (InstantiationException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        }
    }

    // Cannot instantiate this class
    private PolicyManager() {}

    // The static methods to spawn new policy-specific objects
    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }

    public static LayoutInflater makeNewLayoutInflater(Context context) {
        return sPolicy.makeNewLayoutInflater(context);
    }

    public static WindowManagerPolicy makeNewWindowManager() {
        return sPolicy.makeNewWindowManager();
    }
}

第57 行 sPolicy对象是有第 38,39行通过类路径”com.Android.internal.policy.impl.Policy“生成的,那么我们在源码中找到 Policy类,在此类中找到了如下方法:

    public PhoneWindow makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }

由此可见,我们终于找到Activity类中的 mWindow对象的实现类了,就是PhoneWindow类。

所以现在调用的实际上是PhoneWindow的setContentView方法

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null) {
            cb.onContentChanged();
        }
    }

看installDecor方法:

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer = findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            }
        }
    }

在代码的第 3 行我们看到 mDecor = generateDecor();方法调用,继续跳进 generateDecor()方法:

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

这里生成一个DecorView对象,DecorView是PhoneWindow类的内部类,继承自FrameLayout。到目前为止,setContentView

方法里生成一个FrameLayout类型的DecorView组件。

继续分析代码,看第 11 行:

mContentParent = generateLayout(mDecor);

把 DecorView 对象 mDecor 作为参数传递给 generateLayout方法得到 mContentParent。generateLayout()方法中的代码实现如下:

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();

        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }

        /**以下这些是Activity 窗口属性特征的设置*/
        //窗口是否浮动,一般用于Dialog窗口是否浮动:是否显示在布局的正中间。
        mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }

        //设置窗口是否支持标题栏,隐藏显示标题栏操作在此处。
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        }

        //当前Activity是否支持全屏
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags()));
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
            setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
        }

        WindowManager.LayoutParams params = getAttributes();

        //设置输入法的状态
        if (!hasSoftInputMode()) {
            params.softInputMode = a.getInt(
                    com.android.internal.R.styleable.Window_windowSoftInputMode,
                    params.softInputMode);
        }

        if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled,
                mIsFloating)) {
            /* All dialogs should have the window dimmed */
            if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            }
            params.dimAmount = a.getFloat(
                    android.R.styleable.Window_backgroundDimAmount, 0.5f);
        }

        //设置当前Activity的出现动画效果
        if (params.windowAnimations == 0) {
            params.windowAnimations = a.getResourceId(
                    com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
        }

        // The rest are only done if this window is not embedded; otherwise,
        // the values are inherited from our container.
        if (getContainer() == null) {
            if (mBackgroundDrawable == null) {
                if (mBackgroundResource == 0) {
                    mBackgroundResource = a.getResourceId(
                            com.android.internal.R.styleable.Window_windowBackground, 0);
                }
                if (mFrameResource == 0) {
                    mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0);
                }
                if (false) {
                    System.out.println("Background: "
                            + Integer.toHexString(mBackgroundResource) + " Frame: "
                            + Integer.toHexString(mFrameResource));
                }
            }
            mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);
        }

        //以下代码为当前Activity窗口添加 decor根布局。
        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                layoutResource = com.android.internal.R.layout.dialog_title_icons;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title_icons;
            }
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = com.android.internal.R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                layoutResource = com.android.internal.R.layout.dialog_custom_title;
            } else {
                layoutResource = com.android.internal.R.layout.screen_custom_title;
            }
        } 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) {
                layoutResource = com.android.internal.R.layout.dialog_title;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = com.android.internal.R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();

        //通过布局添加器LayoutInflater获取layoutResource布局,
        View in = mLayoutInflater.inflate(layoutResource, null);
        //将XML资源为layoutResource的布局添加到decor容器里面,至此PhoneWindow 内部类DecorView就添加了之布局
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

        //此处很重要,通过findViewById找到 contentParent容器,也是该方法的返回值。
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        //以下代码设置Activity窗口的背景,标题等
        if (getContainer() == null) {
            Drawable drawable = mBackgroundDrawable;
            if (mBackgroundResource != 0) {
                drawable = getContext().getResources().getDrawable(mBackgroundResource);
            }
            mDecor.setWindowBackground(drawable);
            drawable = null;
            if (mFrameResource != 0) {
                drawable = getContext().getResources().getDrawable(mFrameResource);
            }
            mDecor.setWindowFrame(drawable);

            // System.out.println("Text=" + Integer.toHexString(mTextColor) +
            // " Sel=" + Integer.toHexString(mTextSelectedColor) +
            // " Title=" + Integer.toHexString(mTitleColor));

            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }

            if (mTitle != null) {
                setTitle(mTitle);
            }
            setTitleColor(mTitleColor);
        }

        mDecor.finishChanging();

        return contentParent;
    }

我们具体来看看decor中添加的布局到底是什么样子的,我们来看下R.layout.screen_simple

            layoutResource = com.android.internal.R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();

        //通过布局添加器LayoutInflater获取layoutResource布局,
        View in = mLayoutInflater.inflate(layoutResource, null);
        //将XML资源为layoutResource的布局添加到decor容器里面,至此PhoneWindow 内部类DecorView就添加了之布局
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <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:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

  原来我们的DecorView根布局里面添加了类似上面的布局,线性布局LinearLaout里包含两个组件,ViewStub是懒加载,默认不显示,FrameLayout是什么呢?看看id=content,就是我们184行找到的父容器 contentParent。那么这个父容器 contentParent有什么作用呢?

  我们回到setContentView的第 11行,mContentParent = generateLayout(mDecor); 获得 父容器 mContentParent。我们再次回到 generateLayout步的第17行, mLayoutInflater.inflate(layoutResID, mContentParent); 这里通过LayoutInflater将 setContentView(layoutResID)传进来的布局id加载到 父容器mContentParent中,至此,setContentView就将布局添加到Activity里面了。

三.总结:现在我们来梳理一下流程:

  Activity setContentView—>Window setContentView—>PhoneWindow setContentView—->PhoneWindow installDecor—–>PhoneWindow generateLayout——>PhoneWindow mLayoutInflater.inflate(layoutResID, mContentParent);

  Activity 类中有一个Window抽象类的实现PhoneWindow类,该类中有个内部类DecorView,继承自FrameLayout,在DecorView容器中添加了根布局,根布局中包含了一个id为 contnet的FrameLayout 内容布局,我们的Activity加载的布局xml最后添加到 id为content的FrameLayout布局当中了。用一个图来描述,如下:

这里写图片描述

这里写图片描述

四.其中涉及的概念的解释

ActivityThread:该类为应用程序的主线程类,所有的apk程序都有且仅有一个ActivityThread类,程序的入口为该类中的static main函数,ActivityThread所在的线程即为UI线程或者主线程。

Activity:该类为apk程序的apk程序的最小的运行单元,换句话说就是主线程动态加载可执行代码的最小单元类,一个apk程序中可以包含多个Activity对象,ActivityThread主类会根据用户操作选择动态加载哪个Activity对象。

PhoneWindow:该类继承自Window类,同时,PhoneWindow类内部包含了一个DecorView对象,DecorView的父类是FrameLayout,因此,PhoneWindow是内含一个View对象,并提供了一组通用窗口操作API.

Window:该类提供了一组通用的窗口操作api,这里的窗口仅仅是客户端程序层面上的,wms所管理的窗口并不是window类,而是一个View或者ViewGroup类,对于PhoneWindow类而言就是其内部包含的DecorView类。Window是一个abstract类型。

DecorView:该类是一个FrameLayout的子类,并且是PhoneWindow中的一个内部类。DecorView是对普通的FrameLayout进行了一定的装饰,比如添加一个通用的TileBar,并响应特定的按键消息等。

ViewRoot:wms管理客户端窗口时,需要通知客户端进行某种操作,这些都是通过IPC调用完成的,而在客户端窗口收到IPC调用后,都会把该调用转换为本地的一个异步调用,实现的方式是使用Handler,ViewRoot就是继承于Handler,其作用主要是把wms的IPC调用转换为本地的一个异步调用。ViewRoot这个类在android的UI结构中扮演的是一个中间者的角色,连接的是PhoneWindow跟WindowManagerService.WindowManagerService中我们知道它读取android系统里所有事件,键盘事件,轨迹球事件等等,它怎么分发到各个activity的呢?就是通过这个ViewRoot。

WindowManager类:客户端要申请创建一个窗口,而具体创建窗口的任务是由wms完成的,windowmanager类就像是一个部门经理,谁有什么需求就告诉它,由它和wms进行交互,客户端不能直接和wms进行交互。

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    写给自己
    ○ 种一棵树最好的时间是十年前,其次是现在

    ○ 坚持输出,坚持书写,才可以持续成长

    ○ 所有美好事物的成长都是缓慢的

    ○ 既往不恋,未来不迎,当下不杂

    ○ 业精于勤,荒于嬉,行成于思,毁于随

    ○将军赶路 不追小兔

    ○不要拘泥于语言,同样也不要拘泥于行业,眼光放远一点

    ○ 如果某件事你做的不够好,不必介怀,因为以后的每一次每一天你都会做得越来越好

    ○ 此心不于事上磨,更于何处磨此心

    ○ 保持热情,保持求知欲

    ○ 千里之行,始于足下

    ○ 最怕你一生碌碌无为,还安慰自己平凡可贵。

    ○ 对于任何事,要保持自觉积极主动探索尝试。但是如果自己不积极认真地生活,不管得到什么样的回答都没有用。——解忧杂货店
    个人资料
    • 访问:581299次
    • 积分:8233
    • 等级:
    • 排名:第2523名
    • 原创:354篇
    • 转载:61篇
    • 译文:0篇
    • 评论:135条
    个人简介