WindowManger与window之基础篇

来源:互联网 发布:ios防骚扰软件 编辑:程序博客网 时间:2024/06/05 21:01

前言 

   在这里这篇中主要讲解:
       (1)WindowManager、Window的简单介绍
       (2)WindowManager的三种方法:addView,removeView,updateViewLayout。        

一 WindowManger

1  描述 

       在android 的窗口机制中,启动Activity时,Activity会将顶级的View(DecorView)注册到 Window Manager 中,当用户触碰屏幕时候,Window Manager就会接收到通知,进而实现对View的操作,从而完成整个通信流程。WindowManager可以修改Window状态、属性以及view增加、删除、更新、窗口顺序、消息收集等工作,WindowManager有三个方法:addView,removeView,updateViewLayout,而属性都在《WindowManager.LayoutParams》类里面,通过它可以设置和获得当前窗口的一些属性。

ViewManager

      结构:
      public interface WindowManager extends ViewManager;
    ViewManager是什么?我们来看:
public interface ViewManager{    /**     * Assign the passed LayoutParams to the passed View and add the view to the window.     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming     * errors, such as adding a second view to a window without removing the first view.     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a     * secondary {@link Display} and the specified display can't be found     * (see {@link android.app.Presentation}).     * @param view The view to be added to this window.     * @param params The LayoutParams to assign to view.     */    public void addView(View view, ViewGroup.LayoutParams params);    public void updateViewLayout(View view, ViewGroup.LayoutParams params);    public void removeView(View view);}

   从上面可以看出WindowManager很好的继承了ViewManager的方法:

(1)窗口添加

publicvoidaddView(View view, ViewGroup.LayoutParamsparams);

(2)窗口更新

publicvoidupdateViewLayout(View view, ViewGroup.LayoutParamsparams);

(3)窗口删除

publicvoidremoveView(View view);

WindowManagerImpl

     从上面来看WindowManager是一个抽象的接口肯定是没有办法直接使用的,而我们要使用它就必须要实现这个接口,WindowManager 的实现类就是WindowManagerImpl,我们看下它的源码,不是很多我们直接贴出来:

package android.view;import android.annotation.NonNull;import android.os.IBinder;public final class WindowManagerImpl implements WindowManager {    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();    private final Display mDisplay;    private final Window mParentWindow;    private IBinder mDefaultToken;    public WindowManagerImpl(Display display) {        this(display, null);    }    private WindowManagerImpl(Display display, Window parentWindow) {        mDisplay = display;        mParentWindow = parentWindow;    }    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {        return new WindowManagerImpl(mDisplay, parentWindow);    }    public WindowManagerImpl createPresentationWindowManager(Display display) {        return new WindowManagerImpl(display, mParentWindow);    }    /**     * Sets the window token to assign when none is specified by the client or     * available from the parent window.     *     * @param token The default token to assign.     */    public void setDefaultToken(IBinder token) {        mDefaultToken = token;    }    @Override    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {        applyDefaultToken(params);        mGlobal.addView(view, params, mDisplay, mParentWindow);    }    @Override    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {        applyDefaultToken(params);        mGlobal.updateViewLayout(view, params);    }    private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {        // Only use the default token if we don't have a parent window.        if (mDefaultToken != null && mParentWindow == null) {            if (!(params instanceof WindowManager.LayoutParams)) {                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");            }            // Only use the default token if we don't already have a token.            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;            if (wparams.token == null) {                wparams.token = mDefaultToken;            }        }    }    @Override    public void removeView(View view) {        mGlobal.removeView(view, false);    }    @Override    public void removeViewImmediate(View view) {        mGlobal.removeView(view, true);    }    @Override    public Display getDefaultDisplay() {        return mDisplay;    }}

  从上面的代码可以看出WindowManagerImpl,实现了WindowManager的三个方法。

二  window   

Android中可以直接可见的界面包括Activity Toast Dialog  PopuWindow 等。

1 android的窗口分为三种:

 a)应用程序窗口 (Application Window): 包括所有应用程序自己创建的窗口,以及在应用起来之前系统负责显示的窗口。

 b)子窗口(Sub Window):比如应用自定义的对话框,或者输入法窗口,子窗口必须依附于某个应用窗口(设置相同的token,例如popupwindow等都要依附一个组件)。

 c)系统窗口(System Window): 系统设计的,不依附于任何应用的窗口,比如说,状态栏(Status Bar), 导航栏(Navigation Bar), 壁纸(Wallpaper), 来电显示窗口(Phone),锁屏窗口(KeyGuard), 信息提示窗口(Toast), 音量调整窗口,鼠标光标等等。

    注意:Window是一个抽象的概念,每一个Window都对应着一个DecorView和一个ViewRootImpl,Window又通过ViewRootImpl与DecorView建立联系,因此Window并不是实际存在的,他是以DecorView的形式存在的。

