1. 程式人生 > >Android的檢視繪製與事件分發流程(底層)

Android的檢視繪製與事件分發流程(底層)

    本文我們來簡單的分析下Activity的SetContentView方法底層是如何對我們的layout.xml檔案進行處理,然後分析一下事件是如何從WindowManagerService中傳遞到View的dispatchTouchEvent方法中的,最後會簡單瞭解下PhoneWindow、DecorWindow、WindowManager等概念。 Activity中建立View的過程大體如下
  1. 呼叫對應Activity的呼叫了onCreate方法
    • 隨後執行setContentView;其實就是呼叫的PhoneWindow.setContentView方法
    • 建立DecorView、View或ViewGroup物件
  2. 呼叫了onResume方法,裡面有呼叫Activity的makeVisible方法,該方法內部
    • 獲取一個WindowManager,並呼叫其addView方法,將檢視交給WindowManagerService進行管理,後者負責顯示檢視和傳遞使用者事件
      • 建立ViewRoot和W類
      • WinowManager呼叫WmS的遠端介面完成新增一個視窗到螢幕
    • 呼叫前面view的setVisibility方法;該方法最終會跳轉到ViewRoot的performTravels方法去;參考筆記《View—重繪》
setContentView方法內容如下
[email protected]
public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
        initWindowDecorActionBar(); //對ActionBar進行一些初始化
}
private Window mWindow = new PhoneWindow(this);

如果要分析繪製的底層細節從下面的幾個方法開始。     3、ViewRoot的建立、顯示、事件分發

檢視繪製

PhoneWindow.class (com.android.internal.policy.impl.PhoneWindow)

public class PhoneWindow extends Window 
private DecorView mDecor;  //DecorView是PhoneWindow中的內部類private final class DecorView extends FrameLayout
private ViewGroup mContentParent;
private LayoutInflater mLayoutInflater;


public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
}

setContentView(int )@PhoneWindow.class
@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();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent); //note1
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
}
1、解析xml檔案同時制定parent為mContentParent setContentView(View )@PhoneWindow.class
@Override
public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@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();  //note1
        } 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); //noe1
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
}
1、建立DecorWindow 和 ContentParent 2、contentView新增到mContentParent中
installDecor()@PhoneWindow.class
private void installDecor() {
        if (mDecor == null) { //建立DecorWindow
            mDecor = generateDecor();  
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) { //如果mContentParent不為空則直接返回該方法下面程式碼就不會被執行
            mContentParent = generateLayout(mDecor); //獲取DecorWindow下的使用者自定義檢視佈局應該所屬的parent ViewGroup
            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();
            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);
            .........
        }
}

generateDecor()@PhoneWindow.class
protected DecorView generateDecor() {
   return new DecorView(getContext(), -1);
}

generateLayout()@PhoneWindow.class
protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        ....//這裡會判斷DecorWindow是全屏顯示還是WrapContent
        int layoutResource;
        int features = getLocalFeatures();
        ....//根據feature採用不同的佈局
        View in = mLayoutInflater.inflate(layoutResource, null);  //這裡載入的是整個手機螢幕即將顯示的View,包括狀態列、ActionBar、使用者自定義View等
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); //note1
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
        ......
        return contentParent;
}
1、 等價於ViewGroup contentParent = (ViewGroup)decor.findViewById(ID_ANDROID_CONTENT);public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content。等價於mDecor.findViewById(com.android.internal.R.id.content)。獲取的是使用者自定義View的佈局檔案所屬的父佈局。 getDecorView()@PhoneWindow.class
@Override
public final View getDecorView() {
        if (mDecor == null) {
            installDecor();
        }
        return mDecor;
}

WindowManager.class

WindowManager常用的自有三個方法addView、updateView、removeView。 WindowManager的addView方法底層
  1. 就是根據View建立ViewRoot,ViewRoot內部包含一個W類。
  2. W是一個實現了IWindow.Stub介面的ViewRoot的內部類,作用就是呼叫ViewRoot的同名方法,WMS與ViewRoot之間的通訊明顯就是通過W;
  3. 而ViewRoot向WMS之間的通訊則是通過IWindowSession sWindowSession = IWindowManager.Stub.asInterface( ServiceManager.getService("window")).openSession(imm.getClient(), imm.getInputContext()); 
  4. 不管W還是IWindowSession,他們底層都是通過Binder通訊機制實現的。
