理解Window和WindowManagager

2016-06-12  本文已影响181人  jacky123

Window和WindowManager

为了分析Window的工作机制,我们看下如何用 WindowManager 添加一个 Window。

mFloatingButton = new Button(this);
mFloatingButton.setText("button");
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mLayoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL| LayoutParams.FLAG_NOT_FOCUSABLE| LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ALERT;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mWindowManager.addView(mFloatingButton, mLayoutParams);

以上代码将一个Button添加到屏幕坐标为(100,300)的位置上,要在Manifest.xml中添加权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />。
WindowManager中的 flags、type 这两个参数比较重要。

1. 几个flag属性

2. type表示Window的类型

Window有三种类型,分别为应用Window、子Window、系统Window。Window是分层的,每个Window都有对应的 z-ordered.层级大的覆盖在层级小的上面。

              | 说          明 | 层级(z-order)
 -----------| ---------------| ------------
应用Window | 对应一个Activity| 1-99
子Window   | 常见的Dialog    | 1000-1999
系统Window | Toast,系统状态栏 | 2000-2999

Window的内部机制

Window是一个抽象概念,每一个Window都对应着一个View和ViewRootImpl,Window 和 View 通过ViewRootImpl来建立联系。
下图显示了Activity的Window和Wms的关系

Window的添加过程

Window 的添加需要 WindowManager的addView来实现,WindowManager是一个接口,它的实现是在 WindowManagerImpl类。

public interface WindowManager extends ViewManager{}        //WindowManager 是一个接口
public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

WindowManagerImpl并没有直接实现 Window的三大操作,而是全部交给WindowManagerGlobal来处理,WindowManagerGlobal是典型的单例。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @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);
    }
    
    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
}

WindowManagerGlobal 中

1. 检查参数是否合法
 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添加到列表中

WindowManagerGlobal 内部有几个列表比较重要:

  1. 存储的是所有 window 所对应的 View
    private final ArrayList<View> mViews = new ArrayList<View>();
  2. 所有的 Window 对应的 ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
  3. 所有 Window 所对应的布局参数
        private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();  
root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
3.通过ViewRootImpl来更新界面并完成 Window的添加

这个步骤由ViewRootImpl的setView方法完成。

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

setView内部会通过 requestLayout 来完成异步刷新请求。scheduleTraversals 其实就是View绘制的入口。

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

接着会通过WindowSession 最终来完成 Window 的添加过程。在下面的代码中 mWindowSession 类型是一个 IWindowSession,它是一个Binder对象,真正的实现是 Session,也就是说Window的添加过程是一次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的添加。

final class Session extends IWindowSession.Stub
        implements IBinder.DeathRecipient {
        ...
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }   
        
}

如此一来,Window的添加请求就交给了WindowManagerService处理了。在WindowManagerService内部会为每一个应用保留一个单独的Session。

Window的创建过程

1.Activity的Window创建过程

从源码分析Activity的启动过程
在ActivityThread 的 performLaunchActivity

/**
 * 4.创建 ContextImpl 对象并通过 Activity 的 attach 方法来完成一些数据的初始化
 * 将 appContext 对象 attach 到 Activity 中
 */
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
        + r.activityInfo.name + " with config " + config);
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);

中,在Activity的 attach方法中,系统会创建Activity所属的Window对象并为其设置回调接口。由于Activity实现了Window的Callback接口,因此当Window接受到外界的状态改变时就会回调Activity方法。Callback接口中方法很多,常见的有:onAttachedToWindow、onDetachedFromWindow、onWindowFocusChanged等等。Api23的代码如下:

mWindow = new PhoneWindow(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.setUiOptions(info.uiOptions);
}

Activity视图是如何依附在Window上面?

由于Activity视图由setContentView方法提供,我们只要看setContentView即可。

public void setContentView(View view, ViewGroup.LayoutParams params) {
    getWindow().setContentView(view, params);
    initWindowDecorActionBar();
}

从Activity的setContentView看出,Activity将具体的实现交给了Window处理,而Window的具体的实现是PhoneWindow。来看PhoneWindow的setContentView。

  1. 如果没有DecorView,就创建它。
  2. 将View添加到DecorView的mContentParent中。
  3. 回调Activity的 onContentChanged方法,通知Activity视图改变了。
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        view.setLayoutParams(params);
        final Scene newScene = new Scene(mContentParent, view);
        transitionTo(newScene);
    } else {
        mContentParent.addView(view, params);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

经过了上面的三个步骤,到这里DecorView已经创建并初始化完毕。Activity的布局文件也被成功添加到DecorView的mContentParent中,但这时候DecorView还没被WindowManager正式添加到Window中。在ActivityThread的handleResumeActivity中,首先会调用Activity的onResume方法,接着会调用Activity的makeVisible方法,正是这个makeVisible方法中,DecorView真正完成了添加和显示这两个过程。

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

参考资料及推荐阅读

  1. 本文主要参考了《Android开发艺术探索》
  2. 示例:做一个炫酷的悬浮迷你音乐盒
  3. 为什么Dialog不能用Application的Context
上一篇 下一篇

猜你喜欢

热点阅读