Android 13 截屏流程
前言
代码贴的比较多,请耐心看;整个截屏流程是详细的,其他的或许就没分析了。
一般截屏都是电源键+音量减键,而这些按键的处理都是在 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.javaprivate 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.javaboolean 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.javaprivate 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.javaprivate 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.javapublic 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.javavoid 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.javaprivate 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.javaprivate 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.DraggableConstraintLayoutxmlns: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.javaprivate 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.javaprivate 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);
}
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓
Android 13 截屏流程相关推荐
- Android实现截屏方式
本文介绍了Android 实现截屏方式整理,分享给大家.希望对大家有帮助 可能的需求: 截自己的屏 截所有的屏 带导航栏截屏 不带导航栏截屏 截屏并编辑选取一部分 自动截取某个空间或者布局 截取长图 ...
- 截屏流程 - 安卓R
截屏类型有三种,分别是全屏截屏.区域截屏和使用提供的图片作为截屏,定义在frameworks/base/core/java/android/view/WindowManager.java中: /*** ...
- Android系统截屏的实现(附代码)
1.背景 写博客快两年了,写了100+的文章,最火的文章也是大家最关注的就是如何实现android系统截屏.其实我们google android_screen_shot就会找到很对办法,但那些都是很多 ...
- android 截长图 方法,Android实现截屏与截长图功能
本文实例为大家分享了Android实现截屏与截长图功能展示的具体代码,供大家参考,具体内容如下 Demo在GitHub的地址:ScreenShoot 在Android开发中,有时候会遇到需要截屏分享到 ...
- android后台截屏实现(2)--screencap源码修改
首先找到screencap类在Android源码中的位置,/442/frameworks/base/cmds/screencap/screencap.cpp. 源码如下: [cpp] view pla ...
- android长截屏代码,android长截屏原理及实现代码
android长截屏原理及实现代码 发布时间:2020-08-31 06:55:16 来源:脚本之家 阅读:158 作者:Android笔记 小米系统自带的长截屏应该很多人都用过,效果不错.当长截屏时 ...
- Android手机截屏
对于android手机截屏,据我所知,现在主要有三种方法. 第一种,通过DDMS. 把手机连接上电脑,运行DDMS,选中你的手机设备,然后点击菜单"设备"->"Sc ...
- android关于截屏,关于android截屏知识的学习
最近要做手机截取当前屏幕的开发,发了大半天时间在网上找了很多资料,终于有了一个大概的头绪和思路,若有问题望指点,谢谢! 目前而言个人了解android有三种截屏方法: 1.android SDK提供的 ...
- android自动截图实现,Android实现截屏功能
原标题:Android实现截屏功能 该方法主要利用SDK提供的view.getDrawingCache()方法,主要步骤如下: 设置view.setDrawingCacheEnabled(true) ...
最新文章
- java使用HttpClient传输json格式的参数
- 中山大学2016年硕士研究生入学考试复试基本分数线
- Rsync + Sersync 实现数据增量同步
- FW : 一只小青蛙的一生(图片连载)
- C语言 模拟实现 strlen strcat strcpy函数
- SVN clean失败解决方法
- C++---vector与list之间的区别
- atitit.js的 字符串内容 转义 js处理html
- 11种刷新按钮的方法
- 亲密关系沟通-【情感勒索】建立良性沟通
- web安全设置(含IIS,php,ASP.NET)与目录权限设置
- 锐起2540无盘教程
- Linux基础——脚本
- 丁磊推荐《你的灯亮着吗》为三大管理必读书
- Linux本地信息收集
- uniapp路线规划
- idea激活到2100年
- SSH超市进销存管理系统
- Android Provision源码分析
- 如何 修改 系统 用户名称 和登陆名称