理解 Window 和 WindowManager

Window 和 WindowManager

Window 表示一个窗口的概念,它是一个抽象类,具体实现是 PhoneWindow,可通过 WindowManager 创建 Window,Window 具体实现位于 WindowManagerService 中,WindowManager 与 WindowManagerService 的交互是一个 IPC 过程。不管是 Activity,Dialog,Toast,它们的视图实际上都是附加在 Window 上,因此 Window 实际是 View 的直接管理者。

  • 使用 WindowManager 添加一个 Window,注意在大于等于23版本下编译,悬浮窗权限默认是关闭没有权限,然在小于23版本下编译悬浮窗权限是开启有权限的,可以在设置 – 应用程序 – 选择你的 App 进行设置。
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Button button = new Button(WindowActivity.this);
button.setText("我是添加的悬浮窗");
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                        WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = 50;
params.y = 400;
wm.addView(button, params);     //onCreate 中添加悬浮窗

protected void onDestroy() {    //移除悬浮窗
     super.onDestroy();
     if (wm != null) {
         wm.removeView(button);
     }
}

FLAG_NOT_FOCUSABLE
表示 Window 不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用 FLAG_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的 Window。

FLAG_NOT_TOUCH_MODAL
系统会将当前 Window 区域以外的单击事件传递给底层的 Window,当前 Window 区域以内的单击事件则自己处理。一般开启此标记,否则其他 Window 将无法收到单击事件。

FLAG_SHOW_WHEN_LOCKED
开启此模式可以让 Window 显示在锁屏的界面上。

Type 参数表示 Window 的类型,有三种类型,并具有层级范围,层级大的会覆盖在层级小的 Window 的上面,如果想要位于所有 Window 最顶层,那么采用较大层级即可。一般采用 TYPE_SYSTEM_OVERLAY 和 TYPE_SYSTEM_ERROR,还要同时声明权限 “android.permission.SYSTEM_ALERT_WINDOW”

Window 类型详解层级范围
应用 Window应用类 Window 对应着一个 Activity1~99
子 Window不能单独存在,需要附属在特定父 Window,比如 Dialog1000~1999
系统 Window需要声明权限在能创建的 Window,比如 Toast2000~2999
Window 的内部机制

Window 是一个抽象的概念,每一个 Window 都对应着一个 View 和 一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,因此 Window 并不是实际存在的,它是以 View 的形式存在。

Window 的添加过程源码分析

Window 的添加过程需要通过 WindowManager 的 addView 来实现,WindowManager 是一个接口,实现类是 WindowManagerImpl 类,部分实现如下:

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

@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);
}

WindowManagerImpl 并没有实现 Window 的三大操作,而是全部交给 WindowManagerGlobal 来处理。mGlobal 的 addView 方法主要分为如下几步

1.检查参数是否合法,如果是子 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);                           
    }                                                                          

2.创建 ViewRootImpl 并将 View 添加到列表中

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>();  
//存储的是正在被删除的 View 对象   
private final ArraySet<View> mDyingViews = new ArraySet<View>(); 

root = new ViewRootImpl(view.getContext(), display);                  
view.setLayoutParams(wparams);                                        
mViews.add(view);        //存储的是所有 Window 所对应的 View                             
mRoots.add(root);        //存储的是所有 Window 所对应的 ViewRootImpl
mParams.add(wparams);    //存储的是所有 Window 所对应的布局参数                                 

3.通过ViewRootImpl 来更新页面并完成 Window 的添加过程

try {                                                         
    root.setView(view, wparams, panelParentView);             
} catch (RuntimeException e) {                                
    if (index >= 0) {                                         
        removeViewLocked(index, true);                        
    }                                                         
    throw e;                                                  
}                                                             

ViewRootImpl 中的 setView 方法会调用 requestLayout 方法

@Override                                  
public void requestLayout() {              
    if (!mHandlingLayoutInLayoutRequest) { 
        checkThread();                     
        mLayoutRequested = true;           
        scheduleTraversals();              
    }                                      
}                                          

