android进阶(八)-----Android中的window和windowManager

265 阅读6分钟

一、Window和WindowManager

1、为了分析Window的工作机制,我们先了解一下如何使用WindowManager添加一个Window。

示例代码:

Button btn = new Button(this); 
btn.setText("button"); 
LayoutParams lp = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,0,0,PixelFromat.TRANSPARENT); 
lp.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_SHOW_WHEN_LOCKED; 
lp.gravity = Gravity.LEFT | Gravity.TOP; lp.x= 100; 
lp.y=300; 
windowManager.addView(btn,lp);

2、Flags参数表示Window的属性,常用的如下:

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

FLAG_NOT_TOUCH_MODAL:系统会将当前window区域以外的单击事件传递给底层window,当前window区域内的单击事件则自己处理

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

3、Type参数表示window类型,window有三种类型,分别应用window、子window、和系统window。

window是分层的,每个window都有对应的z-ordered,层级大的会覆盖在层级小的上面。应用window的层级范围是1-99,子window的层级范围是1000-1999,系统window的层级范围是2000-2999,这些层级对应这WindowManager.LayoutParams的type参数.如果想让Window位于所有window的最上层,采用较大的层级即可。

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

添加View、更新View、删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。

二、window的内部机制

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

1、window的添加过程

window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口,他的真正实现是WindowManagerImpl类。在WindowManagerImpl中Window的三大操作是实现如下

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

可以发现,WindowManagerImpl并没有直接实现Window,而是全部交给WindowManagerGlobal来处理,WindowManagerGlobal以工厂形式向外提供自己的实例。

在WindowManagerGlobal中有一段代码:

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance()。

WindowManagerImpl这种工作模式是典型的桥接模式,将所有的操作全部委托给WindowManagerGlobal实现。WindowManagerGlobal的addView方法主要分为如下几步:

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

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>(); 
private final ArraySet<View> mDyingViews = new ArraySet<VIew>();   
root = new ViewRootImpl(view.getContext(),display); 
view.setLayoutParams(wparams); 
mViews.add(view); 
mRoots.add(root); 
mParams.add(wparams);

(3)通过ViewRootImpl来更新界面并完成Window的添加过程,View的绘制过程是由ViewRootImpl来完成的,在setView内部会通过requestLayout来完成异步刷新请求。scheduleTraversals实际是View绘制的入口:

public void requestLayout(){ 
    if(!mHandlinglayoutInLayoutRequest){ 
        checkThread(); 
        mLayoutRequested = true; 
        scheduleTraversals(); 
    } 
}

2、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 IllegalArgumentException("Calling with view"+view+"but the ViewAncestor is attached to"+curView); 
    }   
} 
public 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和removeViewImmediate,分别表示异步删除和同步删除

boolean die(boolean immediate){ 
    if(immediate && !mIsInTraversal){ 
        doDie(); 
        return false; 
    } 
    if(!mIsDrawing){ 
        destroyHasdwareRenderer(); 
    } 
    mHandler.sendEmptyMessage(MSG_DIE); 
    return true; 
}

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

三:Window的创建过程

1、Activity的Window创建过程

activity启动过程最终由ActivityThread中的performLaunchActivity()来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用attach方法为其关联运行过程中所依赖的一系列上下文环境变量。

在activity的attach方法中,系统会创建Activity所属的Window对象并为其设置回调接口,Window对象的创建是通过PolicyManager的makeNewWindow方法实现的。由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的方法。

Activity的视图附属在window流程:

Activity将具体实现交给Window处理,而Window的具体实现是PhoneWindow,PhoneWindow的setContentView方法大致遵循以下几个步骤:

(1)如果没有DecorView,就创建他

DecorView是FrameLayout,是activity中顶级View,一般内部包含标题栏和内容栏

(2)将View添加到DecorView的mContentParent中

(3)回调Activity的onContentChanged方法通知Activity视图已经发生改变

2、Dialog的Window创建过程

Dialog的Window的创建过程和Activity类似。有以下几个步骤:

(1)创建Window

Dialog中Window的创建同样是通过PolicyManager的makeNewWindow方法来完成的

(2)初始化DecorView并将Dialog的视图添加到DecorView中

(3)将DecorView添加到Window中并显示

3、Toast的Window创建过程

Toast和Dialog不同,他的工作过程比较复杂。Toast也是基于Window来实现的,但是Toast具有定时取消的功能,所以系统采用了Handler。在Toast的内部有两类IPC过程,第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast里的TN接口。

Toast属于系统Window,它内部的视图由两种方式指定,一种是系统默认的样式,另一种是通过setView方法来指定一个自定义View。Toast提供了show和cancel分别用于显示和隐藏Toast。

代码如下:

public void show(){ 
    if(mNextView == null){ 
        throw new RuntimeException("setView must have been called"); 
    } 
    INotificationManager service = getService(); 
    String pkg = mContext.getOpPackageName(); 
    TN tn = mTN; 
    tn.mNextView = mNextView; 
    try{ 
        service.enqueueToast(pkg,tn,mDuration); 
    }catch(RemoteException e){
    } 
} 
public void cancel(){ 
    mTN.hide(); 
    try{ 
        getService().cancelToast(mContext.getPackageName(),mTN); 
    }catch(RemoteException e){ 
    } 
}

显示和隐藏Toast都需要通过NMS来实现,由于NMS运行在系统的进程中,所以只能通过远程调用的方式来显示和隐藏Toast。需要注意的是TN这个类是一个Binder类,在Toast和NMS进行IPC的过程中,当NMS处理Toast的显示或隐藏请求时会跨进程回调TN中的方法,这个时候由于TN运行在Binder线程池中,所以需要通过Handler将其切换到当前线程中