注册

Android中Window 和 WindowManager

Window 是一个抽象类,具体实现是 PhoneWindow,通过 WindowManager 创建。

WindowManager是外界访问Window的入口,Window 的具体实现位于 WindowManagerService 中

WindowManager 和 WindowManagerService 的交互是一个 IPC 的过程

Andorid 中所有的视图都是通过 Window 来呈现的

不管是 Activity 、Dialog 还是 Taost

因此, Window 实际是 View 的直接管理者 !!!

1. Window 和 WindowManager

WindowManager 添加 Window 的过程

将一个 Button 添加到屏幕坐标为(100,,300)的位置上。其中,Flags 和 type 这两个参数比较重要。

mFloatingButton = new Button(this);
mFloatingButton.setText("button");
mLayoutParams = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0 ,0 , PixelFormat.TRANSPARENT);
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
   | LayoutParams.FLAG_NOT_FOCUSABLE
   | LayoutParams.FLAG_SHOW_WHEN_LOCKED
   
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y =300;
mWindowManager.addView (mFloatignButton, mLayoutParams);

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 和 系统 Window

  • 应用 Window 对应着一个 Activity , 层级 1-99
  • 子Window 不能单独存在,需要依附在特定的父Window之中 , 层级 1000-1999
  • 系统Window 是需要声明权限在能创建的 Window, 比如 Toast 和 系统状态栏这些都是系统 Window , 2000-2999

    一般选用 TYPE_SYSTEM_OVERLAY 或者 TYPE_SYSTEM_ERROR

注: Window 是分层的,每个Window 都有对应的 z-ordered , 层级大的会覆盖在层级小的 Window 的上面。

WindowManager 所提供的功能很简单,常用方只有三个方法:

  • 添加 View
  • 更新 View
  • 删除View

这三个方法定义在 ViewManager 中,WindowManager 继承了 ViewManager

public interface ViewManager
{
public void addView(View view , ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}

拖动 Window 的效果

根据手指的位置来设定 LayoutParams 中 的 x 和 y 的值即可改变 Window 的位置。

首先给 View 设置 onTouchListener : mFloatingButton.setOnTouchListener(this)

然后在onTouch 方法中不断更新View 的位置即可。

public boolean onTouch(View v, MotionEvent envet){
int rawX = (int) event.getRawX();
int rawY = (int) event.getRwaY();
switch(event.getAction()){
case MotionEvent.ACTION_MOVE:{
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
break;
}
default:
break;
}
return false;
}

2. Window 的内部机制

Window 是一个抽象的概念,每一个Window 都对应着一个 View 和一个 ViewRootImpl 。

Window 和 View 通过 ViewRootImpl 来建立联系,因此Window 并不是实际存在的,它是以View 的形式存在。

View 才是 Window 存在的实体。

1. Window 的添加过程

Window 的添加过程需要通过 WindowManager 的 addView 来实现, WindowManager 是一个接口,它的真正实现是 WindowManagerImpl 类。


@Override
public void addView(View view, ViewGroup.LayoutParams params){
   mGlobal.addView(view, params , mDisplay, mParentWindow);
}

@Override
public void updateViewLayout(View view , ViewGroup.LayoutParams params){
   mGlobal.updateViewLayout(view ,params);
}

@Override
public void removeView(View view){
   mGlobal.removeView(view, false);
}

交给 WindowManagerGlobal 来处理

WindowManagerGlobal 以工厂的形式向外提供自己的实例。

WindowManagerGlobal 的 addView 方法主要分为如下步

  1. 检查参数是否合法,如果是子 Window 那么还需要调整一些布局参数

    if (view == null){
    throw new IllegalArgumentException("view must not be null");
    }
    if (display == null){
       throw new IllegalArgumentException("display must 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);
    }
  2. 创建 ViewRootImpl 并将 View 添加到列表中

在 WindowManagerGlobal 内部有如下几个列表比较重要:

private final ArrayList<Viwe>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> mDyingVies = new ArraySet<View>();
  • mViews 存储的是所有 Window 所对应的 View
  • mRoots 存储的是所有 Window 所对应的 ViewRootImpl
  • mParams 存储的是所有 Window 所对应的布局参数
  • mDyingViews 则存储了那些正在被删除的 View 对象。或者是那些已经调用 removeView 方法但是删除操作还未完成的 Window 对象。

在 addView 中通过如下方式将 Window 的一系列对象添加到列表中

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

mViews.add(view);
mRoot.add(root);
mParams.add(wparams);
  1. 通过 ViewRootImpl 来更新界面并完成 Window 的添加过程

    由ViewRootImpl 的 setView 方法来完成。View 的绘制过程是由 ViewRootImpl 来完成的。

    setView 内部通过 requestLayout 来完成异步刷新请求。

public void requestLayout(){
if (!mHandlingLayoutInLayoutRequest){
checkThread();
mLayoutRequested = true;
// 实际是VIEW 绘制的入口
scheduleTraversals();
}
}

接着会通过 WindowSession 最终来完成 Window 的添加过程。

在下面的代码中 mWindowSession 的类型是 IWindowSession , 它是一个 Binder 对象,真正实现类是 Session, 也就是 Window 的添加过程是一次IPC 调用

try{
   mOrigWindowType = mWindowAttributes.type;
   mAttachInfo.mRecomputeGlobalAttributes = true;
   collectViewAttributes();
   res =  mWindsSeesion.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, 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);
}

在 Session 内部会通过 WindowManagerService 来实现 Window 的添加

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
                      int viewVisibility, int displayId, Rect outCotentInsets,
                      InputChannel outInputChannel){
   return mService.addWindow(this, window, seq, attrs, viewVisibility, disalayId, outContentInsets, outInputChannel);
}

