前言
代码贴的比较多,请耐心看;整个截屏流程是详细的,其他的或许就没分析了。
一般截屏都是电源键+音量减键,而这些按键的处理都是在 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);
}
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