通常未通过特殊定制的 Android 系统,截屏都是经过同时按住音量下键和电源键来截屏。本篇文章就只讨论使用这些特殊按键来进行截屏。
这里我们就要明白事件是在哪里进行分发拦截的。通过源码的分析,我们发现是在PhoneWindowManager.java 中。
PhoneWindowManager#interceptKeyBeforeQueueing()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
if (!mSystemBooted) {
// If we have not yet booted, don't let key events do anything.
return 0;
}
// 省略部分代码......
// Handle special keys.
switch (keyCode) {
......
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
// 按下音量键调用
handleVolumeKey(event, policyFlags);
......
break;
}
......
case KeyEvent.KEYCODE_POWER: {
......
if (down) {
// 按下电源键将调用
interceptPowerKeyDown(event, interactive);
} else {
interceptPowerKeyUp(event, interactive, canceled);
}
break;
}
}
return result;
}
PhoneWindowManager#interceptPowerKeyDown()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
// 省略部分代码......
// Latch power key state to detect screenshot chord.
if (interactive && !mScreenshotChordPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
// power键按下的标志
mScreenshotChordPowerKeyTriggered = true;
// 获取 Power 键的触发时间
mScreenshotChordPowerKeyTime = event.getDownTime();
// 处理屏幕截图事件
interceptScreenshotChord();
// 这个方法应该是消耗、拦截事件的,避免改变音量、铃声等。
interceptRingerToggleChord();
}
// 省略部分代码......
}
interceptScreenshotChord()该方法下面再说,先介绍电源按键、音量按键的处理。
PhoneWindowManager#handleVolumeKey()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
public void handleVolumeKey(KeyEvent event, int policyFlags) {
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final int keyCode = event.getKeyCode();
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
// Any activity on the vol down button stops the ringer toggle shortcut
cancelPendingRingerToggleChordAction();
if (interactive && !mScreenshotChordVolumeDownKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
// Volume键按下的标志
mScreenshotChordVolumeDownKeyTriggered = true;
// 获取 Volume 键的触发时间
mScreenshotChordVolumeDownKeyTime = event.getDownTime();
// 赋值 false 该属性为了防止截屏的时候音量下键生效出现调节音量的 dialog 状态值
mScreenshotChordVolumeDownKeyConsumed = false;
// 防止触发 Power 键长按功能
cancelPendingPowerKeyAction();
//处理屏幕截图事件
interceptScreenshotChord();
// 拦截相关快捷键
interceptAccessibilityShortcutChord();
}
} else {
// 省略部分代码......
}
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
// 省略部分代码......
}
return;
}
PhoneWindowManager#interceptScreenshotChord()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private void interceptScreenshotChord() {
/*
* if 判断参数介绍
* mScreenshotChordEnabled 其值为mContext.getResources().getBoolean(com.android.internal.R.bool.config_enableScreenshotChord);
* mScreenshotChordVolumeDownKeyTriggered 音量下键按下时值为true
* mScreenshotChordPowerKeyTriggered 电源键按下时值为true
* mA11yShortcutChordVolumeUpKeyTriggered 音量上(加)键抬起时为false , 按下时为true
**/
if (mScreenshotChordEnabled
&& mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered
&& !mA11yShortcutChordVolumeUpKeyTriggered) {
// 获取当前时间
final long now = SystemClock.uptimeMillis();
// 当前时间小于 音量下键按下时间 + 150ms
// 当前时间小于 power键按下时间 + 150ms
if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
&& now <= mScreenshotChordPowerKeyTime
+ SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
boolean inLongScreenshot = Settings.System.getIntForUser(mContext.getContentResolver(),
LONGSCREENSHOT_SETTING, 0, UserHandle.USER_CURRENT_OR_SELF) == 1;
if (hasInPowerUtrlSavingMode() || inLongScreenshot) {
return;
}
// 长按音量下键,达到截屏条件,将该事件消费掉。
mScreenshotChordVolumeDownKeyConsumed = true;
// 防止触发 Power 键长按功能
cancelPendingPowerKeyAction();
// 设置截图的类型,TAKE_SCREENSHOT_FULLSCREEN 为全屏
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
// 截图的方式,(例如:按键、三指下滑 等等)
mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD);
//执行 mScreenshotRunnable
mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
}
}
}
继续查看ScreenshotRunnable,此时会一步步向下调用,最终到SystemUI。
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private class ScreenshotRunnable implements Runnable {
private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
private int mScreenshotSource = SCREENSHOT_KEY_OTHER;
public void setScreenshotType(int screenshotType) {
mScreenshotType = screenshotType;
}
public void setScreenshotSource(int screenshotSource) {
mScreenshotSource = screenshotSource;
}
@Override
public void run() {
// 回调到 DisplayPolicy.java
mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource);
}
}
DisplayPolicy#takeScreenshot()
frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
// 请求截取屏幕截图
public void takeScreenshot(int screenshotType, int source) {
if (mScreenshotHelper != null) {
mScreenshotHelper.takeScreenshot(screenshotType,
mStatusBar != null && mStatusBar.isVisibleLw(),
mNavigationBar != null && mNavigationBar.isVisibleLw(),
source, mHandler, null /* completionConsumer */);
}
}
继续往下看ScreenshotHelper#takeScreenshot()
frameworks/base/core/java/com/android/internal/util/ScreenshotHelper.java
// 请求截取屏幕截图
public void takeScreenshot(final int screenshotType, final boolean hasStatus,
final boolean hasNav, int source, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer) {
ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);
takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest,
completionConsumer);
}
//到了 Binder调用环节, 此为客户端, 服务端为SystemUI中的 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();
}
}
}
}
};
// bindService
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 处理后面的操作。
frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
private Handler mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
// 获取客户端传的 Messenger 对象
final Messenger callback = msg.replyTo;
Consumer<Uri> uriConsumer = uri -> {
Message reply = Message.obtain(null, SCREENSHOT_MSG_URI, uri);
try {
/ /Messenger 双向通信,在服务端用远程客户端的 Messenger 对象给客户端发送信息
callback.send(reply);
} catch (RemoteException e) {
}
};
Runnable onComplete = () -> {
Message reply = Message.obtain(null, SCREENSHOT_MSG_PROCESS_COMPLETE);
try {
callback.send(reply);
} catch (RemoteException e) {
}
};
// 判断用户的设备是否为解锁状态
// 如果用户的存储被锁定,我们没有地方存储截图,所以跳过它,而不是显示一个误导性的动画和错误通知。
if (!mUserManager.isUserUnlocked()) {
Log.w(TAG, "Skipping screenshot because storage is locked!");
post(() -> uriConsumer.accept(null));
post(onComplete);
return;
}
ScreenshotHelper.ScreenshotRequest screenshotRequest =
(ScreenshotHelper.ScreenshotRequest) msg.obj;
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()));
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: // 全屏截图
// 我们在PhoneWindowManager传入的type为全屏截图,所以需要执行全屏截图流程
mScreenshot.takeScreenshot(uriConsumer, onComplete);
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: // 区域截图
mScreenshot.takeScreenshot(uriConsumer, onComplete);
break;
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap(
screenshotRequest.getBitmapBundle());
Rect screenBounds = screenshotRequest.getBoundsInScreen();
Insets insets = screenshotRequest.getInsets();
int taskId = screenshotRequest.getTaskId();
int userId = screenshotRequest.getUserId();
ComponentName topComponent = screenshotRequest.getTopComponent();
mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
taskId, userId, topComponent, uriConsumer, onComplete);
break;
default:
Log.d(TAG, "Invalid screenshot option: " + msg.what);
}
}
};
TakeScreenshotService调用GlobalScreenshot.java的takeScreenshot();
GlobalScreenshot#takeScreenshot()
// GlobalScreenshot.java
/**
*截取当前显示的屏幕截图并显示动画。.
*/
private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
// copy the input Rect, since SurfaceControl.screenshot can mutate it
Rect screenRect = new Rect(crop);
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,
Insets.NONE, true);
}
private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
Insets screenInsets, boolean showFlash) {
// 此方法会清除上一次的截图信息--连续截图行为
dismissScreenshot("new screenshot requested", true);
mScreenBitmap = screenshot;
if (mScreenBitmap == null) {
// 如果没有Bitmap则报告错误信息
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
finisher.accept(null);
mOnCompleteRunnable.run();
return;
}
if (!isUserSetupComplete()) {
// 用户设置尚未完成,不应该向用户展示 分享和编辑 , 只显示一个Toast并保存图片
saveScreenshotAndToast(finisher);
return;
}
// Optimizations
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) {
if (mScreenshotIng == false) {//unisoc: Modify for bug1360276
mScreenshotIng = true;
// 如果开启了省电模式,显示 toast,以便有一些视觉提示已截取屏幕截图
PowerManager powerManager =(PowerManager) mContext . getSystemService (Context.POWER_SERVICE);
if (powerManager.isPowerSaveMode()) {
Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
}
mScreenshotHandler.post(() -> {
if (!mScreenshotLayout.isAttachedToWindow()) {
// mScreenshotLayout是截屏的缩略图的父View
// mScreenshotLayout 在 GlobalScreenshot.java 的构造方法中初始化。对应布局文件:global_screenshot.xml
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
}
// 动画相关的View
mScreenshotAnimatedView.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 () {
@Override
void onActionsReady (SavedImageData imageData) {
showUiOnActionsReady(imageData);
mScreenshotIng = false;
}
});
// 播放快门声音以通知我们已截屏
mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
if (mScreenshotPreview.isAttachedToWindow()) {
mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mScreenshotPreview.buildLayer();
}
// 开始执行动画
mScreenshotAnimation.start();
});
});
}
}
/**
* 创建一个新的工作线程并将屏幕截图保存到媒体存储
*/
private void saveScreenshotInWorkerThread(
Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
SaveImageInBackgroundData data = new SaveImageInBackgroundData();
data.image = mScreenBitmap; // native 层返回的 Bitmap
data.finisher = finisher;
data.mActionsReadyListener = actionsReadyListener;
if (mSaveInBgTask != null) {
// just log success/failure for the pre-existing screenshot
// 只需记录预先存在的屏幕截图的成功失败
mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() {
@Override
void onActionsReady(SavedImageData imageData) {
logSuccessOnActionsReady(imageData);
}
});
}
// 截图的一些信息存储在 SaveImageInBackgroundTask 中构建
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data);
mSaveInBgTask.execute();
}
到此截屏流程完毕,可以查看下截图的View的xml文件:global_screenshot.xml
frameworks/base/packages/SystemUI/res/layout/global_screenshot.xml
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/global_screenshot_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android: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 -->
<ImageView
android: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" />
<ImageView
android: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.ScreenshotSelectorView
android:id="@+id/global_screenshot_selector"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:pointerIcon="crosshair"/>
<!-- 此处包含了一个layout, 而缩略图的View就在此layout中,
截屏右上角的关闭缩略图按钮 也在此layout中 -->
<include layout="@layout/global_screenshot_static"/>
</FrameLayout>