如此一来,Window 的添加请求就给 WindowManagerService 去处理了。在WindowManagerService 内部会为每一个应用保留一个单独的 Session.

2. Window 的删除过程

先通过 WindowManagerImpl后,再进一步通过 WindowManagerGlobal 来实现的。

public void removeView(View view , boolean immediate){
if (view == null){
throw new IllegalArgumentException("view must not be null");
}

synchronized(mLock){
       // 先通过 findViewLocked 来查找待删除的 View 的索引,建立数组遍历
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
       // 调用removeViewLocked 来做进一步的删除
removeViewLocked(index, immediate);
if (curView == view){
return;
}
throw new IllegalStateExceotion("Calling with view " + view + "but the
ViewAncestor is attached to " + curView);
}
}
private void removeViewLocked(int index, boolean immediate){
   ViewRootImpl root = mRoots.get(index);
   View view = root.getView();
   
   if (view != null){
       InputMethodManager imm = InputMethodManager.getInstance();
       if ( imm != null){
           imm.windowDismissed(mViews.get(index).getWindowToken());
      }
  }
   boolean deferred = root.die(immediate);
   if(view != null){
       view.assignParent(null);
       if (deferred){
           mDyingViews.add(view);
      }
  }
}

removeViewLocked 是通过 ViewRootImpl 来完成删除操作。

WindowManager 中提供了两种删除接口 removeView 和 removeImmdiate

分别代表 异步删除和 同步删除

3. Window 的更新过程

由WindowManagerGlobal 的 updateViewLayout 方法实现。

4. Window 的创建过程

Activity 的 Window 是通过 PolicyManager 的一个工厂方法来创建的。

Activity 的视图是如何附属在 Window 上,由于Activity 的视图由setContentView 方法提供。

public void setContentView(int layoutResID){
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

这里可以看出,Activity 将具体实现交给了 Window 处理,而Window 的具体实现是 PhoneWindow .

这里主要看 PhoneWindow

  1. 如果没有 DecorCView ,那么就创建它

    DecorView 是一个 FrameLayout , 是Activity 中的顶级 View , 一般来说它的内部包含标题栏和内部栏。

    不管怎样,内容栏是一定要存在的,并且内容来具体固定的 id ,那就是 “ content" .完整的 id 是 android.R.content.

    DecorView 的创建过程由 installDecor 方法来完成,在方法内部会通过 generateDecor 来直接创建 DecorView ,这时其还是一个空白的FrameLayout:

    protected DecorView generateDecor(){
       return new DecorView(getContext(), -1);
    }

    为了初始化 DecorView 的结构,PhoneWindow 还需要通过 generateLayout 方法来加载具体的布局文件到 DecorView 中。

    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in , new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 该id 对应的ViewGroup 就是 mContentParent
public static final inT ID_ANDROID_CONTENT = com.android.internal.R.id.content
  1. 将View 添加到 DecorView 的 mContentParent 中
// 直接将 Activity 的视图添加到 Decorview 的 mContentParent 中
mLayoutInflater.inflate(layoutResID, mContentParent)
   
// Activity 的布局文件被添加到 DecorView 的 mContentParent中, setContentView
  1. 回调Activity 的 onContentChanged 方法通知 Activity 视图已经改变
final Callback cb =  getCallback();
if (cb != null && !isDestroyed()){
cb.onContentChanged();
}

在 makeVisible 方法中,DecorView 真正地完成了添加和显示这两个过程,此时 Activity 的视图才能被用户看到。

void makeVisible(){
if (!mWindowAdded){
ViewManager wm = getWindiowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisihility(View.VISIBLE);
}


在 makeVisible 方法中,DecorView 真正地完成了添加和显示这两个过程,此时 Activity 的视图才能被用户看到。

void makeVisible(){
if (!mWindowAdded){
ViewManager wm = getWindiowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisihility(View.VISIBLE);
}

0 个评论

要回复文章请先登录注册