说说View的绘制流程(一)

在我们的Android项目开发过程中,相信大家肯定会涉及到对View流程绘制的相关开发(比如:自定义控件)。本文试着从源码(Android 10,我们之所以选择API 10源码,是为了尽量减少一些主题无关的复杂性,便于理解和阅读)角度,,试着总结下关于View的绘制流程。

说起View的绘制过程,本文从Activity的创建过程开始到View被触发绘制流程。我们知道,当用户启动一个Activity时(比如通过startActivity调用),AMS(Activity Manager Service)首先判断该Activity所属应用程序进程时候已经在运行。如果是就向这个进程发送启动指定Activity的操作。Activity从启动到最终在屏幕上显示出来,分别要经历onCreate->onStart->onResume三个状态迁移。我们首先来分析分析启动Activity展示UI的onCreate执行过程。

我们知道当我们在ADT用模板生成Activity的时候,我们生成的onCreate模板方法如下:

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}
我们知道, Activity的自定义布局是在 setContentView来完成加载。我们来分析 setContentView的源码:
public void setContentView(View view) {
        getWindow().setContentView(view);
        initActionBar();
    }
  public Window getWindow() {
        return mWindow;
}
现在我们知道, Activity的布局的加载过程由 mWindow来完成,通过继续查看 Activity的源码,我们找到 attach方法完成对 mWindow初始化,我们看 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);
        mWindow = PolicyManager.makeNewWindow(this); //此处完成对mWindows初始化
        mWindow.setCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();
        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }   
我们看 PolicyManager.makeNewWindow(this)对应的源码如下:
 public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";
    private static final IPolicy sPolicy;
    static {
        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);
        }
    }
    private PolicyManager() {}

    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context); //该处返回我们Window子类
    }
}
我们查看 com.android.internal.policy.impl.Policy看到 Policy源码:
public class Policy implements IPolicy {
    private static final String TAG = "PhonePolicy";

    private static final String[] preload_classes = {
        "com.android.internal.policy.impl.PhoneLayoutInflater",
        "com.android.internal.policy.impl.PhoneWindow",
        "com.android.internal.policy.impl.PhoneWindow$1",
        "com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback",
        "com.android.internal.policy.impl.PhoneWindow$DecorView",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
    };
 ……
    public PhoneWindow makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }
}
到这里我们已经可以知道在我们 Acitivty成员变量 mWindow对应类型是 PhoneWindow。那我们就可以知道我们 PhoneWindow对应的 setContentView方法实现:
@Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) { //具体我们的mContentParent是什么,我们后面就知道了
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mContentParent.addView(view, params);
        final Callback cb = getCallback();
        if (cb != null) {
            cb.onContentChanged();
        }
    }
我们看 installDecor 实现:
private void installDecor() {
        if (mDecor == null) { //关于mDecor,我们这里需要提前介绍下,mDecor是我们Activity的真正的根元素
            mDecor = generateDecor();//完成对我们Activity的根元素的初始化
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor); //该方法完成对Activity的mWindow的属性设置(比如:设置Dialog风格、标题栏是否可见、是否全屏等)等
            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);
                }
            }
        }
}
我们现在肯定还疑惑 mDecormContentParent的关系吧,我们查看 mContentParent = generateLayout(mDecor)的代码我们马上就能知道。我们马上贴出重要代码实现如下:
 protected ViewGroup generateLayout(DecorView decor) {
      …… //此处省略的代码对Window属性的一些设置
        WindowManager.LayoutParams params = getAttributes();
        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;
            }
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {
            layoutResource = com.android.internal.R.layout.screen_progress;
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            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 (mIsFloating) {
                layoutResource = com.android.internal.R.layout.dialog_title;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title;
            }
        } else {
            layoutResource = com.android.internal.R.layout.screen_simple;
        }
		 //以上代码判断当前Acitvity的根布局选择的布局文件:后面我们会选取R.layout.screen_title布局来说明mmDecor和mContentParent的关系。
        mDecor.startChanging();
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
			//从该处代码我们可以看出,我们的Activity的根布局是添加到mDecor中。
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        //我们的contentParent是mDecor的根元素下查找的ID为com.android.internal.R.id.content的ViewGroup
        return contentParent;
    }
