学习笔记:代码贴的比较多,请耐心看;整个截屏流程是详细的,其他的或许就没分析了。
一般截屏都是电源键+音量减键,而这些按键的处理都是在 PhoneWindowManager 中进行的,但在该类中有两个主要处理按键的方法:
- interceptKeyBeforeQueueing():主要处理音量键、电源键(Power键)、耳机键等。
- interceptKeyBeforeDispatching():处理一般性的按键和动作。
参数含义:
- interactive:是否亮屏
- KeyEvent.FLAG_FALLBACK:不被应用处理的按键事件或一些在 键值映射中不被处理的事件(例:轨迹球事件等)。
这里我们直接看 PhoneWindowManager#interceptKeyBeforeQueueing() 方法:
// PhoneWindowManager.java @Override public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { final int keyCode = event.getKeyCode(); final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0 || event.isWakeKey(); if (!mSystemBooted) { // 省略部分代码...... return 0; } // 省略部分代码...... // This could prevent some wrong state in multi-displays environment, // the default display may turned off but interactive is true. final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState()); final boolean interactiveAndOn = interactive && isDefaultDisplayOn; if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { // 这里面有·组合键处理,Android 13 与之前版本不一样 // 在Android 13 有专门的组合键处理,可自行添加规则(即:组合键) handleKeyGesture(event, interactiveAndOn); } // 省略部分代码...... switch (keyCode) { // 省略部分代码...... return result; }
上述代码里说的组合键添加,在 initKeyCombinationRules() 方法中,并在 PhoneWindowManager的 init() 方法中初始化。关于 initKeyCombinationRules() 方法,下文会有讲述。
下面接着看 PhoneWindowManager#handleKeyGesture() 方法:
// PhoneWindowManager.java private void handleKeyGesture(KeyEvent event, boolean interactive) { // 在 if 判断中,调用组合键判断; // 在将键事件发送到窗口之前,检查键事件是否可以被组合键规则拦截。 // 如果键事件可以触发任何活动规则,则返回 true,否则返回 false。 if (mKeyCombinationManager.interceptKey(event, interactive)) { // handled by combo keys manager. mSingleKeyGestureDetector.reset(); return; } if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) { // 触发KEYCODE_POWER 和 ACTION_DOWN事件 mPowerKeyHandled = handleCameraGesture(event, interactive); if (mPowerKeyHandled) { // handled by camera gesture. mSingleKeyGestureDetector.reset(); return; } } mSingleKeyGestureDetector.interceptKey(event, interactive); }
这里我们主要看 mKeyCombinationManager.interceptKey(event, interactive) 方法就行了;
KeyCombinationManager#interceptKey():
// KeyCombinationManager.java boolean interceptKey(KeyEvent event, boolean interactive) { synchronized (mLock) { return interceptKeyLocked(event, interactive); } } private boolean interceptKeyLocked(KeyEvent event, boolean interactive) { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final int keyCode = event.getKeyCode(); final int count = mActiveRules.size(); final long eventTime = event.getEventTime(); if (interactive && down) { // 省略部分代码...... if (mDownTimes.size() == 1) { // 省略部分代码...... } else { // 如果规则已经触发则忽略. if (mTriggeredRule != null) { return true; } // 发送给客户端之前的过度延迟。 forAllActiveRules((rule) -> { if (!rule.shouldInterceptKeys(mDownTimes)) { return false; } Log.v(TAG, "Performing combination rule : " + rule); // 主要是这个方法,会执行 execute() 方法, // 该方法是一个 抽象方法,会在添加组合键规则的地方实现; mHandler.post(rule::execute); mTriggeredRule = rule; return true; }); mActiveRules.clear(); if (mTriggeredRule != null) { mActiveRules.add(mTriggeredRule); return true; } } } else { // 省略部分代码...... } return false; }
这里我们看下组合键添加,及触发回调。
initKeyCombinationRules()
// PhoneWindowManager.java private void initKeyCombinationRules() { mKeyCombinationManager = new KeyCombinationManager(mHandler); final boolean screenshotChordEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_enableScreenshotChord); if (screenshotChordEnabled) { mKeyCombinationManager.addRule( // 截屏组合键的添加 new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) { // 触发组合键后回调 @Override void execute() { mPowerKeyHandled = true; // 发消息准备屏幕截图 interceptScreenshotChord(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay()); } // 取消 @Override void cancel() { cancelPendingScreenshotChordAction(); } }); } // 省略部分代码...... }
上面通过 handle 发了一个消息,将会调用 handleScreenShot() 方法,处理截屏:
PhoneWindowManager# handleScreenShot()
// PhoneWindowManager.java private void handleScreenShot(@WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source) { // 回调到 DisplayPolicy.java mDefaultDisplayPolicy.takeScreenshot(type, source); }
DisplayPolicy#takeScreenshot()
// DisplayPolicy.java // 请求截取屏幕截图 public void takeScreenshot(int screenshotType, int source) { if (mScreenshotHelper != null) { mScreenshotHelper.takeScreenshot(screenshotType, getStatusBar() != null && getStatusBar().isVisible(), getNavigationBar() != null && getNavigationBar().isVisible(), source, mHandler, null /* completionConsumer */); } }
继续往下看 ScreenshotHelper#takeScreenshot()
// 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()) { @Override public 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.TakeScreenshotService final 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 period if (handler.hasCallbacks(mScreenshotTimeout)) { handler.removeCallbacks(mScreenshotTimeout); notifyScreenshotError(); } } } } }; // 绑定服务 TakeScreenshotService; // 绑定成功为true,不成功则发绑定超时消息 if (mContext.bindServiceAsUser(serviceIntent, conn, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, UserHandle.CURRENT)) { mScreenshotConnection = conn; handler.postDelayed(mScreenshotTimeout, timeoutMs); } } else { // 如果已经连接则直接发送Message 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); } } handler.postDelayed(mScreenshotTimeout, timeoutMs); } } }
客户端通过向服务端发送 message 来将截屏任务交给 service,由 service 处理后面的操作。
// TakeScreenshotService.java // 通过 Binder (Messenger) 响应传入消息 @MainThread private boolean handleMessage(Message msg) { // 获取客户端传的 Messenger 对象 final Messenger replyTo = msg.replyTo; // reportUri(replyTo, uri) 方法,Messenger 双向通信, // 在服务端用远程客户端的 Messenger 对象给客户端发送信息 final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri); RequestCallback requestCallback = new RequestCallbackImpl(replyTo); // 如果此用户的存储空间被锁定,我们就没有地方可以存储屏幕截图, // 因此请跳过截屏,而不是显示误导性的动画和错误通知。 if (!mUserManager.isUserUnlocked()) { mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_save_user_locked_text); requestCallback.reportError(); return true; } if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER_ALL)) { mBgExecutor.execute(() -> { // 跳过屏幕截图,因为 IT 管理员已禁用设备上的“+”屏幕截图 String blockedByAdminText = mDevicePolicyManager.getResources().getString( SCREENSHOT_BLOCKED_BY_ADMIN, () -> mContext.getString(R.string.screenshot_blocked_by_admin)); mHandler.post(() -> Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH_SHORT).show()); requestCallback.reportError(); }); return true; } ScreenshotHelper.ScreenshotRequest screenshotRequest = (ScreenshotHelper.ScreenshotRequest) msg.obj; ComponentName topComponent = screenshotRequest.getTopComponent(); mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0, topComponent == null ? "" : topComponent.getPackageName()); switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: // 全屏截图 mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, requestCallback); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: // 截取所选区域 mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, requestCallback); break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: // 截取提供的图像 Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap( screenshotRequest.getBitmapBundle()); Rect screenBounds = screenshotRequest.getBoundsInScreen(); Insets insets = screenshotRequest.getInsets(); int taskId = screenshotRequest.getTaskId(); int userId = screenshotRequest.getUserId(); if (screenshot == null) { // 从屏幕截图消息中获得空位图 mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); requestCallback.reportError(); } else { mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets, taskId, userId, topComponent, uriConsumer, requestCallback); } break; default: // 无效的屏幕截图选项 Log.w(TAG, "Invalid screenshot option: " + msg.what); return false; } return true; }
TakeScreenshotService 调用 ScreenshotController.java 的 takeScreenshotFullscreen();
ScreenshotController#takeScreenshotFullscreen()
// ScreenshotController.java void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher, RequestCallback requestCallback) { // 断言,是主线程则继续执行,不是则抛出异常。 Assert.isMainThread(); mCurrentRequestCallback = requestCallback; DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); takeScreenshotInternal( topComponent, finisher, new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); } // 获取当前显示的屏幕截图并显示动画。 private void takeScreenshotInternal(ComponentName topComponent, Consumer<Uri> finisher, Rect crop) { mScreenshotTakenInPortrait = mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; // 复制输入 Rect,因为 SurfaceControl.screenshot 可以改变它 Rect screenRect = new Rect(crop); // 截图 Bitmap screenshot = captureScreenshot(crop); // 屏幕截图位图为空 if (screenshot == null) { mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); if (mCurrentRequestCallback != null) { mCurrentRequestCallback.reportError(); } return; } // 保存截图 saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true); mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), ClipboardOverlayController.SELF_PERMISSION); }
如何截图的呢?这里我们看 captureScreenshot() 方法;
ScreenshotController#captureScreenshot()
// ScreenshotController.java private Bitmap captureScreenshot(Rect crop) { int width = crop.width(); int height = crop.height(); Bitmap screenshot = null; final Display display = getDefaultDisplay(); final DisplayAddress address = display.getAddress(); if (!(address instanceof DisplayAddress.Physical)) { Log.e(TAG, "Skipping Screenshot - Default display does not have a physical address: " + display); } else { final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address; final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken( physicalAddress.getPhysicalDisplayId()); // 捕获参数 final SurfaceControl.DisplayCaptureArgs captureArgs = new SurfaceControl.DisplayCaptureArgs.Builder(displayToken) .setSourceCrop(crop) .setSize(width, height) .build(); // 屏幕截图硬件缓存 final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = SurfaceControl.captureDisplay(captureArgs); // 截图缓存 screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); } return screenshot; }
上面是捕获图片的过程,里面到底如何捕获的。这点我目前还没弄清。
接着拿到截屏的 Bitmap 后就可以进行图片保存,显示等等一些操作。
接着看 ScreenshotController#saveScreenshot()
// ScreenshotController.java private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, ComponentName topComponent, boolean showFlash) { withWindowAttached(() -> mScreenshotView.announceForAccessibility( mContext.getResources().getString(R.string.screenshot_saving_title))); // 判断缩略图的那个窗口是否已附加上去了。 // ScreenshotView :附件窗口的布局;有:略缩图,编辑按钮、长截屏按钮等一些其他布局。 if (mScreenshotView.isAttachedToWindow()) { // if we didn't already dismiss for another reason if (!mScreenshotView.isDismissing()) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, mPackageName); } if (DEBUG_WINDOW) { Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " + "(dismissing=" + mScreenshotView.isDismissing() + ")"); } // 视图的状态重置,例如:可见性、透明度等。 mScreenshotView.reset(); } // 省略部分代码...... // 在工作线程中保存屏幕截图。 saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); // The window is focusable by default setWindowFocusable(true); // Wait until this window is attached to request because it is // the reference used to locate the target window (below). // 这个方法没看明白。 withWindowAttached(() -> { // 请求滚动捕获,捕获长截屏的。 requestScrollCapture(); mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback( new ViewRootImpl.ActivityConfigCallback() { @Override public void onConfigurationChanged(Configuration overrideConfig, int newDisplayId) { // 省略部分代码...... } @Override public void requestCompatCameraControl(boolean showControl, boolean transformationApplied, ICompatCameraControlCallback callback) { // 省略部分代码...... } }); }); // 创建附加窗口 attachWindow(); // 省略部分代码...... // 设置缩略图,ScreenBitmap 为所截的图片 mScreenshotView.setScreenshot(mScreenBitmap, screenInsets); // 将 ScreenshotView 添加到附加窗口 setContentView(mScreenshotView); // 省略部分代码...... }
截屏布局 screenshot_static.xml:
<com.android.systemui.screenshot.DraggableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/actions_container_background" android:visibility="gone" android:layout_height="0dp" android:layout_width="0dp" android:elevation="4dp" android:background="@drawable/action_chip_container_background" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" app:layout_constraintBottom_toBottomOf="@+id/actions_container" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/actions_container" app:layout_constraintEnd_toEndOf="@+id/actions_container"/> <!-- 缩略图下方的几个按钮 --> <HorizontalScrollView android:id="@+id/actions_container" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" android:layout_marginBottom="4dp" android:paddingEnd="@dimen/overlay_action_container_padding_right" android:paddingVertical="@dimen/overlay_action_container_padding_vertical" android:elevation="4dp" android:scrollbars="none" app:layout_constraintHorizontal_bias="0" app:layout_constraintWidth_percent="1.0" app:layout_constraintWidth_max="wrap" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/screenshot_preview_border" app:layout_constraintEnd_toEndOf="parent"> <LinearLayout android:id="@+id/screenshot_actions" android:layout_width="wrap_content" android:layout_height="wrap_content"> <include layout="@layout/overlay_action_chip" android:id="@+id/screenshot_share_chip"/> <include layout="@layout/overlay_action_chip" android:id="@+id/screenshot_edit_chip"/> <include layout="@layout/overlay_action_chip" android:id="@+id/screenshot_scroll_chip" android:visibility="gone" /> </LinearLayout> </HorizontalScrollView> <!-- 缩略图边框,使用 android:elevation="7dp" 属性,确定哪个覆盖在哪个上面,值大的布局显示在上方 --> <View android:id="@+id/screenshot_preview_border" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="@dimen/overlay_offset_x" android:layout_marginBottom="12dp" android:elevation="7dp" android:alpha="0" android:background="@drawable/overlay_border" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/screenshot_preview_end" app:layout_constraintTop_toTopOf="@id/screenshot_preview_top"/> <!-- constraintlayout 这种布局方式的,一个属性。 --> <androidx.constraintlayout.widget.Barrier android:id="@+id/screenshot_preview_end" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierMargin="@dimen/overlay_border_width" app:barrierDirection="end" app:constraint_referenced_ids="screenshot_preview"/> <androidx.constraintlayout.widget.Barrier android:id="@+id/screenshot_preview_top" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierDirection="top" app:barrierMargin="@dimen/overlay_border_width_neg" app:constraint_referenced_ids="screenshot_preview"/> <!-- 缩略图 --> <ImageView android:id="@+id/screenshot_preview" android:visibility="invisible" android:layout_width="@dimen/overlay_x_scale" android:layout_margin="@dimen/overlay_border_width" android:layout_height="wrap_content" android:layout_gravity="center" android:elevation="7dp" android:contentDescription="@string/screenshot_edit_description" android:scaleType="fitEnd" android:background="@drawable/overlay_preview_background" android:adjustViewBounds="true" android:clickable="true" app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border" app:layout_constraintStart_toStartOf="@id/screenshot_preview_border" app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border" app:layout_constraintTop_toTopOf="@id/screenshot_preview_border"> </ImageView> <!--add by jingtao.guo TFBAAA-2325 添加截图"X"图标--> <FrameLayout android:id="@+id/screenshot_dismiss_button" android:layout_width="@dimen/overlay_dismiss_button_tappable_size" android:layout_height="@dimen/overlay_dismiss_button_tappable_size" android:elevation="10dp" app:layout_constraintStart_toEndOf="@id/screenshot_preview" app:layout_constraintEnd_toEndOf="@id/screenshot_preview" app:layout_constraintTop_toTopOf="@id/screenshot_preview" app:layout_constraintBottom_toTopOf="@id/screenshot_preview" android:contentDescription="@string/screenshot_dismiss_description"> <ImageView android:id="@+id/screenshot_dismiss_image" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/overlay_dismiss_button_margin" android:src="@drawable/overlay_cancel"/> </FrameLayout> <ImageView android:id="@+id/screenshot_scrollable_preview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="matrix" android:visibility="gone" app:layout_constraintStart_toStartOf="@id/screenshot_preview" app:layout_constraintTop_toTopOf="@id/screenshot_preview" android:elevation="7dp"/> </com.android.systemui.screenshot.DraggableConstraintLayout>
至此,全截屏流程就到此结束,saveScreenshotInWorkerThread() 这里不做分析。
下面分析长截屏:
在上述代码中,有讲到 requestScrollCapture(),请求滚动捕获,即长截屏。
ScreenshotController#requestScrollCapture()
// ScreenshotController.java private void requestScrollCapture() { if (!allowLongScreenshots()) { Log.d(TAG, "Long screenshots not supported on this device"); return; } mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken()); if (mLastScrollCaptureRequest != null) { mLastScrollCaptureRequest.cancel(true); } // 请求长截图捕获 final ListenableFuture<ScrollCaptureResponse> future = mScrollCaptureClient.request(DEFAULT_DISPLAY); mLastScrollCaptureRequest = future; mLastScrollCaptureRequest.addListener(() -> onScrollCaptureResponseReady(future), mMainExecutor); }
长截图捕获流程: mScrollCaptureClient.request() 请求捕获→mWindowManagerService.requestScrollCapture()→ViewRootImpl#requestScrollCapture()→ViewRootImpl#handleScrollCaptureRequest() 处理滚动捕获请求,拿到捕获目标。→ViewGroup#dispatchScrollCaptureSearch() 通过检查此视图,处理滚动捕获搜索请求,然后检查每个子视图。该隐藏的隐藏,设置视图偏移等等。
走完上述流程,才会继续往下执行;
接着看 ScreenshotController#onScrollCaptureResponseReady()
// ScreenshotController.java private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) { try { // 上次滚动捕获响应 if (mLastScrollCaptureResponse != null) { mLastScrollCaptureResponse.close(); mLastScrollCaptureResponse = null; } // 长截屏响应,这和 网络请求中,响应头类似, response 里有很多的数据。 if (responseFuture != null) { if (responseFuture.isCancelled()) { return; } // 将本次滚动捕获响应 设置成 滚动捕获响应 mLastScrollCaptureResponse = responseFuture.get(); } else { Log.e(TAG, "onScrollCaptureResponseReady responseFuture is null!"); } if (mLastScrollCaptureResponse != null && !mLastScrollCaptureResponse.isConnected()) { // No connection means that the target window wasn't found // or that it cannot support scroll capture. Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " [" + mLastScrollCaptureResponse.getWindowTitle() + "]"); return; } Log.d(TAG, "ScrollCapture: connected to window [" + mLastScrollCaptureResponse.getWindowTitle() + "]"); // 滚动捕获响应,这和 网络请求中,响应头类似, response 里有很多的数据。 final ScrollCaptureResponse response = mLastScrollCaptureResponse; // 截取更多内容按钮,即长截屏按钮;这里确实奇怪:还没点击长截屏,有些数据就已经捕获好了,例如:显示范围内的窗口边界、窗口空间中滚动内容的边界、当前窗口标题等等数据。 mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> { DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); // 新的位图 Bitmap 。 Bitmap newScreenshot = captureScreenshot( new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); // 设置视图,这里只是一个缩略图,和普通截图一样大; mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait); // delay starting scroll capture to make sure the scrim is up before the app moves // 捕获视图。长截图会在 LongScreenshotActivity 显示。 mScreenshotView.post(() -> runBatchScrollCapture(response)); }); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "requestScrollCapture failed", e); } }
private void runBatchScrollCapture(ScrollCaptureResponse response) { // Clear the reference to prevent close() in dismissScreenshot mLastScrollCaptureResponse = null; if (mLongScreenshotFuture != null) { mLongScreenshotFuture.cancel(true); } // 通过 response 得到 LongScreen 的视图。 mLongScreenshotFuture = mScrollCaptureController.run(response); mLongScreenshotFuture.addListener(() -> { ScrollCaptureController.LongScreenshot longScreenshot; try { // 获取 longScreenshot 。 longScreenshot = mLongScreenshotFuture.get(); } catch (CancellationException e) { Log.e(TAG, "Long screenshot cancelled"); return; } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "Exception", e); mScreenshotView.restoreNonScrollingUi(); return; } if (longScreenshot.getHeight() == 0) { mScreenshotView.restoreNonScrollingUi(); return; } // 相当于数据保存,把截图数据设置进去,但这里不是存储在本地。 mLongScreenshotHolder.setLongScreenshot(longScreenshot); mLongScreenshotHolder.setTransitionDestinationCallback( (transitionDestination, onTransitionEnd) -> mScreenshotView.startLongScreenshotTransition( transitionDestination, onTransitionEnd, longScreenshot)); final Intent intent = new Intent(mContext, LongScreenshotActivity.class); intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); // 跳转到编辑界面,也可以叫预览界面吧。 mContext.startActivity(intent, ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle()); RemoteAnimationAdapter runner = new RemoteAnimationAdapter( SCREENSHOT_REMOTE_RUNNER, 0, 0); try { WindowManagerGlobal.getWindowManagerService() .overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY); } catch (Exception e) { Log.e(TAG, "Error overriding screenshot app transition", e); } }, mMainExecutor); }
勉勉强强长截图也完成了吧,自己也还是有点不太清楚,完全没有任何资料可以参考。