Android 理解Window 和 WindowManager

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tianmi1988/article/details/50409869

    这几天阅读了《Android开发艺术探索》的关于Window和WindowManager的章节,特此写一片博文来整理和总结一下学到的知识,做一个笔记方便自己查阅。

 说到Window,大家都会想到所有的视图,包括Activity,Dialog,Toast,它们实际上都是附加在Window上的,Window是这些视图的管理者.今天我们就来具体将一下这些视图是如何附加在Window上的,Window有是如何管理这些视图的.

1.Window的属性和类别

 当我们通过WindowManager添加Window时,可以通过WindowManger.LayoutParams来确定Window的属性和类别.其中Flags参数标示Window的属性,我们列出几个比较常见的属性:

  • FLAG_NOT_FOCUSABLE 这个参数表示Window不需要获取焦点,也不需要接收任何输入事件
  • FLAG_NOT_TOUCH_MODAL 这个参数表示当前Window区域之外的点击事件传递给底层Window,区域之内的点击事件自己处理,一般默认开启
  • FLAG_SHOW_WHEN_LOCKED 这个属性可以让Window显示在锁屏界面上

 Window不仅有属性,还有类型.Type参数表示Window的类型,分别为应用Window(activity对应的),子window(dialog对应的),和系统Window(Toast和系统通知栏).Window是分层的,每个window都有z-ordered,层级大的window会覆盖层级小的window,其大小关系为系统window>子window>应用window.所以系统window总会显示在最上边,但是使用系统window是需要声明相应的权限的.这一点需要注意.具体Type参数表示Window的类型如下

  • 应用 Window:层级范围 1-99,优先级最低;一个应用 Window 对应一个 Activity
  • 子 Window:1000-1999,优先级中等;例如对应一个 Dialog 。
  • 系统 Window:2000-2999,一般为 TYPE_SYSTEM__OVERLAY 或 TYPE_SYSTEM_ERROR ,需要声明 SYSTEM_ALERT_WINDOW 权限

2.WindowManager接口

WindowManger实现了ViewManager这个接口,所提供的主要函数只有三个:
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
  • 每一个 Window 都对应着一个 View 和一个 ViewRootImpl
  • Window 和 View 通过 ViewRootImpl 建立联系
  • 无法直接操作 Window,只能通过 WidowManager

2.1 Window的添加过程

示例代码可以在Window中简单添加一个Button
Button btn = new Button(this);
    btn.setText("Button");
    LayoutParams params = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
    params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | 
        LayoutParams.FLAG_NOT_FOCUSABLE |
        LayoutParams.FLAG_SHOW_WHEN_LOCKED;
    params.gravity = Gravity.LEFT | Gravity.TOP;
    params.x = 100;
    params.y = 300;
    windowManager.addView(btn, params);

看看WindowManagerImpl#addView
    public void  addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }                                                                                                                                                         private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();<span style="font-family: monospace;font-size:14px; white-space: pre;"></span>

接着看WindowManagerGlobal#addView
 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 {
            // If there's no parent and we're running on L or above (or in the
            // system context), assume we want hardware acceleration.
            final Context context = view.getContext();
            if (context != null
                    && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }

            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 {
            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;
        }
    }
顺便看下这个类里面比较重要的成员变量
// 存储所有window所对应的view
    private final ArrayList<View> mViews = new ArrayList<View>();
    // 存在window所对应的viewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    // 存储了所有window对应的布局参数
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    // 存储了那些正在被删除的view对象,调用了removeVIew,但是没有完成的
    private final ArraySet<View> mDyingViews = new ArraySet<View>();
WindowManagerGlobal#addView按照书中精简一下代码就是
// 创建ViewRootImpl,然后将下述对象添加到列表中
root = new ViewRootImpl(view.getContext(), display);
       view.setLayoutParams(wparams);//设置Params
       mViews.add(view);//window列表添加
       mRoots.add(root);//ViewRootImpl列表添加
       mParams.add(wparams);//布局参数列表添加
最后添加要加入的View
// 通过ViewRootImpl的setView来完成
root.setView(view, wparams, panelParentView);
在ViewRootImpl的setView函数中,会调用requestLayout来完成异步刷新,然后在requestLayout中调用scheduleTraversals来进行view绘制.
@Override
   	public void requestLayout() {
       if (!mHandlingLayoutInLayoutRequest) {
           checkThread();
           mLayoutRequested = true;
           scheduleTraversals(); // 实际View绘制的入口
       }
   }

最后通过WindowSession来完成Window的添加过程,它是一个Binder对象,通过IPC调用来添加window,sheduleTraversals()会调用到下面的方法
mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel);

所以,Window的添加请求就交给WindowManagerService去处理,在其内部为每个应用保留一个单独的Session.

