Window和WindowManager学习笔记

Window

Window表示一个窗口的概念,同时Window也是一个抽象的概念,Window是一个抽象类,其具体实现是PhoneWindow。

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
    /** Flag for the "options panel" feature.  This is enabled by default. */
    public static final int FEATURE_OPTIONS_PANEL = 0;
    /** Flag for the "no title" feature, turning off the title at the top
     *  of the screen. */
    public static final int FEATURE_NO_TITLE = 1;

    ......
}

/**
 * Android-specific Window.
 * <p>
 * todo: need to pull the generic functionality out into a base class
 * in android.widget.
 *
 * @hide
 */
public class PhoneWindow extends Window implements MenuBuilder.Callback {
   ......

}

在Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的,因此Window是View的直接管理者。

在日常开发中,直接接触Window的机会并不多,但是在某些特殊的时候我们需要在桌面上显示一个悬浮窗的东西,这个时候就需要用到Window来实现了。

private void addFloatBtnToWindow() {
        Button btn = new Button(this);
        WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
                0, 0, PixelFormat.TRANSPARENT);
        wlp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        wlp.x = 100;
        wlp.y = 100;
        wlp.gravity = Gravity.TOP | Gravity.LEFT;
        wlp.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;//系统类Window需在Manifest中配置权限,否则报错
        getWindowManager().addView(btn, wlp);
}

根据触摸的MotionEvent,动态的改变x、y的坐标就可完成随手指移动的效果。

向Window中添加或更新一个View需要的相关参数说明:

常用的Flag

  • WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE     -   表示Window不需要获取焦点,也不需要获取接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的Window。
  • WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL     -  此模式下,系统会将当前Window区域以外的单击事件传递给底层Window,当前Window区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法接收到单击事件。
  • WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED     -  可以让Window显示在锁屏的界面上

Type - Widnow的类型

  • 应用类Window - Activity
  • 子Window - Dialog,子Window不能独立存在,它需要在特定的父Window中,子Window的创建需要父Window的Token
  • 系统Window - Toast、系统状态栏等

Window之z-orderd

每个Window都有对应的z-orderd,层级大的会覆盖层级小的Window上面。

  • 应用类Window:1~99
  • 子Window:1000~1999
  • 系统Window:2000~2999

WindowManager

需要说明的是,在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager,Window是以View的形式存在的,这点从WindowManager的定义也可以看出:

public interface WindowManager extends ViewManager {
    ......
}

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
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);
}

由此可以说明View才是Window存在的实体

Window的内部机制 - Window的添加、删除、更新

                                                                       

Window的内部机制大致如上图所示,其中ViewRootImpl与WMS的通信是IPC过程。

WindowManager是一个接口,其实现类是WindowManagerImpl:

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;

    private IBinder mDefaultToken;

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }


    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }


    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

    ......
}

可以看到WindowManagerImpl并没有直接实现Window的三大操作,而是交给了WindowManagerGlobal来处理了。

WindowManagerGlobal之添加 - addView

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ......

        ViewRootImpl root;
        View panelParentView = null;

        ......
        // 创建ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);
        // 将View添加到列表中
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        // do this last because it fires off messages to start doing things
        try {
            // 完成最终的View的添加
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
          if (index >= 0) {
              removeViewLocked(index, true);
          }
          throw e;
        }
     }
}
  • mViews:所有Window所对应的View
  • mRoots:所有Window所对应的ViewRootImpl
  • mParams:所有Window所对应的布局参数

除此之外,WMG中还有一个mDyingViews用于存储那些正在被删除的View对象,或者说已经调用removeView方法但是删除操作尚未完成的Window对象。

以上这些集合会用于后续的删除和更新操作。

下面详细分析ViewRootImpl的流程

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.
                // 开始第一次scheduleTraversals() - measure/layout/draw
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();// 事件的接收
                }

                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    ......
                    // 核心代码 - mWindowSession.addToDisplay - IPC过程
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                } catch (RemoteException e) {
                   ......
                }

                ......
                if (res < WindowManagerGlobal.ADD_OKAY) {
                    mAttachInfo.mRootView = null;
                    mAdded = false;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    switch (res) {
                        // Window的创建需要Token,而Activity会提供这个Token
                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not valid; is your activity running?");
                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not for an application");
                        case WindowManagerGlobal.ADD_APP_EXITING:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- app for token " + attrs.token
                                    + " is exiting");
                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- window " + mWindow
                                    + " has already been added");
                       ......
                    }
                    throw new RuntimeException(
                            "Unable to add window -- unknown error code " + res);
                }

                ......
            }
        }
    }

上面这些代码,主要干了三件事,如下图所示:

