1. 程式人生 > >Android View的繪製機制前世今生---前世

Android View的繪製機制前世今生---前世

就像上個文章說的,觸控事件的傳遞機制是從外層到內層的過程。
我們想來看看這個頁面裡面的層級關係:

以下我們就用what-how-why三部曲的方式來分析View的繪製過程。
由於篇幅很大,所以分幾篇來解析這個過程。
這篇主要是自定義view/viewgroup,以及從Activity到DecorView的載入過程。

1.what:怎麼自定義一個View

1.1自定義View

自定義View的話,常見過程如下:

/**
 *   @author     DemanMath
 *   @date       2020-02-16
 *
 */
class CustomView : View {

    constructor(context: Context):super(context)
    constructor(context: Context,attributeSet: AttributeSet):super(context,attributeSet)
    constructor(context: Context,attributeSet: AttributeSet,def:Int):super(context,attributeSet,def)

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        AppLog.i()
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        AppLog.i()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        AppLog.i()
    }

}

三個構造方法+三個可以複寫的方法。
我們先看下這3個方法的順序:

2020-02-16 13:50:28.212 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure:  [at (CustomView.kt:32)]
2020-02-16 13:50:28.222 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure:  [at (CustomView.kt:32)]
2020-02-16 13:50:28.253 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure:  [at (CustomView.kt:32)]
2020-02-16 13:50:28.255 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure:  [at (CustomView.kt:32)]
2020-02-16 13:50:28.259 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onLayout:  [at (CustomView.kt:27)]
2020-02-16 13:50:28.403 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onDraw:  [at (CustomView.kt:22)]

1.2自定義ViewGroup

上程式碼

package com.joyfulmath.androidarchitecture.view

import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import com.joyfulmath.androidarchitecture.base.AppLog
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin

/**
 *   @author     DemanMath
 *   @date       2020-02-16
 *
 */
class FerrisWheel:ViewGroup {

    var count = 12
    var a = 2*PI/count
    var startA = PI/2

    constructor(context: Context):super(context){
        initViews()
    }
    constructor(context: Context,attributeSet: AttributeSet):super(context,attributeSet){
        initViews()
    }
    constructor(context: Context,attributeSet: AttributeSet,def:Int):super(context,attributeSet,def){
        initViews()
    }

    private fun initViews() {
        for(i in 0 until count){
            this.addView(CustomView(context))
        }
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        var mViewWidth = measuredWidth
        var mViewHeight = measuredHeight
        AppLog.i("$mViewWidth,$mViewHeight")
        var cx = mViewWidth/2
        var cy = mViewHeight/2
        var r  = min(measuredWidth,measuredHeight)*0.5f -20
        AppLog.i("r:$r,cx:$cx")
        for(i in 0 until count){
            var view = getChildAt(i)
            var width = view.measuredWidth
            var height = view.measuredHeight
            var cx1 = r* sin(startA+a*i)
            var cy1 = -r* cos(startA+a*i)
            AppLog.i("width:$width,height:$height")
            AppLog.i("cx1:$cx1,cy1:$cy1")
            view.layout(cx+(cx1-width/2).toInt(),
                cy+(cy1-height/2).toInt(),
                cx+(cx1+width/2).toInt(),
                cy+(cy1+height/2).toInt())
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        measureChildren(widthMeasureSpec,heightMeasureSpec)
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
}

效果如下:

這裡override了layout方法。可見View的繪製跟他的父View只有一個關係,ViewGroup指定了子View的位置。
關於View/ViewGroup繪製的機制,在下一節討論。

2.How:View的繪製機制是什麼

從上一節看出:整個繪製流程三個過程,measure,layout,draw這三個過程。
下面我們從原始碼的角度來分析下是不是這個過程。

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        .......

        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
            final Activity a = r.activity;

            if (localLOGV) Slog.v(
                TAG, "Resume " + r + " started activity: " +
                a.mStartedActivity + ", hideForNow: " + r.hideForNow
                + ", finished: " + a.mFinished);

            final int forwardBit = isForward ?
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

            // 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.
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManager.getService().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            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 (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    } else {
                        // The activity will get a callback for this {@link LayoutParams} change
                        // earlier. However, at that time the decor will not be set (this is set
                        // in this method), so no action will be taken. This call ensures the
                        // callback occurs with the decor set.
                        a.onWindowAttributesChanged(l);
                    }
                }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(
                    TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }

            // Get rid of anything left hanging around.
            cleanUpPendingRemoveWindows(r, false /* force */);