PhoneWindow

      在android中《Window》的实现类是PhoneWindow,可以自己到源码库中看下PhoneWindow源码,源码太多在这里就不贴出来了。

三  add View

  在《DecorView与window的创建这篇文章中,详细讲解了DecorView与Window的创建的过程,在这里就不再累述了,我把小结贴出来:

    1)Window是在Activity的attach的方法中创建的,window即PhoneWindow,因为PhoneWindow是Window的实现类;

    2)DecorView是在PhoneWindow的setContentView方法中进行创建的,DecorView是ViewTree最顶层的View,而Decor View本质就是FramLayout布局;

   3)WindowMangerImpl(windowManger的实现类)通过调用WindowMangerGlobal的addVIew方法添加DecorView,然后交给ViewRootImpl去处理(其实最终是交给WindowMangerService处理的)。

    在这里要知道DecorView是主窗口中的顶级view(实际上就是ViewGroup),而ViewGroup是对一组View的管理。因此,在ViewGroup中建立了所有view的关系网。而最终ViewGroup附属在主窗口上。这样就很容易在窗口中通过findViewById找到具体的View了。view中的事件处理也是根据这个路径来处理的。

1 引出ViewRootImpl 

WindowMangerImpl#addView方法:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {       applyDefaultToken(params);       mGlobal.addView(view, params, mDisplay, mParentWindow);}
   从上述代码可以看出WindowMangerImpl中的方法addView调用了WindowManagerGlobal中的方法addView。
WindowManagerGlobal#addView方法
先认识几个数据列表:
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>();  
     在上面的声明中吗,mViews存储的是所有Window所对应的View,mRoots存储的是所有Window所对应的ViewRootImpl,mParams存储的是所有Window所对应的布局参数,而mDyingViews存储了那些正在被删除的View对象,或者说是那些已经调用removeView方法但是还没有删除的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");        }        ............        synchronized (mLock) {            ............           //第二步:把数据添加到相应的列表中            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 {            //第三步:通过ViewRootImpl的实例root完成view的添加工作            root.setView(view, wparams, panelParentView);        } catch (RuntimeException e) {            // BadTokenException or InvalidDisplayException, clean up.            synchronized (mLock) {                final int index = findViewLocked(view, false);                if (index >= 0) {                    removeViewLocked(index, true);                }            }            throw e;        }    }
添加view可以分为三步走:
第一步:判断参数是否合法;
第二步:把数据添加到相应的列表中;
第三步:通过ViewRootImpl的实例root调用setView方法完成view的添加工作;
ViewRootImpl#setView:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {        synchronized (this) {            if (mView == null) {                mView = view;               ...........                // 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();                if ((mWindowAttributes.inputFeatures                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {                    mInputChannel = new InputChannel();                }                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) {                   ...........                } finally {                    if (restore) {                        attrs.restore();                    }                }           ...........        }    }

     在setView内部会通过requestLayout来完成异步刷新请求,requestLayout最终会调用performTraversals方法来完成View的绘制,源码注释如下:差不多意思就是在添加Window之前先完成第一次layout布局过程,以确保在收到任何系统事件后面重新布局,然后使用mWindowSession添加Window,那么mWindowSession是?这一部分内容来源于Android中的ViewRootImpl类源码解析》。

mWindowSession:
 final IWindowSession mWindowSession;

    说明:mWindowSession类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是说这其实是一次IPC过程,远程调用了Session中的addToDisPlay方法。

