一 UI 绘制流程及原理(Android UI )

34 篇文章 0 订阅

在了解绘制原理之前,我们首先了解一下View视图结构

1. Android UI View视图结构

View是如何被添加到屏幕窗口上?

1.1 平常代码中使用如下方式添加布局xml到Activity或Fragment中

setContentView(R.layout.main);

1.2 接1.1场景下,接下来在Activity中会调用如下代码段

	/**
     *  getWindow()方法调用
     */
    public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

1.3 我们知道Window只有一个唯一子类实现:android.view.PhoneWindow
接下来我们查看PhoneWindow的源码:


	// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
	
	// This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;

	// 我只贴出了部分关键代码(我们本次浏览源码目的是弄清楚View如何加载到屏幕,所以只关注主流程)
	 @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor(); 
            ......
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            ......
        } else {
        	// 1.5 这个mContentParent就是我们调用installDecor方法后,初始化的基础布局
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
		......
    }
        	 
     // 创建顶层布局 mDecor
	 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 1. 如果顶层布局mDecor为空,则会创建一个DecorView(实际上就是继承FrameLayout的一个ViewGroup)
            mDecor = generateDecor(-1);
            ......
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
        	// 2. 顶层布局mDecor创建成功后,如果mContentParent为空,则会根据Activity主题不一样,
        	// 创建一个包含id为com.android.internal.R.id.content的基础布局Layout
            mContentParent = generateLayout(mDecor);
        }
        ......
     }

	 // 3. 通过该方法在创建好这个基础布局Layout后,会将其添加到我们前面创建的顶层布局mDecor当中 
	 protected ViewGroup generateLayout(DecorView decor) {
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
	}
     

1.4 DecorView部分关键代码

	// 4. 将1.3步创建的基础布局Layout添加到顶层布局mDecor当中
	void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ......
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            ......
        } else {
            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

1.5 经过1.4步后,顶层布局mDecor,基础布局都初始化完毕,我们在回过头看看PhoneWindowsetContentView方法

// 将我们传入的Layout添加到基础布局当中
mLayoutInflater.inflate(layoutResID, mContentParent);

综上几步简单分析,总结有如下几步加载布局到屏幕:

  1. 创建顶层布局DecorView
  2. 在顶层布局中加载加载基础布局ViewGroup
  3. 将我们设置的setContentView(R.id.layoutId)中R.id.layoutId布局添加到基础布局ViewGroup当中
    在这里插入图片描述

2. View绘制流程和原理

2.1 View绘制入口从ActivityThread.java开始

ActivityThread.java中
 
 	@Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, 
    								boolean isForward, String reason) {
    	 ......
    	 final Activity a = r.activity;
    	 // 1. 首先在Activity中获取我们根布局DecorView
    	 View decor = r.window.getDecorView();
    	 // 2. 通过Activity对象获取WindowManager对象
		 ViewManager wm = a.getWindowManager();
		 ......
		 // 然后通过ViewManager的实现类添加decor到手机屏幕上
		 wm.addView(decor, l);
    }

2.2 通过源码可知,系统是通过WindowManager来调用addView方法的,在Activity.java


	/** Retrieve the window manager for showing custom windows. */
    public WindowManager getWindowManager() {
        return mWindowManager;
    }

2.3 在Activity.java中我们发现,mWindowManager又是通过Window对象来获取的

private Window mWindow;
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, ActivityConfigCallback activityConfigCallback) {
        ......
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        ......
        // 3. 通过Window实例对象获取
        mWindowManager = mWindow.getWindowManager();
        ......
    }

2.4 我们知道Window又仅有PhoneWindow一个实现类

	
	/**
     * Return the window manager allowing this Window to display its own
     * windows.
     *
     * @return WindowManager The ViewManager.
     */
    public WindowManager getWindowManager() {
    	// 查看源码得知,该mWindowManager初始化,又是通过父类Window.java中 `setWindowManager`来初始化的
        return mWindowManager;
    }

2.5 在Window.java中,我们看到


    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        ......
        // mWindowManager最终是由WindowManagerImpl创建的
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

2.6 所以到此时,我们回归到第一步时的场景,addView方法是怎么添加DecorView到手机屏幕上的了

// WindowManagerImpl中addView方法

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        // 通过该方法添加View
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    

2.7 我们查看一下WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
     		......
     		ViewRootImpl root;
  			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) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
}

2.8 接下来看下ViewRootImpl.java

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ......
     			// 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();
    ......
    }

	@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

以上8步,其实就做了一件事情,就是将根布局通过ViewRootImpl.java添加到窗口上,接下来就是我们比较熟悉的View绘制3步曲了

2.9 我们在回头看下2.8步中requestLayout方法

	// 检查View绘制线程是否在主线程
	void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

3.0 scheduleTraversals方法


	final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

	final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

	 void scheduleTraversals() {
        if (!mTraversalScheduled) {
            ......
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ......
        }
    }

3.1 doTraversal 会调用 performTraversals();重点方法来了(View绘制流程3大步)

	
	void performTraversals(){
	......
	// Ask host how big it wants to be 测量
	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

	......
	// 布局
	performLayout(lp, mWidth, mHeight);
	......
	// 绘制
	 performDraw();
	}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

初心一点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值