            // 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.newConfig != null) {
                    performConfigurationChangedForActivity(r, r.newConfig);
                    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
                            + r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig);
                    r.newConfig = null;
                }
                if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
                        + isForward);
                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);
                    }
                }

                r.activity.mVisibleFromServer = true;
                mNumVisibleActivities++;
                if (r.activity.mVisibleFromClient) {
                    r.activity.makeVisible();
                }
            }

            if (!r.onlyLocalRequest) {
                r.nextIdle = mNewActivities;
                mNewActivities = r;
                if (localLOGV) Slog.v(
                    TAG, "Scheduling idle handler for " + r);
                Looper.myQueue().addIdleHandler(new Idler());
            }
            r.onlyLocalRequest = false;

            // Tell the activity manager we have resumed.
            if (reallyResume) {
                try {
                    ActivityManager.getService().activityResumed(token);
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            }

        } else {
            // If an exception was thrown when trying to resume, then
            // just end this activity.
            try {
                ActivityManager.getService()
                    .finishActivity(token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

2.1 關鍵是頁面繪製流程

整個的過程就是一開始講的層級關係。
第一點:performResumeActivity 比wm.addView(decor, l)先執行。所以Activity是先獲取焦點,才繪製view。
performResumeActivity->r.activity.performResume()->mInstrumentation.callActivityOnResume(this)->activity.onResume()
在performResume最後可以看到onPostResume

final void performResume() {
        performRestart();
        ...
        // mResumed is set by the instrumentation
        mInstrumentation.callActivityOnResume(this);
      ...
        onPostResume();
       ...
    }
    
    protected void onPostResume() {
        final Window win = getWindow();
        if (win != null) win.makeActive();
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
        mCalled = true;
    }

window出現了,這個就是phonewindow。
下面我們去看docorview的過程。

//2020.02.18 phonewindow在這裡獲取
if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                //2020.02.18 docorview在這裡獲取
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
               ...
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    } else {
                        // The activity will get a callback for this {@link LayoutParams} change
                        // earlier. However, at that time the decor will not be set (this is set
                        // in this method), so no action will be taken. This call ensures the
                        // callback occurs with the decor set.
                        a.onWindowAttributesChanged(l);
                    }
                }

我們來看下wm.addView(decor, l);這個的過程。wm的實現就是WindowManagerImpl

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

mGlobal是WindowManagerGlobal, addview的核心程式碼如下

    root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            
            root.setView(view, wparams, panelParentView);
    

關於從ViewGroup開始的繪製流程,請看下篇。

更多內容:demanmath
公共號:

相關推薦

Android View繪製機制前世今生---前世

就像上個文章說的,觸控事件的傳遞機制是從外層到內層的過程。 我們想來看看這個頁面裡面的層級關係: 以下我們就用what-how-why三部曲的方式來分析View的繪製過程。 由於篇幅很大,所以分幾篇來解析這個過程。 這篇主要是自定義view/viewgroup,以及從Activity到DecorView的

Android View繪製原理:繪製流程排程、測算等

本文主要關注View的測量、佈局、繪製三個步驟,討論這三個步驟的執行流程。本文暫不涉及View和Window之間的互動以及Window的管理。再論述完這三個步驟之後,文末以自定義TagGroup為例,講述如何自定義ViewGroup。 View 樹的繪圖流程 View樹的繪圖流

Android View繪製原理解析

概述 本篇文章主要講述View是如何在Android原始碼中產生的,以便於我們能夠更好的去自定義一些控制元件,大體上是按照View繪製的流程來走步驟,在追蹤原始碼之前我們先了解幾個基礎知識。來看下面的這張圖: 一張典型的系統View分解圖,一個Activity

View繪製機制和LayoutInflater動態載入以及三種繪圖介面更新區別

View繪製流程及機制 流程研究 場景:最外層自定義MaxViewGroup繼承自LinearLayout+內層自定義ViewGroup繼承自LinearLayout+自定義View 注:1.LinearLayout的onMearsure過程為兩遍,每次呼叫Vi

Android View繪製流程

或許你已經看到過很多部落格總結的View的繪製流程的.我這篇部落格是跟著原始碼去看,對自己學到的知識加以印證.水平有限,僅供參考,如有錯誤,歡迎指正 我在之前的部落格就已經說明了Activity的啟動到顯示的相關流程,現在我們來看下View具體的工作流程. 上次

Android View繪製流程原始碼淺析

Android中View的繪製是一個面試的必答題,網上他人的博文也很多,本文旨在分析出大致流程。 廢話不說,read the fucking source code! 先從ActivityThread主執行緒啟動Activity說起,當Activity初始化 Window和

Android View重新整理機制

在Android的佈局體系中,父View負責重新整理、佈局顯示子View;而當子View需要重新整理時,則是通知父View來完成。這種處理邏輯在View的程式碼中明確的表現出來: void invalidate(boolean invalidateCache) {

Android View 繪製流程(Draw) 完全解析

前言 前幾篇文章,筆者分別講述了DecorView,measure,layout流程等,接下來將詳細分析三大工作流程的最後一個流程——繪製流程。測量流程決定了View的大小,佈局流程決定了View的位置,那麼繪製流程將決定View的樣子,一個View該顯示什麼

轉載一篇詳細的分析:Android View繪製和顯示原理簡介

圖片沒有貼上過來,還是去原連結看吧。 現在越來越多的應用開始重視流暢度方面的測試,瞭解Android應用程式是如何在螢幕上顯示的則是基礎中的基礎,就讓我們一起看看小小螢幕中大大的學問。這也是下篇文章——《Android應用流暢度測試分析》的基礎。 首

android View繪製原始碼分析

在開發過程中我們經常要進行view的自定義。如果熟練掌握自定義技巧的話就能做出很多控制元件出來。這篇部落格來講講view繪製背後發生的那些事。 一, view的基礎知識 view的繪製概括 首先先說說view繪製的整體過程。 View繪製的原始碼分析

Android View繪製和顯示原理簡介

現在越來越多的應用開始重視流暢度方面的測試,瞭解Android應用程式是如何在螢幕上顯示的則是基礎中的基礎,就讓我們一起看看小小螢幕中大大的學問。這也是我下篇文章——《Android應用流暢度測試分析》的基礎。     首先,用一句話來概括一下Android應用程式顯

Android View 繪製流程 與invalidate 和postInvalidate 分析--從原始碼角度

整個View樹的繪製流程是在ViewRootImpl.java類的performTraversals()函式展開的,該函式做的執行過程可簡單概況為  根據之前設置的狀態,判斷是否需要重新計算檢視大小(measure)、是否重新需要佈局檢視的位置(layout

Android View繪製流程看這篇就夠了

前言        自定義View、多執行緒、網路,被認為是Android開發者必須牢固掌握的最基礎的三大基本功。Android View的繪製流程原理又是學好自定義View的理論基礎,所以掌握好View的繪製原理是Android開發進階中無法繞過的一道坎。而關乎到原理

Android OpenGL ES 入門系列(一) --- 了解OpenGL ES的前世今生

target 初始化 vertex 單獨 http hang tex 變化 3d圖 轉載請註明出處 本文出自Hansion的博客 OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,

Android 性能篇 -- 帶你領略Android內存泄漏的前世今生

http 外部類 sched content 技術 oncreate 保持 roi 作用域 基礎了解 什麽是內存泄漏? 內存泄漏是當程序不再使用到的內存時,釋放內存失敗而產生了無用的內存消耗。內存泄漏並不是指物理上的內存消失,這裏的內存泄漏是指由程序分配的內存但是由於程序邏

MySQL · 引擎特性 · B+樹併發控制機制前世今生

前言 B+樹是1970年Rudolf Bayer教授在《Organization and Maintenance of Large Ordered Indices》一文中提出的[1]。它採用多叉樹結構,降低了索引結構的深度,避免傳統二叉樹結構中絕大部分的隨機訪問操作,從而有效減少了磁碟磁頭的尋道

Android和iOS的前世今生

Android和iOS的區別(從開發角度比較) 一、Android、ios發展史: https://www.jianshu.com/p/3fbab95bbb60 https://www.jianshu.com/p/aa3758739145 二、Android和iOS的區別(從

Android 官方架構元件 ViewModel:從前世今生到追本溯源

爭取打造 Android Jetpack 講解的最好的部落格系列: Android官方架構元件Lifecycle:生命週期元件詳解&原理分析 Android官方架構元件ViewModel:從前世今生到追本溯源 Android官方架構元件Paging:分頁庫的設計美學

Android 效能篇 -- 帶你領略Android記憶體洩漏的前世今生

基礎瞭解 什麼是記憶體洩漏? 記憶體洩漏是當程式不再使用到的記憶體時,釋放記憶體失敗而產生了無用的記憶體消耗。記憶體洩漏並不是指物理上的記憶體消失,這裡的記憶體洩漏是指由程式分配的記憶體但是由於程式邏輯錯誤而導致程式失去了對該記憶體的控制,使得記憶體浪費。 Java

Android應用架構前世今生

作者:黃俊彬(http://huangjunbin.com/) 責編:錢曙光([email protected]) 作者授權CSDN釋出,如需轉載請聯絡作者本人。 前言 Android的開發生態系統發展迅速,在開發Andro