Android设计模式(二)- 续:WindowManager

原文地址 http://blog.csdn.net/qq_25806863/article/details/66530372

通过前面的分析可以知道,Android系统中,所有的界面内容显示都是通过Window来实现的,包括Activity,Dialog,Toast等。
先初步分析一下Window,WindowManager,WindowManagerService的关系。

简书地址

获取WindowManager

从Android设计模式(一)-单例模式中后面的内容可以看到,系统在启动的时候就注册了许多服务。其中就有这样的代码:

package android.app;
final class SystemServiceRegistry {
......
    static {
......
        registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
            }});
    }
}

这样就创建了一个WindowManager,而且也能看出来WindowManager的实现类是WindowManagerImpl。
同理,要获得一个WindowManager也很简单了:

WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

Dialog的显示

从头开始分析,先看AlertDialog的构造方法:

AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                createContextThemeWrapper);

        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = AlertController.create(getContext(), this, getWindow());
    }

调用了父类Dialog的构造方法:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
       ......
//获取WindowManager
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//创建一个PhonwWindow
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
//设置回调
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
//设置WindowManager
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mListenersHandler = new ListenersHandler(this);
    }

Window通过调用他的w.setWindowManager(mWindowManager, null, null)将WindowManager和Window联系起来。:

package android.view;
public abstract class Window {
        ......
        public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        ......
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
}

最后一句代码很重要mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);,调用了WindowManagerImpl的createLocalWindowManager方法,这个方法里就一行代码

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

这里重新创建了一个WindowManagerImpl,但是参数比系统注册的时候多了一个构造参数parentWindow。这说明这个WindowManagerImpl是跟一个具体的Window绑定起来的。

package android.view;
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;
......
    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }
......
    public void setDefaultToken(IBinder token) {
        mDefaultToken = token;
    }
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), 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);
    }
    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }
......
    @Override
    public Display getDefaultDisplay() {
        return mContext.getDisplay();
    }
}

从中 很明显的可以看出真正实现一些关键方法如addview 、remoteView等方法的也不是WindowManagerImpl,而是在内部交给了WindowManagerGlobal,由WindowManagerGlobal来真正实现。

然后看一下真正的addView吧:

package android.view;
public final class WindowManagerGlobal {
    private final ArrayList mViews = new ArrayList();
    private final ArrayList mRoots = new ArrayList();
    private final ArrayList mParams =
            new ArrayList();

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
       ......
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            ...... 
//创建ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
//添加到列表中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // 最后执行这个,将View显示在手机屏幕上
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
          ......
        }
    }
}

ViewRootImpl

new ViewRootImpl(view.getContext(), display);

上面的方法中先是new了一个ViewRootImpl,然后调用他的setView来显示布局。
Android中的View都是通过ViewRootImpl来完成绘制的。

书中说这个类继承Handler ,可能是以前是这样吧,我看的源码不是这样的

package android.view;
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
    public ViewRootImpl(Context context, Display display) {
        mContext = context;
//获取IWindowSession,与WindowManagerService建立连接
        mWindowSession = WindowManagerGlobal.getWindowSession();
  ......
//这里保存当前线程
        mThread = Thread.currentThread();
        ......
    }
}

继续追踪WindowManagerGlobal.getWindowSession():

public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
//获取WindowManagerService
                    IWindowManager windowManager = getWindowManagerService();
//与系统的WindowManagerService建立一个IWindowSession
                    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;
        }
    }

public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
//这里返回的是IBinder对象,进行IPC通信
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
               ......
            }
            return sWindowManagerService;
        }
    }

继续看 sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService(“window”));这行:

package android.os;
public final class ServiceManager {
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }
}

所以IWindowManager.Stub.asInterface(
ServiceManager.getService(“window”));得到的是一个IBinder对象。

到这里,在ServiceManager的getService方法中通过getIServiceManager().getService(“window”)获取到一个IBinder,与WMS建立初步连接。
然后通过IWindowManager.Stub.asInterface方法将IBinder转换成IWindowManager对象。
通过这个对象调用openSession打开一个Session,实现通话。

但是,WMS只负责管理View的z-order,也就是管理当前那个View在最上层显示,并不管理绘制。

setView(view, wparams, panelParentView);

