前言
什么是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;
private WindowManager.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完成。
网友评论