Session#addToDisPlay:

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

   说明:Session这个类在package com.android.server.wm,如果没有找到可以点击这里下载查看。可以看出,Window的添加请求就交给WindowManagerService去处理了。addView大概一个过程如下:WindowManager——>WindowManagerGobal——>ViewRootImpl——>Session——>WindowManagerService。

2  DecorView加入到WindowMangaer

 《ActivityThead》中的两个重要的方法:
            performLaunchActivity( );

            handleResumeActivity( );

ActivityThead#performLaunchActivity部分源码如下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent{       .....        try {            Application app = r.packageInfo.makeApplication(false, mInstrumentation);            if (localLOGV) Slog.v(TAG, "Performing launch of " + r);            if (localLOGV) Slog.v(                    TAG, r + ": app=" + app                    + ", appName=" + app.getPackageName()                    + ", pkg=" + r.packageInfo.getPackageName()                    + ", comp=" + r.intent.getComponent().toShortString()                    + ", dir=" + r.packageInfo.getAppDir());            if (activity != null) {               ......                activity.attach(appContext, this, getInstrumentation(), r.token,                        r.ident, app, r.intent, r.activityInfo, title, r.parent,                        r.embeddedID, r.lastNonConfigurationInstances, config,                        r.referrer, r.voiceInteractor, window);                if (customIntent != null) {                    activity.mIntent = customIntent;                }                ....        } catch (SuperNotCalledException e) {            throw e;        } catch (Exception e) {            if (!mInstrumentation.onException(activity, e)) {                throw new RuntimeException(                    "Unable to start activity " + component                    + ": " + e.toString(), e);            }        }        return activity;    }

activity#attach方法部分源码如下:

