Window相关

《Android开发艺术探索》第8章的笔记 :)

1、基础使用

首先利用WindowManager添加一个Window
这里直接上代码

mFloatingButton = Button(this)
mFloatingButton.text="button"
mLayoutParams=WindowManager.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,ActionBar.LayoutParams.WRAP_CONTENT,0,0,PixelFormat.TRANSPARENT)
mLayoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
mLayoutParams.type=WindowManager.LayoutParams.TYPE_APPLICATION
mLayoutParams.gravity = Gravity.LEFT or Gravity.TOP
mLayoutParams.x=100
mLayoutParams.y=300
manager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
manager.addView(mFloatingButton,mLayoutParams)

声明一个mFloatingButton,之后设置LayoutParams的各项参数。最后调用Window.addView添加View。

flag

  • FLAG_NOT_FOCUSABLE
    window不需获得焦点,也不需接受输入事件,且该falg会同时添加FLAG_NOT_TOUCH_MODAL
  • FLAG_NOT_TOUCH_MODAL
    当前window范围内大点击自己处理,范围外的传递给下一层
  • FLAG_SHOW_WHEN_LOCKED
    让window显示在锁屏界面

type

type参数表示Window的类型,Window共有三种类型,应用Window,子Window,系统Window

  • 应用Window
    应用Window对应着一个Activity,层级范围是1~99。
  • 子Window
    子Window不能单独存在,需要附属在特定的父Window中,层级范围是1000~1999。
  • 系统Window
    系统Window需要声明相应的权限,即"",层级为2000~2999。

Windowmanager

WindowManager常用的有添加,更新,删除View。这三个方法定义在ViewManager中,WindowManager继承了ViewManager。

public interface ViewManager {
    void addView(View var1, LayoutParams var2);

    void updateViewLayout(View var1, LayoutParams var2);

    void removeView(View var1);
}

2、WindowManager常用方法

WindowManager实际上是一个接口,真正的实现是WindowManagerImpl类。
在WindowManagerImpl中的实现如下:

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

@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中交给了WindowManagerGlobal实现。
下面看在WindowManagerGlobal中的相关的列表定义。

    private final ArrayList mViews = new ArrayList();
    //存储所有Window对应的View
    private final ArrayList mRoots = new ArrayList();
    //存储所有Window对应的ViewRootImpl
    private final ArrayList mParams =
            new ArrayList();
    //存储所有Window对应的布局参数
    private final ArraySet mDyingViews = new ArraySet();
    //存储已经调用remove但未被删除的Window

这四个列表存储了Window的信息,将用在接下来的添加,更新和删除。

2.1、addView

  • 第一步验证参数合法,同时判断有无parentWindow,即是否是子Window,如果是子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 {
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
  • 第二步创建ViewRootImpl,并添加View至列表.
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);

    mViews.add(view);
    mRoots.add(root)   
    mParams.add(wparams);
  • 第三步更新界面完成Window的添加过程
try {
    root.setView(view, wparams, panelParentView);
} 
catch (RuntimeException e) {
   if (index >= 0) {
        removeViewLocked(index, true);
    }
    throw e;
}

在addView中最后可以看到调用了ViewRootImpl的setView方法,在setView内部异步刷新请求,之后通过WindowSession完成对Window的添加。其中所使用的WindowSession是一个Binder对象,在Session类中实现,在WindowManagerService中实现Window的添加。
ViewRootImpl.setVIew -> WindowSession -> Session -> WindowManagerService

2.2、removeView

删除Window同样是通过WindowManagerImpl调用WindowManagerGlobal中的removeView方法。
removeView的源码远少于addView,如下:

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

首先调用removeView后,会先验证View是否为null,之后通过findViewLocked查找相应View的索引,即在列表中的位置。之后调用removeViewLocked删除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中,首先获得需要删除的View,之后通过die方法完成。

  • 如果是异步删除,die方法则发送一个删除的指令,之后立刻返回。即在die返回时,并没有完成删除操作,而是将View加入到了mDyingViews列表,即待删除列表。之后又ViewRootImpl中的Handler调用doDie方法完成删除。
  • 如果是同步删除,则在die方法中直接调用doDie方法删除View。