既然addView就肯定要把view显示在屏幕上,那么绘制View的任务就在ViewRootImpl的setView方法中。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            ......
//请求绘制View
            requestLayout();
            ......
            int res; /* = WindowManagerImpl.ADD_OKAY; */
            try {
                   ......
//请求WindowManagerService,让WMS实现Window的添加。
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } 
              ......
       }
    }

setView主要做了两件事:
(1)requestLayout();
(2)向WMS请求添加Window;

requestLayout();

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

这里我另外注意一下checkThread()这个方法。

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

是不是看到了熟悉的异常提示?这个方法要判断当前更新UI 的线程是不是创建ViewRootImpl时的线程,只有在创建ViewRootImpl的线程中更新对应的UI才不会报错。所以不能在子线程中更新UI也是这个原因。

回到requestLayout(),查看scheduleTraversals();

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            ......
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           ......
        }
    }

这里的mTraversalRunnable是个Runnable对象,mChoreographer.postCallback最终会通过一个handler把这个任务发送出去:

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

看doTraversal();

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
......
            performTraversals();
......
        }
    }

看performTraversals();
这个方法里将近一千行代码,大致是四个过程:

private void performTraversals(){
  // 1 获取Surface对象,用于图形绘制
  //2 测量整个视图树中各个View的大小,用performMeasure方法
  //3 布局整个视图树,用performLayout方法
  //4 绘制整个视图树,用performDraw方法
}

在第四步中,performDraw方法里会调用ViewRootImpl的draw()方法。

draw()中获取到绘制表面Surface,里面最后调用ViewRootImpl的drawSoftware方法,调用GPU绘图。

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        final Canvas canvas;
        try {
//获取canvas
            canvas = mSurface.lockCanvas(dirty);

            // TODO: Do this in native
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
        }

        try {
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
            try {
                ......
//从这里开始绘制整个视图树,从DecorView开始
                mView.draw(canvas);
            } finally {
                ......
            }
        } finally {
            try {
//解锁canvas,并通知SurfaceFlinger更新这块区域。
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
              ......
            }
        }
        return true;
    }

综上所述,视图树的绘制主要有以下步骤:
(1)判断使用CPU还是GPU绘制
(2)获取绘制表面Surface对象
(3)通过Surface对象获取并锁住绘图对象
(4)从DecorView开始绘制整个视图树
(5)解锁Canvas,并通知SurfaceFlinger更新这块区域。

注意Dialog的使用,系统级弹窗

在创建AlertDialog时,如果传入的Context是ApplicationContext而不是Activity的Context,那么抛出一个异常:

Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
                                                                              at android.view.ViewRootImpl.setView(ViewRootImpl.java:583)
                                                                              at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:313)
                                                                              at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:86)

看出出错的地方是ViewRootImpl中的setView方法中。在这个方法中,调用mWindowSession.addToDisplay发送添加View的请求后会返回一个数值。
然后对这个数值进行判断

int res; 
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
if (res < WindowManagerGlobal.ADD_OKAY) {
                    mAttachInfo.mRootView = null;
                    mAdded = false;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    switch (res) {
                        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");
                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                            // Silently ignore -- we would have just removed it
                            // right away, anyway.
                            return;
                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- another window of type "
                                    + mWindowAttributes.type + " already exists");
                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- permission denied for window type "
                                    + mWindowAttributes.type);
                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified display can not be found");
                        case WindowManagerGlobal.ADD_INVALID_TYPE:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified window type "
                                    + mWindowAttributes.type + " is not valid");
                    }
                    throw new RuntimeException(
                            "Unable to add window -- unknown error code " + res);
                }

通过异常提示可以看出,异常的原因是应用没有token造成的。而token一般只有activity才有,所以要传入activity的context才行。

有一个特殊的对话框不需要传入activity,那就是系统对话框。不传入activity的context也能正常弹出系统对话框。
使用过程:
- 在AndroidManifest.xml中加上权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
  • 然后在调用dialog.show()之前加上下面一句
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);

系统的type有很多都可以用,TYPE_SYSTEM_ERROR,TYPE_SYSTEM_OVERLAY都可以。

  • 有些手机需要对这个应用开启悬浮窗权限才能看见这个应用弹出的系统级对话框。

你可能感兴趣的:(Android设计模式读书笔记,Android开发,Android设计模式)