final void attach(Context context, ActivityThread aThread,            Instrumentation instr, IBinder token, int ident,            Application application, Intent intent, ActivityInfo info,            CharSequence title, Activity parent, String id,            NonConfigurationInstances lastNonConfigurationInstances,            Configuration config, String referrer, IVoiceInteractor voiceInteractor,            Window window) {        attachBaseContext(context);        mFragments.attachHost(null /*parent*/);       //创建Window对象        mWindow = new PhoneWindow(this, window);        mWindow.setWindowControllerCallback(this);        mWindow.setCallback(this);        mWindow.setOnWindowDismissedCallback(this);        mWindow.getLayoutInflater().setPrivateFactory(this);        .....}

  上述代码我们只看红色的,可以看出在performLaunchActivity中,会调用activity.attach方法建立一个window。

ActivityThead#handleResumeActivity部分源码如下:

final void handleResumeActivity(IBinder token,            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {        ActivityClientRecord r = mActivities.get(token);            ........                                 if (r.window == null && !a.mFinished && willBeVisible) {                r.window = r.activity.getWindow();                View decor = r.window.getDecorView();// 获得窗口的顶级View                decor.setVisibility(View.INVISIBLE);                ViewManager wm = a.getWindowManager();// WindowManager继承自ViewManager                WindowManager.LayoutParams l = r.window.getAttributes();                a.mDecor = decor;                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;                l.softInputMode |= forwardBit;                if (r.mPreserveWindow) {                    a.mWindowAdded = true;                    r.mPreserveWindow = false;                    // Normally the ViewRoot sets up callbacks with the Activity                    // in addView->ViewRootImpl#setView. If we are instead reusing                    // the decor view we have to notify the view root that the                    // callbacks may have changed.                    ViewRootImpl impl = decor.getViewRootImpl();                    if (impl != null) {                        impl.notifyChildRebuilt();                    }                }                if (a.mVisibleFromClient && !a.mWindowAdded) {                    a.mWindowAdded = true;                    wm.addView(decor, l);// 把主窗口的顶级view加入到WindowMangaer                }            // If the window has already been added, but during resume            // we started another activity, then don't yet make the            // window visible.            } else if (!willBeVisible) {                if (localLOGV) Slog.v(                    TAG, "Launch " + r + " mStartedActivity set");                r.hideForNow = true;            }            // Get rid of anything left hanging around.            cleanUpPendingRemoveWindows(r, false /* force */);            ....    }
    上述代码,我们只看红色的部分,其余的先不要管,可以看出在这里,主窗口(DecorView)加入到WindowManager中,也就是说方法ActivityThead#handleResumeActivity把DecorView、WindowManager联系在一起了。

四 removeView 


WindowManagerGlobal#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);        }}
   首先调用findViewLocked来查找删除view的索引,这个过程就是建立数组遍历。然后再调用removeViewLocked来做进一步的删除。
WindowManagerGlobal#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);            }        }    }

    真正删除操作是viewRootImpl来完成的。windowManager提供了两种删除接口,removeViewImmediate,removeView。它们分别表示异步删除和同步删除。具体的删除操作由ViewRootImpl的die来完成。
ViewRootImpl#die:
boolean die(boolean immediate) {        // Make sure we do execute immediately if we are in the middle of a traversal or the damage        // done by dispatchDetachedFromWindow will cause havoc on return.        if (immediate && !mIsInTraversal) {            doDie();            return false;        }         if (!mIsDrawing) {            destroyHardwareRenderer();        } else {            Log.e(TAG, "Attempting to destroy the window while drawing!\n" +                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());        }        mHandler.sendEmptyMessage(MSG_DIE);        return true;}
    由上可知如果是removeViewImmediate,即immediate为true,立即调用doDie,如果是removeView,用handler发送消息,ViewRootImpl中的Handler会处理消息并调用doDie。
ViewRootImpl#doDie:
void doDie() {        checkThread();        if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);        synchronized (this) {            if (mRemoved) {                return;            }            mRemoved = true;            if (mAdded) {                //窗口从窗口管理器上摘除 在内部会调用onDetachedFromWindow()和onDetachedFromWindowInternal()                dispatchDetachedFromWindow();            }             if (mAdded && !mFirst) {                destroyHardwareRenderer();                 if (mView != null) {                    int viewVisibility = mView.getVisibility();                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;                    if (mWindowAttributesChanged || viewVisibilityChanged) {                        // If layout params have been changed, first give them                        // to the window manager to make sure it has the correct                        // animation info.                        try {                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {                                mWindowSession.finishDrawing(mWindow);                            }                        } catch (RemoteException e) {                        }                    }                    // 垃圾回收相关工作,比如清数据,回调等                    mSurface.release();                }            }             mAdded = false;        }       //通过doRemoveView刷新数据,删除相关数据        WindowManagerGlobal.getInstance().doRemoveView(this);}
主要做四件事:
1.垃圾回收相关工作,比如清数据,回调等。
2.通过Session的remove方法删除Window,最终调用WindowManagerService的removeWindow
 
3.调用dispathDetachedFromWindow,在内部会调用onDetachedFromWindow()和onDetachedFromWindowInternal()。当view移除时会调用onDetachedFromWindow,它用于作一些资源回收。
4.通过doRemoveView刷新数据,删除相关数据,如在mRoot,mDyingViews中删除对象等。

WindowManagerGlobal#doRemoveView:
void doRemoveView(ViewRootImpl root) {        synchronized (mLock) {            final int index = mRoots.indexOf(root);            if (index >= 0) {                mRoots.remove(index);                mParams.remove(index);                final View view = mViews.remove(index);                mDyingViews.remove(view);            }        }        if (HardwareRenderer.sTrimForeground && HardwareRenderer.isAvailable()) {            doTrimForeground();        }}

五 updateViewLayout


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);       }}
   通过viewRootImpl的setLayoutParams更新viewRootImpl的layoutParams,接着scheduleTraversals对view重新布局,包括测量,布局,重绘,此外它还会通过WindowSession来更新window。这个过程由WindowManagerService实现.

六 小结

 1.WindowManger,DecorView是在ActivityThread#handleResumeActivity中建立联系的,即DecorView到了WindowManger中;
2.WindowManger通过viewRootImpl操作Window,实现view的添加、删除、更新等,可以说viewRootImpl是WindowManger和Window(DecorView)通信的纽带(桥梁);
3.addView流程,大致为:WindowManager——>WindowManagerGobal——>ViewRootImpl——>Session——>WindowManagerService。

学习更多:
悬浮窗口示例
参考:
资料1
资料2