Android 11 截图流程梳理
Android 原生截屏方式为,power键和音量下键的组合键,那么想要分析截屏流程就从按键的处理流程开始往下进行分析
1. PhoneWindowManager -- Android按键分发
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {case KeyEvent.KEYCODE_VOLUME_DOWN:case KeyEvent.KEYCODE_VOLUME_UP:case KeyEvent.KEYCODE_VOLUME_MUTE: {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) {//该值在后面的判断中会需要mScreenshotChordVolumeDownKeyTriggered = true;mScreenshotChordVolumeDownKeyTime = event.getDownTime();mScreenshotChordVolumeDownKeyConsumed = false;cancelPendingPowerKeyAction();interceptScreenshotChord();interceptAccessibilityShortcutChord();}} else {mScreenshotChordVolumeDownKeyTriggered = false;cancelPendingScreenshotChordAction();cancelPendingAccessibilityShortcutAction();}}break;}case KeyEvent.KEYCODE_POWER: {EventLogTags.writeInterceptPower(KeyEvent.actionToString(event.getAction()),mPowerKeyHandled ? 1 : 0, mPowerKeyPressCounter);// Any activity on the power button stops the accessibility shortcutcancelPendingAccessibilityShortcutAction();result &= ~ACTION_PASS_TO_USER;isWakeKey = false; // wake-up will be handled separatelyif (down) {//power键按下事件处理interceptPowerKeyDown(event, interactive);} else {interceptPowerKeyUp(event, interactive, canceled);}break;}
}
在PhoneWindowManager中处理power键的事件及音量下键的事件, 截图时Power键为按下状态,所以去看power键的按下事件处理
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {......//锁屏电源键状态并且检测屏幕截图if (interactive && !mScreenshotChordPowerKeyTriggered&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {//power键按下的标志mScreenshotChordPowerKeyTriggered = true;//获取 Power 键的触发时间mScreenshotChordPowerKeyTime = event.getDownTime();//处理屏幕截图事件interceptScreenshotChord();interceptRingerToggleChord();}......}private void interceptScreenshotChord() {/*mScreenshotChordEnabled 其值为mContext.getResources().getBoolean(com.android.internal.R.bool.config_enableScreenshotChord);mScreenshotChordVolumeDownKeyTriggered 音量下键按下时值为truemScreenshotChordPowerKeyTriggered 电源键按下时值为truemA11yShortcutChordVolumeUpKeyTriggered 音量上键抬起时为false , 按下时为true*/if (mScreenshotChordEnabled&& mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered&& !mA11yShortcutChordVolumeUpKeyTriggered) {//获取当前时间final long now = SystemClock.uptimeMillis();//当前时间小于 音量下键按下时间 + 150ms//当前时间小于 power键按下时间 + 150ms if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS&& now <= mScreenshotChordPowerKeyTime+ SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {mScreenshotChordVolumeDownKeyConsumed = true;cancelPendingPowerKeyAction();//使用全屏截图--typemScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD);//执行mScreenshotRunnablemHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());}}
}
继续查看ScreenshotRunnable, 此时会一步步向下调用,最终到SystemUI
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() {mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource);}
}//向下调用至DisplayPolicy.java
public void takeScreenshot(int screenshotType, int source) {if (mScreenshotHelper != null) {mScreenshotHelper.takeScreenshot(screenshotType,getStatusBar() != null && getStatusBar().isVisibleLw(),getNavigationBar() != null && getNavigationBar().isVisibleLw(),source, mHandler, null /* completionConsumer */);}
}//继续向下至 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中的 TakeScreenshotService
private 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处理后面的操作
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.takeScreenshotFullscreen(uriConsumer, onComplete);break;case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION://区域截图mScreenshot.takeScreenshotPartial(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的takeScreenshotFullscreen
//GlobalScreenshot.java 目前Google GitHub上的代码和MTK AndroidR 基线代码稍有差异,总体流程一致
void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) {mOnCompleteRunnable = onComplete;mDisplay.getRealMetrics(mDisplayMetrics);takeScreenshotInternal(finisher,new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));}
private void takeScreenshotInternal(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();//此处调用SurfaceControl.screenshot方法进行截屏, 此方法返回一个BitmapsaveScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,Insets.NONE, true);
}
稍有差异,但截图流程一致
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,Insets screenInsets, boolean showFlash) {if (mScreenshotLayout.isAttachedToWindow()) {// if we didn't already dismiss for another reasonif (mDismissAnimation == null || !mDismissAnimation.isRunning()) {mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED);}//此方法会清除上一次的截图信息--连续截图行为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;}mScreenBitmap.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) {mScreenshotHandler.post(() -> {// mScreenshotLayout是截屏的缩略图的父Viewif (!mScreenshotLayout.isAttachedToWindow()) {mWindowManager.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);}});//播放相机拍照时的声音--截图使用此声音mCameraSound.play(MediaActionSound.SHUTTER_CLICK);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 screenshotmSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() {@Overridevoid onActionsReady(SavedImageData imageData) {logSuccessOnActionsReady(imageData);}});}//截图的一些存储信息在SaveImageInBackgroundTask中构建mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data);mSaveInBgTask.execute();
}
到此截图流程完毕,可以查看下截图的View的xml文件
<FrameLayoutxmlns: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中 --><include layout="@layout/global_screenshot_static"/><!-- 截屏右上角的关闭缩略图按钮 --><FrameLayoutandroid:id="@+id/global_screenshot_dismiss_button"android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"android:elevation="7dp"android:visibility="gone"android:contentDescription="@string/screenshot_dismiss_ui_description"><ImageViewandroid:id="@+id/global_screenshot_dismiss_image"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_margin="@dimen/screenshot_dismiss_button_margin"android:src="@drawable/screenshot_cancel"/></FrameLayout>
</FrameLayout>
发布评论