2.2 Window的删除过程

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的index
        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先通过findViewLocked来查找待删除的View的索引,然后用removeViewLocked来做进一步删除.
private void removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots.get(index); //获得当前的view的viewRootImpl
    View view = root.getView();

    if (view != null) { //先让imm下降
        InputMethodManager imm = InputMethodManager.getInstance();
        if (imm != null) {
            imm.windowDismissed(mViews.get(index).getWindowToken());
        }
    }
    boolean deferred = root.die(immediate); //die方法只是发送一个请求删除的消息之后就就返回
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);//加入dyingView
        }
    }
}
WindowManagerGlobal 通过 ViewRootImpl 来完成删除操作,在WindowManager中提供了两种删除接口removeVIew()和removeViewImmediate(),它们分别表示异步和同步删除(不常用).而异步操作中会调用die函数,来发送一个MSG_DIE消息来异步删除,ViewRootImpl的Handler会调用doDie(),而如果是同步删除,那么就直接调用doDie(),然后在removeView函数中把View添加到mDyingViews中.
ViewRootImpl.die()
boolean die (boolean immediate) {
    if (immediate && !mIsInTraversal) {
        doDia();
        return flase;
    }

    if (!mIsDrawing) {
        destroyHardwareRenderer();
    } else {
        Log.e(TAG, "尝试摧毁正常绘制中的 Window");
    }
    // ViewRootImpl 的 mHandler 将处理此消息并调用 doDie
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

当 immediate 为 false,使用异步删除,就发送一个 MSG_DIE 的消息,ViewRootImpl 的 mHandler 将处理此消息并调用 doDie 方法;如果是同步删除,就是直接调用 doDie 方法;doDie 方法会调用 dispatchDetachedFromWindow 方法,内部真正实现了 View 的删除逻辑。
dispatchDetachedFromWindow 方法做了四步工作:

  • 垃圾回收,例如清除数据、消息、移除回调
  • 通过 Session 的 remove 方法删除 Window

mWindow.remove(mWindow)
WindowManagerService.removeWindow()
  • 调用 View 的 dispatchDetachedFromWindow 方法,内部调用 View 的 onDetachedFromWindow、onDetachedFromInternal()

  • 调用 WindowManagerGlobal 的 doRemoveView 方法刷新数据,包括 mRoots、mParams、mDyingViews

2.3 Window 的更新过程

WindowManagerGlobal.updateViewLayout
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
      	.....
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,更新 ViewRootImpl 的 LayoutParams,内部调用 scheduleTraversals 对 view 重绘,通过 WindowSession 更新 Window 的视图,最终调用 WindowMService 的 relayoutWindow() 具体实现。

3.Window的创建

学习完第二章我就有个疑问:WindowManager提供的接口只有Window的增删改,那么Window的创建到底是在哪进行的?不同类型的Window的创建过程不同,这里我只来讲一下Activity的Window的创建过程。在Window的启动过程中,会调用attach()函数来为其关联运行过程中所依赖的一系列上下文环境变量,这里的流程也可以参照setContentView源码浅析中的第三点。
ActivityThread performLaunchActivity()
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...
if (activity != null) {
    Context appContext = createBaseContextForActivity(r, activity);
    CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
    Configuration config = new Configuration(mCompatConfiguration);
    // 为 Activity 关联运行过程中所依赖的一系列上下文环境变量
    activity.attch(appContext, this, getInstrumentation(), r.token, 
            r.ident, app, r.intent, r.activityInfo, title, r.parent, 
            r.embeddedID, r.lastNonConfigurationInstances, config, r.voiceInteractor);
}
在 Activity 的 attach 方法内部,系统为 Activity 创建所属 Window 对象并设置回调,注意此时并未与 WindowManager 关联,最终 onResume 时才会完成关联:
mWindow = PolicyManager.makeNewWindow(this);
// 当 Window 接收到外界状态改变时回调 Activity 的接口实现
// onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent
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.setUiOptions(info.uiOptions);
}
PolicyManager 实现了 IPolicy 定义的四个方法:
public interface IPolicy {
    public Window makeNewWindow(Context context);
    public LayoutInflater makeNewLayoutInflater(Context context);
    public WindowManagerPolicy makewNewWindowManager();
    public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}
PolicyManager 的实现 Policy,makeNewWindow
public Window makeNewWindow(Context context) {
    return new PhoneWindow(context);
}
Window对象是通过PolicyManager的makeNewWindow方法实现的,由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的对应方法.而我们去追寻Window的具体实现类,会发现它就是PhoneWindow,而Activity中最常用的setContentView方法的具体操作都是在PhoneWindow的相应方法中实现的.至于setContentView的具体流程请参考setContentView源码解析



猜你喜欢

转载自blog.csdn.net/tianmi1988/article/details/50409869