View是Android檢視的呈現方式,但是View不能單獨存在,它必須依附於Window這個抽象的概念上。一個PhoneWindow對應一個View(decorView),;一個view對應一個ViewRoot和一個WindowManager.LayoutParam。ViewRoot用於對view進行檢視繪製的控制和事件的處理,WindowManager.LayoutParams用於告訴WMS當前Window在螢幕什麼位置進行顯示,以何種方式顯示!其中WindowManager.LayoutParams的flags和type引數比較重要,下面我們就對其進行解釋說明。 WindowManager.LayoutParams.class
WindowManager.LayoutParams extends ViewGroup.LayoutParams  implements Parcelable{
public int x;
//視窗的X座標,但是並不一定是絕對座標;如果對應的gravity是LEFT、RIGHT等則表示x偏移值
public int y;
//視窗的Y座標,但是並不一定是絕對座標;如果對應的gravity是BOTTOM、TOP等則表示y偏移值
public int flags;
public int type;
.....
}
Flags引數表示Window的屬性,通過修改Flags引數的值控制Window的顯示特性,比如下面幾個常用的選項
  • FLAG_NOT_FOCUSABLE:表明該Window不接收輸入事件,此標記會同時啟動FLAG_NOT_TOUCH_MODAL。輸入事件傳遞給下層的具有焦點的Window
  • FLAG_NOT_TOUCH_MODAL:當前Window區域以外的單擊事件傳遞給底層的Window,當前Window區域內的單擊事件則自己處理。一般都需要開啟這個標記,否則下面的Window將無法獲取到事件。
  • FLAG_SHOW_WHEN_LOCKED:讓該Window可以顯示在鎖屏的介面上面
Type引數表示Window的型別,Window有三種類型:
  • 應用Window:即我們普通的Activity對應的View
  • 子Window:需要依賴於一個父Window中,不能單獨存在,如dialog
  • 系統Window:需要宣告許可權才能建立的Window,比如Toast和系統狀態列
Window是分層的,對應的z-ordered越大越靠前顯示,層級大的會覆蓋層級小的Window。  具體如下 
//應用Window 1~99
FIRST_APPLICATION_WINDOW = 1;
TYPE_BASE_APPLICATION   = 1;
TYPE_APPLICATION        = 2;
TYPE_APPLICATION_STARTING = 3;
LAST_APPLICATION_WINDOW = 99;

//子Window 1000~1999
FIRST_SUB_WINDOW = 1000;
TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
LAST_SUB_WINDOW = 1999;

//系統Window 2000~2999
FIRST_SYSTEM_WINDOW     = 2000;
TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;  //對應AndroidManifest要宣告<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;
TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16;
TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22;
TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
TYPE_KEYGUARD_SCRIM           = FIRST_SYSTEM_WINDOW+29;
TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33;
LAST_SYSTEM_WINDOW      = 2999;

事件分發

WindowManager的addView方法底層就是建立ViewRoot和W類。
1、W是一個實現了IWindow.Stub介面的ViewRoot的內部類,作用就是呼叫ViewRoot的同名方法,WMS與ViewRoot之間的通訊明顯就是通過W; 2、而ViewRoot向WMS之間的通訊則是通過IWindowSession sWindowSession = IWindowManager.Stub.asInterface( ServiceManager.getService("window")).openSession(imm.getClient(), imm.getInputContext());  3、不管W還是IWindowSession,他們底層都是通過Binder通訊機制實現的。
addView的引數是mDecorWindow,所以ViewRoot的建立是基於這個mDecorView,對應ViewRoot的mView域(ViewRoot只有一個子View)。因此mDecorWindow是第一個接收到使用者點選事件的View。下面我們對整個流程進行一下分析。 首先從ViewRoot的handlerMessage方法看起。 handlerMessage()@ViewRoot.class
public void  handleMessage(Message msg) {
    case DISPATCH_POINTER: {
           MotionEvent event = (MotionEvent) msg.obj;
            try {
                deliverPointerEvent(event);
           } finally {
              event.recycle();
                if (msg.arg1 != 0) {
                  finishInputEvent();
               }
               if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");
           }
       } break;
}
DISPATCH_POINTER事件對應使用者的點選事件。呼叫ViewRoot的deliverPointerEvent方法。 deliverPointerEvent()@ViewRoot.class
private void deliverPointerEvent(MotionEvent event){
    ....
    handled = mView.dispatchTouchEvent(event);
    ....
}
ViewRoot的deliverPointerEvent方法會呼叫mView的dispatchTouchEvent方法 dispatchTouchEvent()@DecorWindow.class
public boolean dispatchTouchEvent(MotionEvent ev) {
            final Callback cb = getCallback(); //note1
            return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) //note2
                    : super.dispatchTouchEvent(ev);
}
1、呼叫PhoneWindow的getCallback方法獲取Activity在Attach方法中傳入的Callback物件,Activity實現了這個介面
2、cb.dispatchTouchEvent(ev) 就是呼叫Activity的dispatchTouchEvent方法
@Window.class
public void setCallback(Callback callback) {
      mCallback = callback;
}
@Window.class
public final Callback More ...getCallback() {
        return mCallback;
}
dispatchTouchEvent()@Activity.Class
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) { //note1
            return true;
        }
        return onTouchEvent(ev); //note2
}
1、呼叫PhoneWindow的superDispatchTouchEvent()方法 2、呼叫自己的onTouchEvent方法 uperDispatchTouchEvent()@PhoneWindow.class
public boolean uperDispatchTouchEvent(MotionEvent event) {
      return mDecor.superDispatchTouchEvent(event);  
 }