下面我们来看看Activity的根布局为R.layout.screen_title情况:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:fitsSystemWindows="true">  
    <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> 
从该布局我们很明白的能看出来,我们的mDecor为LinearLayout的父元素,我们的mContentParent为该布局下id为content的FrameLayout布局。我们本文分析到这里后我们可以用图很形象的来说明,我们的Activity里调用setContent都做了什么。

说了这么多,下面终于开始进入本文的正题,尝试说说View的绘制流程。

我们开始说View的绘制流程前,我们肯定很想知道我们的绘制工作从哪里开始?稍微对View自定义过程了解一些,大家应该知道我们的绘制从ViewRoot(高版本是ViewRootImp)的performTraversals方法开始。大家肯定也会好奇,View绘制控制流程是怎么样?谁来管理Acitvity的GUI绘制过程?下面我们引出我们GUI绘制流程的控制器ViewRoot(高版本是ViewRootImp)。我们在学习Android的过程中,我们的Android架构设计大量使用了MVC模式。个人觉得Android的GUI绘制管理也可以认为是MVC模式的应用。ViewRoot的认识,ViewRoot是GUI管理与GUI呈现系统之间的桥梁,我们看ViewRoot的定义,我们发现它并不是一个View类型,而是一个Handler。我们还知道ViewRoot在本地(此处本地理解为APP进程里)代理WMS,通过与WMS交互控制Window的管理和绘制。我们查看我们的源码知道, 静态类变量sWindowSession就是WMS在本地的代理,如源码如下:

public static IWindowSession getWindowSession(Looper mainLooper) {
        synchronized (mStaticInit) {
            if (!mInitialized) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
                    sWindowSession = IWindowManager.Stub.asInterface(
                            ServiceManager.getService("window"))
                            .openSession(imm.getClient(), imm.getInputContext());
                    mInitialized = true;
                } catch (RemoteException e) {
                }
            }
            return sWindowSession;
        }
    }
如果我们能弄明白我们的ViewRoot在什么时候完成初始化,我们就能弄明白需要ViewRoot的意义。这个问题我们又要回到Activity的生命周期,我们的Activity开始被展示可见只要在onResume过程来完成,也可以说View的流程绘制工作主要子啊onResume期间完成(说的不对,还望园友指出)。onResume调用从ActivityThread的handleResumeActivity方法开始,我们查看关键代码:
if (r.window == null && !a.mFinished && willBeVisible) { //此处我们的Activity还没完成可见
                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); //此处我们的WindowManager开始添加当前Activity的Window进管理队列
                }
            ……
             //此处的代码执行时机是Activity 再次进入onResume方法是否,比如,Acitvity从后台回到前台显示
            if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
                if (r.newConfig != null) {
                    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
                            + r.activityInfo.name + " with newConfig " + r.newConfig);
                    performConfigurationChanged(r.activity, r.newConfig);
                    r.newConfig = null;
                }
             
                WindowManager.LayoutParams l = r.window.getAttributes();
                if ((l.softInputMode
                        & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
                        != forwardBit) {
                    l.softInputMode = (l.softInputMode
                            & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
                            | forwardBit;
                    if (r.activity.mVisibleFromClient) {
                        ViewManager wm = a.getWindowManager();
                        View decor = r.window.getDecorView();
                        wm.updateViewLayout(decor, l);
                    }
                }
               ……
            }
           ……
        } 