doDie方法内部则会调用dispatchDetachedFromWindow方法完成对View的删除,dispatchDetachedFromWindow将主要完成:1.垃圾回收;2.通过Session移除Window(最终调用WindowManagerService的removeWindow方法;3.调用View的dispatchDetachedFromWindow,在内部调用onDetachedFromWindow和onDetachedFromWindowInternal。在这个方法中完成资源回收;4.调用
WindowManagerGlobal的doRemoveView刷新数据,刷新mRoots,mParams,mDyingViews。

2.3、updateView

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

在更新方法中只需要替换掉mParams中的布局信息,之后通过setLayoutParams更新ViewRootImpl中的LayoutParams。在ViewRootImpl中的LayoutParams会通过scheduleTraversals对View重新布局,并通过WindowSession更新Window视图。

3、Window的创建

3.1、Activity的Window创建

Activity的启动过程中,在ActivityThread中的performLaunchActivity方法会通过类加载器创建Activity的实例对象,并调用attach方法为其关联一系列环境变量,在attach方法中,会创建Activity所属的Window并设置回调接口。Window的创建是通过PolicyManager的makeNewWindow方法实现的。当Window接收到外界状态改变时就会回调Activity的方法。

  • 创建Window
mWindow=PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if(info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED){
    mWindow.setSoftInputMode(info.softInputMode);
}
if(info.uiOptions!=0){
    mWindow.setUiOption(info.uiOptions);
}

PolicyManager是一个策略类,方法全部在IPolicy中声明,IPolicy声明如下。

public interface IPolicy{
    public Window makeNewWindow(Context context);
    public LayoutInflater makeNewLayoutInflater(Context context);
    public WindowManagerPolicy makeNewWindowManager();
    public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}

在实际调用中,PolicyManager的真正实现类是Policy,在Policy的makeNewWindow方法中,返回了PhoneWindow(context),所以Window的真正实现类是PhoneWindow。

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

关于Ativity视图的附着,可以看到Activity将具体的实现交给了Window,Window的具体实现在PhoneWindow。

在PhoneWindow中:

1.创建DecorView,DecorView是顶级View,创建过程由installDecor完成,在内部调用generateDecor来完成创建。初始化时,PhoneWindow还需要通过generateLayout加载具体的布局文件到DecorView。如下

View in=mLayouytInflater.inflate(layoutResource,null);
decor.addView(in,new ViewGroup.LayoutPatams(MATCH_PARENT,MATCH_PARENT));
mContentRoot=(ViewGroup)in;
ViewGroup contentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT);

2.将View添加到DecorView的mContentParent中,因为之前已经创建完成了DecorVicw,因此只需要添加:mLayoutInflater.inflate(layoutResID,mContentParent)

3.回调Activity的onContentChanged方法通知Acivity视图已经发生改变。在布局文件被添加到DecorView后,会通知Activity。Activity的onContentChanged是一个空实现,可以在子Activity中处理回调。

final Callback cb=getCallback();
if(cb!=null && !isDistoryed()){
    cb.onContentChanged();
}

4.在Activity的onResume方法中会调用makeVisible,使DecorView完成了添加和显示过程,此时Activity的视图才被用户看到。

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

3.2、Dialog的Window创建

Dialog的创建于Activity类似

1.创建Window

Dialog中的创建同样是通过PolicyManager的makeNewWindow完成的,创建后的实际对象是PhoneWindow。与Avtivity相同。

2.初始化DecorView并添加Dialog视图到DecorView

3.将DecorView添加到Window中显示

在Dialog的show方法中,会通过WindowManager将DecorVice添加到Window中

mWindowManager.addView(mDecor,1);
mShowing=true;

Dialog创建Window和Activity的Window创建过程类似。当Dialog移除时,会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)

PS:Dialog必须使用Activity的context,而不能使用Application的context

3.3、Toast的Window创建