//呼叫mDecorDispatchTouchEvent方法 uperDispatchTouchEvent()@DecorWindow.class
public boolean  superDispatchTouchEvent(MotionEvent event) {
         return super.dispatchTouchEvent(event);  
 }
//呼叫父類FrameLayout的dispatchTouchEvent方法 通過上面的簡單分析可以知道如下結論:首先ViewRoot接收到了來自WMS的事件(通過W傳送,W轉換事件為非同步事件),隨後ViewRoot在的Handler中處理事件。對於使用者的觸屏資訊等事件,往往大多數是交給其直接子view,即DecorWindow物件。以DecorWindow的dispatchTouchEvent方法為例,該方法內部會呼叫getCallback的dispatchTouchEvent方法,即對應DecorWindow所繫結的Activity的dispatchTouchEvent方法。Activity的dispatchTouchEvent方法內部先呼叫PhoneWindow的superDispatchTouchEvent方法(方法內部呼叫DecorWindow的superDispatchTouchEvent方法,進而呼叫DecorWindow的父類FrameLayout的dispatchTouchEvent方法,往下就是一個普通的View的dispatchTouchEvent事件分發過程),如果事件沒有被DecorWindow下面的View處理則最後呼叫自身的onTouchEvent方法。

相關推薦

Android檢視繪製事件分發流程(底層)

    本文我們來簡單的分析下Activity的SetContentView方法底層是如何對我們的layout.xml檔案進行處理,然後分析一下事件是如何從WindowManagerService中傳遞到View的dispatchTouchEvent方法中的,最後會

Android之Input子系統事件分發流程

Android建立視窗機制,請看如下轉載: 一、Android4.2系統服務側——與View關係 1.服務端channel註冊過程 frameworks/base/core/java/android/view/ViewRootImpl.java public void s

SimpleTouch:以稍微(zui)簡單的方式解決Android事件分發流程

該庫已經開源到github,地址github.com/AlexMahao/S… 目標 一個用於監聽android事件分發流程的庫,兩行程式碼即可在執行時期監聽事件的分發流程。 在編寫一些複雜的佈局時,常常由於事件分發到底是哪個view處理產生困擾,做法通常需要經過以

Android檢視繪製流程完全解析,帶你一步步深入瞭解View(二)

在上一篇文章中,我帶著大家一起剖析了一下LayoutInflater的工作原理,可以算是對View進行深入瞭解的第一步吧。那麼本篇文章中,我們將繼續對View進行深入探究,看一看它的繪製流程到底是什麼樣的。如果你還沒有看過我的上一篇文章,可以先去閱讀 Android Layo

Android原始碼解析(三十)-->觸控事件分發流程

前面一篇文章中我們分析了App返回按鍵的分發流程,從Native層到ViewRootImpl層到DocorView層到Activity層,以及在Activity中的dispatchKeyEvent方法中分發事件,最終呼叫了Activity的finish方法,

觸控事件/事件分發流程/滑動衝突處理