我们后面的分析知道,我们的在调用 wm.addView(decor, l)这句源码的时候,开始对ViewRoot的初始化。
private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
    {
       ……
        ViewRoot root;
        View panelParentView = null;
        synchronized (this) {
            //此处查看Activity对应的mDecor的管理对象ViewRoot是否在mViews中
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (!nest) {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                root = mRoots[index];
                root.mAddNesting++;
                // Update layout parameters.
                view.setLayoutParams(wparams);
                root.setLayoutParams(wparams, true);
                return;
            }
            ……
            root = new ViewRoot(view.getContext());
            root.mAddNesting = 1;
            view.setLayoutParams(wparams);
            if (mViews == null) {
                index = 1;
                mViews = new View[1];
                mRoots = new ViewRoot[1];
                mParams = new WindowManager.LayoutParams[1];
            } else { //完成对新建的ViewRoot入队列
                index = mViews.length + 1;
                Object[] old = mViews;
                mViews = new View[index];
                System.arraycopy(old, 0, mViews, 0, index-1);
                old = mRoots;
                mRoots = new ViewRoot[index];
                System.arraycopy(old, 0, mRoots, 0, index-1);
                old = mParams;
                mParams = new WindowManager.LayoutParams[index];
                System.arraycopy(old, 0, mParams, 0, index-1);
            }
            index--;
            mViews[index] = view;
            mRoots[index] = root;
            mParams[index] = wparams;
        }
       root.setView(view, wparams, panelParentView);
    }

通过以上的代码分析过程,我们知道ViewRoot 和Activity的根元素(mDecor)一一对应。此时大有没有觉得WMS和AMS的管理方式很类似啊。

最后我们来简单说说我们的绘制流程触发时机:我们知道ViewRoot的基类是Handler类型,我们就很容易知道我们的消息绘制流程是基于消息队列来处理我们的绘制流程。我们的查看ViewRoot的handleMessage方法知道,让我们给消息队列发送DO_TRAVERSAL消息的时候会触发performTraversals来绘制消息流。我们继续搜索ViewRoot源码,我们看到scheduleTraversals方法封装了TRAVERSAL消息的发送:
public void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            sendEmptyMessage(DO_TRAVERSAL);
        }
}
我们找到调用scheduleTraversals方法的地方,注意到几处调用如:requestLayout、invalidateChild、requestChildFocus、draw和setLayoutParams。从调用的方法名我们大致也知道,大部分是在View的绘制过程后重新调用scheduleTraversals绘制(重绘或者绘制子控件)。我们注意到setLayoutParams方法,在我们的handleResumeActivity方法过程中,我们重新来看WindowManagerImpl方法的addView代码实现:
private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
    {
       ……
        ViewRoot root;
        View panelParentView = null;
        synchronized (this) {
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (!nest) {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                root = mRoots[index];
                root.mAddNesting++;
                view.setLayoutParams(wparams);//给Activity的mDecor重新设置,给绘制流程的消息队列发送消息DO_TRAVERSAL,开始绘制流程
                root.setLayoutParams(wparams, true);
                return;
            }
            ……
            root = new ViewRoot(view.getContext());
            root.mAddNesting = 1;
            view.setLayoutParams(wparams);
            if (mViews == null) {
                index = 1;
                mViews = new View[1];
                mRoots = new ViewRoot[1];
                mParams = new WindowManager.LayoutParams[1];
            } else { //完成对新建的ViewRoot入队列
                index = mViews.length + 1;
                Object[] old = mViews;
                mViews = new View[index];
                System.arraycopy(old, 0, mViews, 0, index-1);
                old = mRoots;
                mRoots = new ViewRoot[index];
                System.arraycopy(old, 0, mRoots, 0, index-1);
                old = mParams;
                mParams = new WindowManager.LayoutParams[index];
                System.arraycopy(old, 0, mParams, 0, index-1);
            }
            index--;
            mViews[index] = view;
            mRoots[index] = root;
            mParams[index] = wparams;
        }
       root.setView(view, wparams, panelParentView);
    }

我们分析该段代码我们注意到,view.setLayoutParams(wparams)调用了两次,一次是mDecor存在mView队列,一次是mDecor新加入mView队列。我们可以大胆的肯定,第一次是Activity从任务栈重新回到前台,第二次新开始Activity添加到任务栈顶端。相同点在,两次的调用都是在Activity的onResume方法期间。

说了这么多,还是没有开始View的绘制流程分析,本本只是为自然引出View的绘制流程铺垫。文章难免有错误,欢迎园友能指正。下篇开始View绘制流程的真正分析,感兴趣的同学欢迎一起探讨。

转载请注明出处:http://blog.csdn.net/johnnyz1234/article/details/46291041


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值