Toast是基于Window来完成的,同时采用了Handler实现了定时取消的功能。在Toast内部有两类IPC过程,一是Toast访问NotificationManagerService,另一类是NotificationManagerService回调Toast中的TN接口。
Toast内部的视图有两种方式,一种是系统默认样式,或者通过setView方法指定一个自定义View,二者都对应了Toast内部的一个View类型成员mNextView。Toast内部提供了show和cancel方法用于显示和隐藏。内部为IPC过程。

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) {
        // Empty
    }
}

public void cancel(){
    mTN.hide();
    try{
        getService().cancelToast(mContext.getPackageName(),mTN);
    }catch(RemoteException e){
        //Empty
    }
}

在show和cancel方法中,都通过NMS来具体实现,NMS运行在系统的进程中,所以只能通过远程调用显示。
TN是一个Binder类,在进行IPC时,NMS处理Toast显示和取消请求时会回调TN中的方法,又因为TN运行在Binder线程池中,所以需要Handler切换到发送Toast的线程。因为使用了Handler,所以Toast无法在没有Looper的线程中使用。
Toast的显示调用了NMS的enqueueToast方法,enqueueToast首先将Toast封装成ToastRecord对象并添加到MToastQueue队列中,MToastQueue实际上是一个数组,大小为50。

if (!isSystemToast) {
    int count = 0;
    final int N = mToastQueue.size();
    for (int i=0; i= MAX_PACKAGE_NOTIFICATIONS) {
                Slog.e(TAG, "Package has already posted " + count
                        + " toasts. Not showing more. Package=" + pkg);
                return;
            }
        }
    }
}

当Toast被添加到MToastQueue中,NMS会通过showNextToastLocaked方法显示当前Toast,Toast的显示是由ToastRecord的callback完成的。

void showNextToastLocked() {
    ToastRecord record = mToastQueue.get(0);
    while (record != null) {
        if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
        try {
            record.callback.show(record.token);
            cheduleTimeoutLocked(record);
            return;
        } catch (RemoteException e) {
            Slog.w(TAG, "Object died trying to show notification " + record.callback
                     + " in package " + record.pkg);
            // remove it from the list and let the process die
            int index = mToastQueue.indexOf(record);
            if (index >= 0) {
                mToastQueue.remove(index);
            }
            keepProcessAliveIfNeededLocked(record.pid);
            if (mToastQueue.size() > 0) {
                record = mToastQueue.get(0);
            } else {
                record = null;
            }
        }
    }
}

在Toast显示完成后,会通过scheduleTimeroutLocked发送延时消息

private void scheduleTimeoutLocked(ToastRecord r){
    mHandler.removeCallbacksAndMessages(r);
    Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
    long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
    mHandler.sendMessageDelayed(m, delay);
}

LONG_DELAY是3.5s,SHORT_DELAY是2s。延迟时间后,NMS会通过cancelToastLocked隐藏Toast并将其从mToastQueue中删除。
Toast的隐藏同样是通过ToastRecord的callback完成的,是一次IPC过程。

try {
    record.callback.hide();
} catch (RemoteException e) {
    Slog.w(TAG, "Object died trying to hide notification " + record.callback
            + " in package " + record.pkg);
    // don't worry about this, we're about to remove it from
    // the list anyway
        }

综上,Toast的显示和隐藏实际上是通过Toast的TN这个方法实现的,他有show和hide两个方法。两个方法是被NMS以跨进程调用的,因此运行在Binder的线程池内,同时使用了Handler以便于切换到当前线程。

@Override
public void show(){
    if(localLOGY)log.v(TAG,"SHOW:" + this);
    mHandler.post(mShow);
}

@Override
public void hide(){
    if(localLOGY)log.v(TAG,"HIDE:" + this);
    mHandler.post(mHide);
}

此处的mShow和mHide是Runnable类型,内部调用了handleShow和handleHide来具体实现Toast的显示隐藏。

//handleShow
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);

//handleHide
if (mView.getParent() != null) {
    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
    mWM.removeViewImmediate(mView);
}

参考来源:

《Android开发艺术探索》

你可能感兴趣的:(Window相关)