目錄          1.觸控事件MotionEvent: 1)主要對應3操作 2)MotionEvent內部主要方法:          2.事件傳遞 1)預設情況demo:

Android熱插拔事件處理流程--Vold

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Nginx 多程序連線請求/事件分發流程分析

https://www.cnblogs.com/NerdWill/p/4992345.html Nginx使用多程序的方法進行任務處理,每個worker程序只有一個執行緒,單執行緒迴圈處理全部監聽的事件。本文重點分析一下多程序間的負載均衡問題以及Nginx多程序事件處理流程,方便大家自己寫程式的

帶你從原始碼角度分析ViewGroup中事件分發流程

序言 這篇博文不是對事件分發機制全面的介紹,只是從原始碼的角度分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件在ViewGroup中的分發邏輯,瞭解各個事件在ViewGroup的分發邏輯對理解、解決滑動衝突問題很有幫助。 ViewGroup中事件分發流

Android檢視狀態及重繪流程分析,帶你一步步深入瞭解View(三)

在前面一篇文章中,我帶著大家一起從原始碼的層面上分析了檢視的繪製流程,瞭解了檢視繪製流程中onMeasure、onLayout、onDraw這三個最重要步驟的工作原理,那麼今天我們將繼續對View進行深入探究,學習一下檢視狀態以及重繪方面的知識。如果你還沒有看過我前面一篇文章

Android面試準備:事件分發機制

View的事件分發機制舉例 為按鈕設定onClick點選事件和onTouch觸控事件的執行順序為: 1、onClick事件: button.setOnClickListener(new OnClickListener() { @Over

Android開發中的事件分發和消費機制的一些理解

說明事件分發被用作解決事件衝突,還被用作自定義View事件包含的動作 1.ACTION_DOWN(按下):手指只要一觸控式螢幕幕就立即觸發這個動作 2.ACTION_MOVE(移動):手指觸控式螢幕幕

Android Activity的按鍵事件處理流程

       Android裡,Activity按鍵事件相關的分發/處理函式有如下幾個:        1) public boolean dispatchKeyEvent(KeyEvent event);       2)public boolean onKeyDown

非同步通訊事件分發框架

本文的潛在讀者是五年以下經驗的程式設計師,五年以上的資深工程師請繞道。 如果讀者有需求,本文回覆超過十個類似需求,我會整理一套開源的開發框架,含python/c++的socket伺服器端/客戶端程式碼,Java/oc的socket客戶端外加事件分發以及sample。這樣的框

Android軟解碼硬解碼流程概述

最近整理了下Android端專案裡硬解碼與軟解碼的大致流程,做個筆記,方便以後review。如有錯誤,歡迎指正。硬解碼:1.建立MediaCodec:如 MediaCodec.createDecoderByType(keyMime);2.configure codec:配置format,surface等引數3

Android --- 檢視繪製監聽

// 從檢視上獲取檢視樹觀察者 ViewTreeObserver vo = mainView.getViewTreeObserver(); // 對檢視監聽即將繪製 vo.addOnPreDrawListener(new ViewTreeObserver.OnP

Android中ViewGroup的事件分發結論記錄

開發十年,就只剩下這套架構體系了! >>>   

Android事件分發(dispatchTouchEvent),攔截(onInterceptTouchEvent)處理(onTouchEvent)

在Android中,View的結構是樹狀的,所以,當觸發觸控事件的時候,其事件傳遞也是從上之下一層層的傳遞。下面我們結合例子來一點點進行分析。 首先,我們需要了解事件處理中的幾個方法: 1、在ViewGroup中,事件分為dispatchTouchEvent(事件的分發)

android面試題之觸控事件分發處理簡述

android觸控事件分發與處理     android的分發機制:由父控制元件判斷是否攔截,如果不攔截事件,則繼續分發到子控制元件,然後一直分發下去。     處理:與分發相反,由子控制元件先處理事件,如果子控制元件不處理,則交給父控制元件處理,一直向上傳遞,直到那個控制

Android事件分發底層原理

1.簡介 1.1事件構成 在Android中,事件(TouchEvent)主要包括點按、長按、拖拽、滑動等,所有的事件都由如下三個部分組成 按下(ACTION_DOWN) 移動(ACTION_MOVE) 擡起(ACTION_UP) 一般來說,一次