Window和WindowManager(一)
前言
什麼是Window?其實Window是一個抽象的概念,在日常開發中,可能很少遇到,但是有時候我們需要實現一些懸浮窗的東西,這種效果就需要用到Window。我們先看下Window類:
/** * Abstract base class for a top-level window look and behavior policy.An * instance of this class should be used as the top-level view added to the * window manager. It provides standard UI policies such as a background, title * area, default key processing, etc. * * <p>The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */ public abstract class Window {
看Windwo的原始碼,我們知道,它是一個抽下類,從註釋上可以看到,Window的唯一實現類是PhonWindow。實現Window的具體實現是由PhoneWindow負責的。看下PhoneWindow類。
/** * Android-specific Window. * <p> * todo: need to pull the generic functionality out into a base class * in android.widget. * * @hide */ public class PhoneWindow extends Window implements MenuBuilder.Callback {
建立一個Window通過WindowManager完成,也就是說,WindowManager是外界訪問Window的入口。而Window的具體實現是在WindowManagerService中,是一個獨立的程序。WindowManager和WindowManagerService的通訊其實就是一個IPC過程。也就是一個跨程序通訊。Android上所有的檢視都是通過Window來呈現的,不管是Activity,Dialog,Toast她們的檢視都是附加在Window上。也就是說,Window是所有View的直接管理者。每一個View的呈現都是附加在Window上。
一、Window和WindowManager
在前言中我們瞭解到了Window其實就是所有檢視的直接管理者,所有檢視的呈現都是附加在Window上。打個比喻就是,Window就是我們實際生活中的窗戶玻璃,具體的View的呈現就是我們在玻璃上描繪的各種圖案。接著我們知道通過WindowManager可以訪問Window,他們倆執行在不同的程序中,通過 IPC機制進行通訊。
下面我們來簡單的介紹如何新增一個Window。
package com.mujin.keji.myapplication; import android.content.Context; import android.graphics.Color; import android.graphics.PixelFormat; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.Gravity; import android.view.View; import android.view.WindowManager; import android.widget.Button; import android.widget.ImageView; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private ImageView ivEtxt; private Button button; privateWindowManager.LayoutParams params; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); ivEtxt = (ImageView) findViewById(R.id.iv_etxt); ivEtxt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { button = new Button(MainActivity.this); button.setText("這是一個按鈕"); button.setTextColor(Color.parseColor("#FF0000")); params= new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,0,0, PixelFormat.TRANSPARENT); params.gravity = Gravity.TOP|Gravity.LEFT; params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; params.x = 100; params.y = 300; windowManager.addView(button,params); } }); } }
新增系統許可權:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
從上面的例子我們把一個Button新增到螢幕的座標(100,300)的位置上。我們發現新增Window其實就是新增一個View。Window的概念是抽象的,我們把對Window的操作直接當成對View的操作即可。一個空的Window是沒有意義的。在上面初始化WindowManager.LayoutParams有兩個重要的引數:Flags和Type。
Flags:表示Window屬性,主要用來控制Window的顯示特性。接下來介紹幾個常用的特性。
FLAG_NOT_FOCUSABLE:
表示Windwo沒有焦點,不接受輸入事件。
FLAG_NOT_TOUCH_MODAL:
表示當前Window區域的事件,自己處理。不在自己的區域的事件,交給底層Window。
FLAG_SHOW_WHEN_LOCKED:
這個屬性,表示Window可以顯示在鎖屏的介面上。
Type:表示Window的型別,有三種類型,分別是應用Window,子Window和系統Window。應用Window對應著一個Activity,子Window不能單獨存在,需要附屬在特定的父Window中,比如常見的Dialog。系統Window的建立,需要許可權。
在WindowManager中所提供的功能很簡單,常用的方法有三個。addView,updateView,removeView。這三個方法定義在了ViewManager中,而WindowManager繼承了ViewManager。我們從上面的三個方法可以看到,WindowManager對Window的操作,實際就是對Window中的View進行操作。因為一個空的Window是沒有意義的,我們在這講的Window都是說Window中的View。
二、Window的內部機制
Window是一個抽象的概念,每一個Window都對應著一個View和一個ViewRootImpl。Window和View是通過ViewRootImpl來建立連線的。之所以說Window 是抽象的,是因為,WindowManager實際上對Window的操作,其實就是對Window中的View進行操作。可以說Window是一個虛擬的不存在的東西。接下來我們分析Window的具體新增過程:
(一)Window的新增過程
Window的新增,實際就是WindowManager通過addView新增一個View。我們來看下Window的原始碼。
@SystemService(Context.WINDOW_SERVICE) public interface WindowManager extends ViewManager {
我們看到WindowManager是一個介面,而它的實現類是WindowManagerImpl
* @see WindowManager * @see WindowManagerGlobal * @hide */ public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); private final Context mContext; private final Window mParentWindow;
我們來看下三個核心的方向addView removeView updateView
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } @Override public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.updateViewLayout(view, params); } @Override public void removeView(View view) { mGlobal.removeView(view, false); }
我們看到這三個方法的具體實現都是通過WindowManagerGlobal mGlobal 物件呼叫,mGlobal 在第一行程式碼中進行初始化
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
所以我們知道WindowManager對Window的操作,實際上又交給了WindowManagerGlobal這個東西去處理。接著我們看下WindowManagerGlobal對Window的新增過程的實現。
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (display == null) { throw new IllegalArgumentException("display must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { // If there's no parent, then hardware acceleration for this view is // set from the application's hardware acceleration setting. final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. if (mSystemPropertyUpdater == null) { mSystemPropertyUpdater = new Runnable() { @Override public void run() { synchronized (mLock) { for (int i = mRoots.size() - 1; i >= 0; --i) { mRoots.get(i).loadSystemProperties(); } } } }; SystemProperties.addChangeCallback(mSystemPropertyUpdater); } int index = findViewLocked(view, false); if (index >= 0) { if (mDyingViews.contains(view)) { // Don't wait for MSG_DIE to make it's way through root's queue. mRoots.get(index).doDie(); } else { throw new IllegalStateException("View " + view + " has already been added to the window manager."); } // The previous removeView() had not completed executing. Now it has. } // If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { final int count = mViews.size(); for (int i = 0; i < count; i++) { if (mRoots.get(i).mWindow.asBinder() == wparams.token) { panelParentView = mViews.get(i); } } } 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; } } }
1.檢查引數的合法性
if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (display == null) { throw new IllegalArgumentException("display must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { // If there's no parent, then hardware acceleration for this view is // set from the application's hardware acceleration setting. final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } }
如果是子Window,那麼要調整一些佈局引數
if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); }
2.建立ViewRootImpl類,新增View
我們在上面也提到了每一個Window都會對應一個View,它們之間是通過ViewRootImpl來建立連線的。這裡涉及到幾個重要的列表。
private final ArrayList<View> mViews = new ArrayList<View>(); private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>(); private final ArraySet<View> mDyingViews = new ArraySet<View>();
ArrayList<View> mViews ,用來存放所有Window對應的View。
ArrayList<ViewRootImpl> mRoots ,用來儲存所有Window和View建立連線的ViewRootImpl。
ArrayList<WindowManager.LayoutParams> mParams,用來儲存所有View的佈局引數。
ArraySet<View> mDyingViews ,儲存著那些正在被刪除的View,或者說是WindowManager正在呼叫remoview,但是Window還沒被銷燬。
新增View過程。
ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. if (mSystemPropertyUpdater == null) { mSystemPropertyUpdater = new Runnable() { @Override public void run() { synchronized (mLock) { for (int i = mRoots.size() - 1; i >= 0; --i) { mRoots.get(i).loadSystemProperties(); } } } }; SystemProperties.addChangeCallback(mSystemPropertyUpdater); } int index = findViewLocked(view, false); if (index >= 0) { if (mDyingViews.contains(view)) { // Don't wait for MSG_DIE to make it's way through root's queue. mRoots.get(index).doDie(); } else { throw new IllegalStateException("View " + view + " has already been added to the window manager."); } // The previous removeView() had not completed executing. Now it has. } // If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { final int count = mViews.size(); for (int i = 0; i < count; i++) { if (mRoots.get(i).mWindow.asBinder() == wparams.token) { panelParentView = mViews.get(i); } } } root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams);
把要新增的View,ViewRootImpl,佈局引數mParams 新增到集合列表中。
3.新增到列表之後,更新介面顯示
通過ViewRootImpl呼叫setView進行介面的繪製。
// 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; }
我們知道,把View新增到Window之後,需要繪製,才能呈現到介面上。在setView方法中通過呼叫requestLayout()方法實現View的繪製,具體的繪製流程,先不介紹,後面的章節會介紹如何繪製View。
/* = WindowManagerImpl.ADD_OKAY; */ // 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();
接著把View繪製之後,繼續往下面看程式碼。
try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); }
我們看到核心程式碼。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel);
window的新增過程交給了mWindowSession物件呼叫addToDisplay方法進行新增。我們看下mWindowSeesion初始化的地方
public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { InputMethodManager imm = InputMethodManager.getInstance(); IWindowManager windowManager = getWindowManagerService(); sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { @Override public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); } }, imm.getClient(), imm.getInputContext()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return sWindowSession; } }
IWindowManager windowManager = getWindowManagerService(); sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { @Override public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); } }, imm.getClient(), imm.getInputContext());
而mWindowSeesion通過windowManager.openSession()而來。是一個Binder物件。所以在這裡涉及到了IPC機制,也就是把新增window的任務交給了WindowManagerService來完成。而WindowManagerService是一個獨立的程序。
小結:
我們不必深入程式碼的具體實現,在這裡我們大概知道新增window的流程其實就是
1.首先通過ViewRootImpl把view新增到window中,
2.然後通過ViewRootImpl類實現對View的繪製
3.最後通過IWindowSession mWindowSession;物件呼叫addToDisplay方法實現對window的新增。而mWindowSession是一個Binder物件,這裡涉及到IIPC機制,跨程序通訊。最終Window的新增其實交給了WindowManagerService完成。