scheduleTraversals 实际是 View 绘制的入口,接着会通过 WindowSession 最终来完成 Window 的添加过程,mWindowSession 类型是 IWindowSession,是一个 Binder 对象,真正实现类是 Session,一次 IPC 调用。

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);                
}                                                              

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

Window 的删除过程

Window 的删除过程和添加过程一样,都是先通过 WindowManagerImpl 后,再进一步通过 WindowManagerGlobal 来实现。

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

    synchronized (mLock) {                                                
        int index = findViewLocked(view, true);                           
        View curView = mRoots.get(index).getView();                       
        removeViewLocked(index, immediate);                               
        if (curView == view) {                                            
            return;                                                       
        }                                                                 

        throw new IllegalStateException("Calling with view " + view       
                + " but the ViewAncestor is attached to " + curView);     
    }                                                                     
}                                                                         

通过 findViewLocked 来查找待删除的 View 的索引,查找过程就是建立的数组遍历,然后调用 removeViewLocked 来进一步删除。

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 中提供异步删除和同步删除两种接口,这里是异步情况,具体的删除操作由 ViewRootImpl 的 die 方法来完成。die 方法发送一个请求消息立刻返回,并没有完成删除操作,添加到待删除的 mDyingViews 列表中。

boolean die(boolean immediate) {                                                  
    if (immediate && !mIsInTraversal) {                                          
        doDie();                                                                 
        return false;                                                            
    }                                                                            

    if (!mIsDrawing) {                                                           
        destroyHardwareRenderer();                                               
    } else {                                                                     
        Log.e(mTag, "Attempting to destroy the window while drawing!\n" +        
                "  window=" + this + ", title=" + mWindowAttributes.getTitle()); 
    }                                                                            
    mHandler.sendEmptyMessage(MSG_DIE);                                          
    return true;                                                                 
}                                                                                

在 die 方法中做了简单的判断,如果是异步删除,那么就发送一个 MSG_DIE 的消息,ViewRootImpl 中的 Handler 会处理此消息并调用 doDie 方法,如果是同步删除就不直接发消息,直接调用,doDie 会调用 dispatchDetachedFromWindow,真正删除在这个方法中,主要做四件事。

  1. 垃圾回收相关的工作,比如清除数据和消息,移除回调。
  2. 通过 Session 的 remove 方法删除 Window,同样是一个 IPC 过程,Session 内部通过 WindowManagerService 的 removeWindow 方法。
  3. 调用 View 的 dispatchDetachedFromWindow 方法,内部调用 View 的 onDetachedFromWindow 以及 onDetachedFromWindowInternal,当 View 移除时,做一些资源回收的工作,比如终止动画,停止线程等。
  4. 调用 WindowManagerGloal 的 doRemoveView 方法刷新数据,包括 mRoots、mParams 以及 mDyingViews,需要将当前 Window 所关联的这三类对象从列表中删除。
Window 的更新过程

查看 WindowManagerGlobal 的 updateViewLayout 方法

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {                 
    if (view == null) {                                                                  
        throw new IllegalArgumentException("view must not be null");                     
    }                                                                                    
    if (!(params instanceof WindowManager.LayoutParams)) {                               
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); 
    }                                                                                    

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;       

    view.setLayoutParams(wparams);                                                       

    synchronized (mLock) {                                                               
        int index = findViewLocked(view, true);                                          
        ViewRootImpl root = mRoots.get(index);                                           
        mParams.remove(index);                                                           
        mParams.add(index, wparams);                                                     
        root.setLayoutParams(wparams, false);                                            
    }                                                                                    
}                                                                                        

首先需要更新 View 的 LayoutParams 并替代掉老的 LayoutParams,接着在更新 ViewRootImpl 中的 LayoutParams,这一步通过 setLayoutParams 实现,同样通过requestLayout 中的 scheduleTraversals 方法对 View 重新布局,ViewRootImpl 还会通过 WindowSession 来更新 Window 的视图,最终由 WindowManagerService 的relayoutWindow 方法来具体实现,也是一个 IPC 过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值