说明:

  • ViewRoot(ViewRootImpl)是一个ViewTree的管理者,而不是ViewTree的根节点。
  • 严格意义上说,ViewTree的根节点只有DecorView。
  • ViewRoot(ViewRootImpl)将DecorView和PhoneWindow(Activity创建的Window实例)“组合”起来。
  • ViewRoot(ViewRootImpl)会接收键盘输入和屏幕触摸事件,并将其回调给Activity
  • ViewRoot(ViewRootImpl)会检查Token是否异常,这也是为什么创建Window需要传递Activity的上下文。

而View的绘制三大流程(measure,layout,draw)均是通过ViewRoot来完成的。

下面看一下上述流程中的IPC过程,核心代码如下:

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

那么,这个WindowSession从和而来呢?

public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();//WMS
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

 可以看到WMS会返回给我们一个Session,而这个Session是一个Binder:

class Session extends IWindowSession.Stub implements IBinder.DeathRecipient{
  ......
}

 通过Session的addToDispaly方法,最终调用WMS的addWindow方法,完成View的添加。

Session 类继承自 IWindowSession.Stub,每一个应用进程都有一个唯一的 Session 对象与 WMS 通信。

WindowManagerGlobal之删除、更新流程与上述类似,不再详述。

View是Android中的视图的呈现方式,但是View不能单独存在,它必须附着在Window这个抽象的概念上面,因此有视图的地方就有Window。哪些地方有视图?Android中提供视图的地方有Activity、Dialog、Toast,除此之外,还有一些依托Window而实现的视图,比如PopupWindow、菜单等。

Activity的Window创建过程

Activity的Window创建时机是在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, ActivityConfigCallback activityConfigCallback) {
        ......

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
       
	......

        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        ......
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        ......
    }

 可以看到,在attach方法中创建了Window的实例:PhoneWindow,并为其设置了CallBack,这个CallBack里面的方法有很多我们看着很眼熟:

也就是Window接收外界的状态改变之后,会回调Activity的方法。

接下来看一下,Activity的视图是如何附属在Window上的。主要通过分析setContentView方法。

 public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

 此处的getWindow()返回的就是在attach方法中生成的PhoneWindow。接着看PhoneWindow的setContentView方法:

 public void setContentView(int layoutResID) {
        
        if (mContentParent == null) {
            installDecor();// 1
        }
	......	
	// ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        mLayoutInflater.inflate(layoutResID, mContentParent);// 2


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

上面这段代码主要完成了三件事,如下所示: 

 到这里为止,DeorView已经被创建并初始化完毕,Activity的布局文件也已经成功添加到了DecorView中,但是这个时候DecorView还没有被WindowManager添加到Window中。这里需要正确理解Window的概念,虽然早在Activity attach的时候PhoneWindow已被创建,但是这个时候DecorView并没有被WindowManager识别,所以这个时候的Window还无法提供具体的功能。

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

 Activity的handleResumeActivity方法中会调用该makeVisible方法,此时DecorView被WindowManager添加到了Window中(最终通过WMS的addWindow方法完成了View的添加 - 具体分析过程见文章前半部分)。

Dialog的Window创建过程

Dialog的创建Window创建与Activity的Window的创建基本一样,几乎没什么区别,如下:

当Dialog被关闭时,会通过WM来移除DecorView。

普通的Dialog有一点比较特殊,就是必须采用Activity的Context,如果采用Application的Context,那么就会报错。为啥?

因为普通Dialog的创建需要应用的Token,而应用的Token一般只有Activity拥有。另外之一:系统Window比较特殊,不需要Token。

Toast的源码分析

Toast属于系统Window,内部视图由两种方式指定:系统默认的样式 或者 通过setView方法来指定。Toast实现最核心的就是TN,显示、隐藏都是TN来完成的。

Toast的主要原理及流程见下图:

开始阅码,从最常见的用法处代码出发:

public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        Toast result = new Toast(context, looper);
        // 加载系统的默认样式
        LayoutInflater inflate = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);

        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }

 可以看到makeText方法主要是为我们生成了Toast对象。

   public void show() {
        if (mNextView == null) {//通过setView设置自定义的布局,该布局View不能为null 
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();// NotificationManagerService - 是Binder
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;// TN也是一个Binder
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

入队(IPC过程)的Toast在NMS中都被包装成了ToastRecord,对于非系统应用,mToastQueue队列最多同时存在50个ToastRecord。这是防止大量的弹出Toast导致其他应用没有机会弹出Toast了。

NMS通过showNextToastLocked方法展示Toast:

 void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
             ......
            try {
                record.callback.show(record.token);
                scheduleDurationReachedLocked(record);
                return;
            } catch (RemoteException e) {}
        }
}

这里的record.callback中的callback就是TN,也就是说这也是一次IPC过程。

TN的show方法处理流程如下:

public void show(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
        }

 mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW: {
                            IBinder token = (IBinder) msg.obj;
                            handleShow(token);
                            break;
                        }
              ......
             }

 public void handleShow(IBinder windowToken) {
           ......
                try {
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
            }
        }

可以看到,TN通过WM的addView将View添加到Window中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值