欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 社会 > Android 10.0 截屏流程

Android 10.0 截屏流程

2024/10/24 22:42:39 来源:https://blog.csdn.net/u010345983/article/details/135170458  浏览:    关键词:Android 10.0 截屏流程

通常未通过特殊定制的 Android 系统,截屏都是经过同时按住音量下键和电源键来截屏。本篇文章就只讨论使用这些特殊按键来进行截屏。
这里我们就要明白事件是在哪里进行分发拦截的。通过源码的分析,我们发现是在PhoneWindowManager.java 中。
PhoneWindowManager#interceptKeyBeforeQueueing()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

    @Overridepublic int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {if (!mSystemBooted) {// If we have not yet booted, don't let key events do anything.return 0;}// 省略部分代码......// Handle special keys.switch (keyCode) {......case KeyEvent.KEYCODE_VOLUME_DOWN:case KeyEvent.KEYCODE_VOLUME_UP:case KeyEvent.KEYCODE_VOLUME_MUTE: {// 按下音量键调用handleVolumeKey(event, policyFlags);......break;}......case KeyEvent.KEYCODE_POWER: {......if (down) {// 按下电源键将调用interceptPowerKeyDown(event, interactive);} else {          interceptPowerKeyUp(event, interactive, canceled);}break;}}return result;}

1、电源键处理

PhoneWindowManager#interceptPowerKeyDown()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

    private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {// 省略部分代码......// Latch power key state to detect screenshot chord.if (interactive && !mScreenshotChordPowerKeyTriggered&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {// power键按下的标志mScreenshotChordPowerKeyTriggered = true;// 获取 Power 键的触发时间mScreenshotChordPowerKeyTime = event.getDownTime();// 处理屏幕截图事件interceptScreenshotChord();// 这个方法应该是消耗、拦截事件的,避免改变音量、铃声等。interceptRingerToggleChord();}// 省略部分代码......}

interceptScreenshotChord()该方法下面再说,先介绍电源按键、音量按键的处理。

2、音量键处理

PhoneWindowManager#handleVolumeKey()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

    public void handleVolumeKey(KeyEvent event, int policyFlags) {final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;final int keyCode = event.getKeyCode();final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {if (down) {// Any activity on the vol down button stops the ringer toggle shortcutcancelPendingRingerToggleChordAction();if (interactive && !mScreenshotChordVolumeDownKeyTriggered&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {// Volume键按下的标志mScreenshotChordVolumeDownKeyTriggered = true;// 获取 Volume 键的触发时间mScreenshotChordVolumeDownKeyTime = event.getDownTime();// 赋值  false 该属性为了防止截屏的时候音量下键生效出现调节音量的 dialog 状态值mScreenshotChordVolumeDownKeyConsumed = false;// 防止触发 Power  键长按功能cancelPendingPowerKeyAction();//处理屏幕截图事件interceptScreenshotChord();// 拦截相关快捷键interceptAccessibilityShortcutChord();}} else {// 省略部分代码......}} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {// 省略部分代码......}return;}

3、截屏事件处理 interceptScreenshotChord()

PhoneWindowManager#interceptScreenshotChord()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

    private void interceptScreenshotChord() {/** if 判断参数介绍* mScreenshotChordEnabled 其值为mContext.getResources().getBoolean(com.android.internal.R.bool.config_enableScreenshotChord);* mScreenshotChordVolumeDownKeyTriggered 音量下键按下时值为true* mScreenshotChordPowerKeyTriggered  电源键按下时值为true* mA11yShortcutChordVolumeUpKeyTriggered 音量上(加)键抬起时为false , 按下时为true**/if (mScreenshotChordEnabled&& mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered&& !mA11yShortcutChordVolumeUpKeyTriggered) {// 获取当前时间final long now = SystemClock.uptimeMillis();// 当前时间小于 音量下键按下时间 + 150ms// 当前时间小于 power键按下时间 + 150msif (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS&& now <= mScreenshotChordPowerKeyTime+ SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {boolean inLongScreenshot = Settings.System.getIntForUser(mContext.getContentResolver(),LONGSCREENSHOT_SETTING, 0, UserHandle.USER_CURRENT_OR_SELF) == 1;if (hasInPowerUtrlSavingMode() || inLongScreenshot) {return;}// 长按音量下键,达到截屏条件,将该事件消费掉。mScreenshotChordVolumeDownKeyConsumed = true;// 防止触发 Power  键长按功能cancelPendingPowerKeyAction();// 设置截图的类型,TAKE_SCREENSHOT_FULLSCREEN 为全屏mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);// 截图的方式,(例如:按键、三指下滑 等等)mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD);//执行 mScreenshotRunnablemHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());}}}

继续查看ScreenshotRunnable,此时会一步步向下调用,最终到SystemUI。
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

    private class ScreenshotRunnable implements Runnable {private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;private int mScreenshotSource = SCREENSHOT_KEY_OTHER;public void setScreenshotType(int screenshotType) {mScreenshotType = screenshotType;}public void setScreenshotSource(int screenshotSource) {mScreenshotSource = screenshotSource;}@Overridepublic void run() {// 回调到 DisplayPolicy.javamDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource);}}

DisplayPolicy#takeScreenshot()
frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java

    // 请求截取屏幕截图public void takeScreenshot(int screenshotType, int source) {if (mScreenshotHelper != null) {mScreenshotHelper.takeScreenshot(screenshotType,mStatusBar != null && mStatusBar.isVisibleLw(),mNavigationBar != null && mNavigationBar.isVisibleLw(),source, mHandler, null /* completionConsumer */);}}

继续往下看ScreenshotHelper#takeScreenshot()
frameworks/base/core/java/com/android/internal/util/ScreenshotHelper.java

    // 请求截取屏幕截图public void takeScreenshot(final int screenshotType, final boolean hasStatus,final boolean hasNav, int source, @NonNull Handler handler,@Nullable Consumer<Uri> completionConsumer) {ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest,completionConsumer);}//到了 Binder调用环节, 此为客户端, 服务端为SystemUI中的 TakeScreenshotServiceprivate void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {synchronized (mScreenshotLock) {final Runnable mScreenshotTimeout = () -> {synchronized (mScreenshotLock) {if (mScreenshotConnection != null) {// 在获取屏幕截图捕获响应之前超时Log.e(TAG, "Timed out before getting screenshot capture response");// 重置连接resetConnection();// 通知截屏错误notifyScreenshotError();}}if (completionConsumer != null) {completionConsumer.accept(null);}};Message msg = Message.obtain(null, screenshotType, screenshotRequest);Handler h = new Handler(handler.getLooper()) {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case SCREENSHOT_MSG_URI:if (completionConsumer != null) {completionConsumer.accept((Uri) msg.obj);}handler.removeCallbacks(mScreenshotTimeout);break;case SCREENSHOT_MSG_PROCESS_COMPLETE:synchronized (mScreenshotLock) {resetConnection();}break;}}};msg.replyTo = new Messenger(h);if (mScreenshotConnection == null || mScreenshotService == null) {// 一个标准的Service连接// config_screenshotServiceComponent == com.android.systemui/com.android.systemui.screenshot.TakeScreenshotServicefinal ComponentName serviceComponent = ComponentName.unflattenFromString(mContext.getResources().getString(com.android.internal.R.string.config_screenshotServiceComponent));final Intent serviceIntent = new Intent();serviceIntent.setComponent(serviceComponent);ServiceConnection conn = new ServiceConnection() {@Override// 当Service连接成功之后public void onServiceConnected(ComponentName name, IBinder service) {synchronized (mScreenshotLock) {if (mScreenshotConnection != this) {return;}mScreenshotService = service;Messenger messenger = new Messenger(mScreenshotService);try {messenger.send(msg);} catch (RemoteException e) {Log.e(TAG, "Couldn't take screenshot: " + e);if (completionConsumer != null) {completionConsumer.accept(null);}}}}@Override// 当Service断开连接时public void onServiceDisconnected(ComponentName name) {synchronized (mScreenshotLock) {if (mScreenshotConnection != null) {resetConnection();// only log an error if we're still within the timeout periodif (handler.hasCallbacks(mScreenshotTimeout)) {handler.removeCallbacks(mScreenshotTimeout);notifyScreenshotError();}}}}};// bindServiceif (mContext.bindServiceAsUser(serviceIntent, conn,Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,UserHandle.CURRENT)) {mScreenshotConnection = conn;handler.postDelayed(mScreenshotTimeout, timeoutMs);}} else {// 如果已经连接则直接发送MessageMessenger messenger = new Messenger(mScreenshotService);try {messenger.send(msg);} catch (RemoteException e) {Log.e(TAG, "Couldn't take screenshot: " + e);if (completionConsumer != null) {completionConsumer.accept(null);}}handler.postDelayed(mScreenshotTimeout, timeoutMs);}}}

客户端通过向服务端发送 message 来将截屏任务交给 service,由 service 处理后面的操作。
frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java

    private Handler mHandler = new Handler(Looper.myLooper()) {@Overridepublic void handleMessage(Message msg) {// 获取客户端传的 Messenger 对象final Messenger callback = msg.replyTo;Consumer<Uri> uriConsumer = uri -> {Message reply = Message.obtain(null, SCREENSHOT_MSG_URI, uri);try {/ /Messenger 双向通信,在服务端用远程客户端的 Messenger 对象给客户端发送信息callback.send(reply);} catch (RemoteException e) {}};Runnable onComplete = () -> {Message reply = Message.obtain(null, SCREENSHOT_MSG_PROCESS_COMPLETE);try {callback.send(reply);} catch (RemoteException e) {}};// 判断用户的设备是否为解锁状态// 如果用户的存储被锁定,我们没有地方存储截图,所以跳过它,而不是显示一个误导性的动画和错误通知。if (!mUserManager.isUserUnlocked()) {Log.w(TAG, "Skipping screenshot because storage is locked!");post(() -> uriConsumer.accept(null));post(onComplete);return;}ScreenshotHelper.ScreenshotRequest screenshotRequest =(ScreenshotHelper.ScreenshotRequest) msg.obj;mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()));switch (msg.what) {case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:  // 全屏截图// 我们在PhoneWindowManager传入的type为全屏截图,所以需要执行全屏截图流程mScreenshot.takeScreenshot(uriConsumer, onComplete);break;case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:  // 区域截图mScreenshot.takeScreenshot(uriConsumer, onComplete);break;case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap(screenshotRequest.getBitmapBundle());Rect screenBounds = screenshotRequest.getBoundsInScreen();Insets insets = screenshotRequest.getInsets();int taskId = screenshotRequest.getTaskId();int userId = screenshotRequest.getUserId();ComponentName topComponent = screenshotRequest.getTopComponent();mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,taskId, userId, topComponent, uriConsumer, onComplete);break;default:Log.d(TAG, "Invalid screenshot option: " + msg.what);}}};

TakeScreenshotService调用GlobalScreenshot.java的takeScreenshot();
GlobalScreenshot#takeScreenshot()

// GlobalScreenshot.java/***截取当前显示的屏幕截图并显示动画。.*/private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {// copy the input Rect, since SurfaceControl.screenshot can mutate itRect screenRect = new Rect(crop);int rot = mDisplay.getRotation();int width = crop.width();int height = crop.height();takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,Insets.NONE, true);}private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,Insets screenInsets, boolean showFlash) {// 此方法会清除上一次的截图信息--连续截图行为dismissScreenshot("new screenshot requested", true);mScreenBitmap = screenshot;if (mScreenBitmap == null) {// 如果没有Bitmap则报告错误信息mNotificationsController.notifyScreenshotError(R.string.screenshot_failed_to_capture_text);finisher.accept(null);mOnCompleteRunnable.run();return;}if (!isUserSetupComplete()) {// 用户设置尚未完成,不应该向用户展示 分享和编辑 , 只显示一个Toast并保存图片saveScreenshotAndToast(finisher);return;}// OptimizationsmScreenBitmap.setHasAlpha(false);mScreenBitmap.prepareToDraw();onConfigChanged(mContext.getResources().getConfiguration());if (mDismissAnimation != null && mDismissAnimation.isRunning()) {mDismissAnimation.cancel();}// 获取焦点setWindowFocusable(true);// 开始截图后动画startAnimation(finisher, screenRect, screenInsets, showFlash);}/*** 截屏后开始动画*/private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,boolean showFlash) {if (mScreenshotIng == false) {//unisoc: Modify for bug1360276mScreenshotIng = true;// 如果开启了省电模式,显示 toast,以便有一些视觉提示已截取屏幕截图PowerManager powerManager =(PowerManager) mContext . getSystemService (Context.POWER_SERVICE);if (powerManager.isPowerSaveMode()) {Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();}mScreenshotHandler.post(() -> {if (!mScreenshotLayout.isAttachedToWindow()) {// mScreenshotLayout是截屏的缩略图的父View// mScreenshotLayout 在 GlobalScreenshot.java 的构造方法中初始化。对应布局文件:global_screenshot.xmlmWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);}// 动画相关的ViewmScreenshotAnimatedView.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets));setAnimatedViewSize(screenRect.width(), screenRect.height());// 显示动画何时开始mScreenshotAnimatedView.setVisibility(View.GONE);//缩略图显示的View,将native层返回的Bitmap加载到此View上mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets));// 使静态预览不可见(消失),以便我们可以在屏幕上查询其位置mScreenshotPreview.setVisibility(View.INVISIBLE);mScreenshotHandler.post(() -> {mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);// 创建动画mScreenshotAnimation =createScreenshotDropInAnimation(screenRect, showFlash);// 保存截图saveScreenshotInWorkerThread(finisher, new ActionsReadyListener () {@Overridevoid onActionsReady (SavedImageData imageData) {showUiOnActionsReady(imageData);mScreenshotIng = false;}});// 播放快门声音以通知我们已截屏mCameraSound.play(MediaActionSound.SHUTTER_CLICK);if (mScreenshotPreview.isAttachedToWindow()) {mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);mScreenshotPreview.buildLayer();}// 开始执行动画mScreenshotAnimation.start();});});}}/*** 创建一个新的工作线程并将屏幕截图保存到媒体存储*/private void saveScreenshotInWorkerThread(Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {SaveImageInBackgroundData data = new SaveImageInBackgroundData();data.image = mScreenBitmap;  // native 层返回的 Bitmapdata.finisher = finisher;data.mActionsReadyListener = actionsReadyListener;if (mSaveInBgTask != null) {// just log success/failure for the pre-existing screenshot// 只需记录预先存在的屏幕截图的成功失败mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() {@Overridevoid onActionsReady(SavedImageData imageData) {logSuccessOnActionsReady(imageData);}});}// 截图的一些信息存储在 SaveImageInBackgroundTask 中构建mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data);mSaveInBgTask.execute();}

到此截屏流程完毕,可以查看下截图的View的xml文件:global_screenshot.xml
frameworks/base/packages/SystemUI/res/layout/global_screenshot.xml

<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/global_screenshot_frame"android:layout_width="match_parent"android:layout_height="match_parent"><ImageViewandroid:id="@+id/global_screenshot_actions_background"android:layout_height="@dimen/screenshot_bg_protection_height"android:layout_width="match_parent"android:layout_gravity="bottom"android:alpha="0.0"android:src="@drawable/screenshot_actions_background_protection"/><!--截屏动画相关的View --><ImageViewandroid:id="@+id/global_screenshot_animated_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="top|start"android:visibility="gone"android:elevation="@dimen/screenshot_preview_elevation"android:background="@drawable/screenshot_rounded_corners" /><ImageViewandroid:id="@+id/global_screenshot_flash"android:layout_width="match_parent"android:layout_height="match_parent"android:visibility="gone"android:elevation="@dimen/screenshot_preview_elevation"android:src="@android:color/white"/><com.android.systemui.screenshot.ScreenshotSelectorViewandroid:id="@+id/global_screenshot_selector"android:layout_width="match_parent"android:layout_height="match_parent"android:visibility="gone"android:pointerIcon="crosshair"/><!-- 此处包含了一个layout, 而缩略图的View就在此layout中,截屏右上角的关闭缩略图按钮 也在此layout中 --><include layout="@layout/global_screenshot_static"/>
</FrameLayout>

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com