从一个截屏窗口的创建看Android的窗口机制

从一个Dialog的创建看Android的窗口机制(上篇)从一个Dialog的创建看Android的窗口机制(下篇) 从应用类型窗口的创建分析了Android的窗口机制,这篇打算从系统窗口再来分析下,看看不同类型窗口的添加有何异同,选了一个比较简单的截屏窗口来看,我们都知道Android默认提供了截屏功能,通过POWER+音量下键可以完成截屏,接着从代码角度看看截屏窗口是如何添加的

在SystemUI有个TakeScreenshotService负责截屏

public class TakeScreenshotService extends Service {
	......
	 private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
			 if (mScreenshot == null) {
                mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
            }
            .....
             switch (msg.what) {
                case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
                    mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
                    break;
                    .....
		}
}

核心实现在GlobalScreenshot里面
GlobalScreenshot构造方法中初始化了很多截屏窗口的信息,注意这个context,传的是TakeScreenshotService.this,这是个service类型的context

public GlobalScreenshot(Context context) {
       	......
        //加载截屏窗口的布局global_screenshot
        mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
		//承载截屏图片的ImageView
        mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
       	......
        //设置窗口相关参数,比如宽高MATCH_PARENT,type是TYPE_SCREENSHOT
        mWindowLayoutParams = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
                WindowManager.LayoutParams.TYPE_SCREENSHOT,
                WindowManager.LayoutParams.FLAG_FULLSCREEN
                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
                PixelFormat.TRANSLUCENT);
        mWindowLayoutParams.setTitle("ScreenshotAnimation");
        mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        //通过service类型的context获取mWindowManager
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        //截屏声音
        mCameraSound = new MediaActionSound();
        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
    }

takeScreenshot

private void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
            Rect crop) {
        ....
        //通过native层进行截屏
        mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);

        if (mScreenBitmap == null) {
            notifyScreenshotError(mContext, mNotificationManager,
                    R.string.screenshot_failed_to_capture_text);
            finisher.run();
            return;
        }
        //截屏完成后出现的动画
        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
                statusBarVisible, navBarVisible);
    }

startAnimation

private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
            boolean navBarVisible) {
		......
        //将截屏获取到的Bitmap设置进布局文件的ImageView
        mScreenshotView.setImageBitmap(mScreenBitmap);
        //获取焦点
        mScreenshotLayout.requestFocus();
        //添加截屏窗口
        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
        ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
        ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
                statusBarVisible, navBarVisible);
        mScreenshotAnimation = new AnimatorSet();
        mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
        mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                //截屏动画结束后保存图片
                saveScreenshotInWorkerThread(finisher);
                //并移除窗口
                mWindowManager.removeView(mScreenshotLayout);

                //清空mScreenBitmap
                mScreenBitmap = null;
                mScreenshotView.setImageBitmap(null);
            }
        });
        mScreenshotLayout.post(new Runnable() {
            @Override
            public void run() {
            	//播放截屏声音
                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
                mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                mScreenshotView.buildLayer();
                //开始截屏动画
                mScreenshotAnimation.start();
            }
        });
    }

mWindowManager.addView

我们主要看截屏窗口的添加,在创建WindowManager实际创建WindowManagerImpl时使用的service类型context,是直接new的一个WindowManagerImpl

registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
            }});

这时WindowManagerImpl的parentWindow为空的

public WindowManagerImpl(Context context) {
        this(context, null);
    }
private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }

addView调到了WindowManagerGlobal的同名方法

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        //前面说过,通过parentWindow为空,所以不会通过下面这个方法给截屏窗口
        //设置token
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
           	......
            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.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

到现在为止,截屏这个窗口的token是空的,我们知道每一个窗口都必须有一个token对象,我们继续看它的token在哪里设置的
ViewRootImpl构造方法中创建了W对象和mWindowSession,这两个对象实现了WMS和应用端双向通信

public ViewRootImpl(Context context, Display display) {
	......
	mWindowSession = WindowManagerGlobal.getWindowSession();
	mWindow = new W(this);
	......
}

root.setView

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
		......
		    //绘制流程,measure,layout,draw
			requestLayout();
		......
		    //将Window添加到WMS
			res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);

}

关于WMS.addWindow在从一个Dialog的创建看Android的窗口机制(下篇) 有比较详细的分析过了,我们主要目的是看token如何设置的
WMS.addWindow

public int addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState) {
			//截屏窗口此时的token还是为空的	
			if (token == null) {
				//如果token为空并且是应用类型窗口则添加失败
                if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                //如果token为空并且是输入法类型窗口则添加失败
                if (rootType == TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                //如果token为空并且是语音交互类型窗口则添加失败
                if (rootType == TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                //如果token为空并且是壁纸类型窗口则添加失败
                if (rootType == TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                //如果token为空并且是屏保类型窗口则添加失败
                if (rootType == TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                //如果token为空并且是TYPE_QS_DIALOG类型窗口则添加失败
                if (rootType == TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                 //如果token为空并且是辅助功能覆盖类型窗口则添加失败
                if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                //如果token为空并且是TOAST类型窗口
                if (type == TYPE_TOAST) {
                   	//如果版本大于N MR1,则不能随意添加TOAST了
                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                            parentWindow)) {
                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                }
                //如果token为空则等于窗口的W对象
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                //根据token或者W创建WindowToken
                token = new WindowToken(this, binder, type, false, displayContent,
                        session.mCanAddInternalSystemWindow);

		}	
		.....
		//根据token创建WindowState,对于截屏窗口来说token是W对象
		final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
		//将窗口添加到WindowToken的WindowList
		win.mToken.addWindow(win);
}

截屏窗口的type为TYPE_SCREENSHOT,不属于上面的那些类型,所以最终是通过应用端传递过来的W对象,由系统创建的一个专属token,之后会根据token创建WindowState,再将WindowState添加到WindowList,在WindowState中会计算窗口的Z轴,关于Z轴计算和窗口添加到WindowList在Android窗口Z轴计算及排列规则 已经详细分析过了,对于系统类型窗口来说是通过mBaseLayer从小到大排序,最终的窗口就按这个规则添加到了WindowToken的WindowList中

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值