安卓10源码学习开发定制(32)系统截屏流程之Java层分析
一、前言
安卓手机中可以通过同时按下"音量-"键和"电源键"实现屏幕截屏,某些手机也可以通过屏幕下拉快捷菜单中的"截屏"功能实现截屏。本文章分析测试的是lineageOS 17.1的系统通过按下"音量-"键和"电源键"实现屏幕截屏的流程分析。
二、PhoneWindowManager简单说明
在安卓系统中常见的按键比如HOME、电源键、返回键、音量键等处理是在PhoneWindowManager类中首先进行处理。所以如果需要对这些按键特殊逻辑处理就需要修改PhoneWindowManager类源码。本篇中分析的屏幕截图涉及到电源键和音量键操作,所以以下我们从该类开始分析截屏的流程。
三、流程分析
1.PhoneWindowManager中的流程分析
该类源码路径位于:
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
该类中处理按键的函数为interceptKeyBeforeQueueing,该方法中关键调用如下:
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
...
// Handle special keys.
switch (keyCode) {
case KeyEvent.KEYCODE_BACK: {
...
}
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
...
//按下音量-键
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
// Any activity on the vol down button stops the ringer toggle shortcut
cancelPendingRingerToggleChordAction();
if (interactive && !mScreenshotChordVolumeDownKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
...
//interceptScreenshotChord处理判断屏幕截图
interceptScreenshotChord();
interceptAccessibilityShortcutChord();
}
} else {
mScreenshotChordVolumeDownKeyTriggered = false;
cancelPendingScreenshotChordAction();
cancelPendingAccessibilityShortcutAction();
if (mClickPartialScreenshot && mScreenshotChordVolumeDownKeyConsumed) {
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_SELECTED_REGION);
mHandler.post(mScreenshotRunnable);
}
}
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
...
}
...
//处理按下电源键的情况
case KeyEvent.KEYCODE_POWER: {
...
//电源键按下
if (down) {
interceptPowerKeyDown(event, interactive);
} else {
interceptPowerKeyUp(event, interactive, canceled);
}
break;
}
...
}
在以上分析中按下音量键-执行了方法interceptScreenshotChord。按下电源键执行interceptPowerKeyDown方法。interceptPowerKeyDown方法逻辑如下:
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
...
// Latch power key state to detect screenshot chord.
//以下调用interceptScreenshotChord判断是否截屏
if (interactive && !mScreenshotChordPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mScreenshotChordPowerKeyTriggered = true;
mScreenshotChordPowerKeyTime = event.getDownTime();
interceptScreenshotChord();
interceptRingerToggleChord();
}
...
}
由上分析按下电源键、音量-键都需要走interceptScreenshotChord方法进行判断是否截屏。interceptScreenshotChord方法逻辑如下:
private void interceptScreenshotChord() {
//判断当前是否同时按下了电源键和音量减键
if (mScreenshotChordEnabled
&& mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered
&& !mA11yShortcutChordVolumeUpKeyTriggered) {
final long now = SystemClock.uptimeMillis();
if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
&& now <= mScreenshotChordPowerKeyTime
+ SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
mScreenshotChordVolumeDownKeyConsumed = true;
cancelPendingPowerKeyAction();
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
//使用handler分发延时处理截屏任务
//截屏逻辑转到mScreenshotRunnable中
mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
}
}
}
截屏逻辑走到了mScreenshotRunnable中,该变量定义如下:
private final ScreenshotRunnable mScreenshotRunnable = new ScreenshotRunnable();
mScreenshotRunnable为ScreenshotRunnable类,该类定义在当前文件中,定义如下:
private class ScreenshotRunnable implements Runnable {
private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
public void setScreenshotType(int screenshotType) {
mScreenshotType = screenshotType;
}
@Override
public void run() {
//使用mDefaultDisplayPolicy类截屏
mDefaultDisplayPolicy.takeScreenshot(mScreenshotType);
}
}
以上流程走到了mDefaultDisplayPolicy.takeScreenshot。mDefaultDisplayPolicy的定义如下:
DisplayPolicy mDefaultDisplayPolicy;
以下接着分析DisplayPolicyle类的实现情况。
2. DisplayPolicy中的流程分析
DisplayPolicy类的源文件如下:
frameworks\base\services\core\java\com\android\server\wm\DisplayPolicy.java
该类中takeScreenshot方法实现如下:
public void takeScreenshot(int screenshotType) {
if (mScreenshotHelper != null) {
//使用了mScreenshotHelper执行屏幕j截图
mScreenshotHelper.takeScreenshot(screenshotType,
mStatusBar != null && mStatusBar.isVisibleLw(),
mNavigationBar != null && mNavigationBar.isVisibleLw(),
mHandler, null /* completionConsumer */);
}
}
以上方法中执行了mScreenshotHelper 对象的takeScreenshot方法。mScreenshotHelper 变量定义如下:
private final ScreenshotHelper mScreenshotHelper;
根据变量定义的类型逻辑流程跳转到ScreenshotHelper类中。
3.ScreenshotHelper类中的流程分析
ScreenshotHelper类源文件如下:
frameworks\base\services\core\java\com\android\server\wm\ScreenshotHelper.java
该类中takeScreenshot方法的实现如下:
public void takeScreenshot(final int screenshotType, final boolean hasStatus,
final boolean hasNav, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer)
takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler,
completionConsumer);
}
public void takeScreenshot(final int screenshotType, final boolean hasStatus,
final boolean hasNav, long timeoutMs, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer) {
...
//此处创建了需要连接的service
final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
SYSUI_SCREENSHOT_SERVICE);
final Intent serviceIntent = new Intent();
...
serviceIntent.setComponent(serviceComponent);
//连接service的回调方法
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != this) {
return;
}
Messenger messenger = new Messenger(service);
Message msg = Message.obtain(null, screenshotType);
final ServiceConnection myConn = this;
Handler h = new Handler(handler.getLooper()) {
@Override
public void handleMessage(Message msg) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection == myConn) {
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection = null;
handler.removeCallbacks(mScreenshotTimeout);
}
}
if (completionConsumer != null) {
completionConsumer.accept((Uri) msg.obj);
}
}
};
msg.replyTo = new Messenger(h);
msg.arg1 = hasStatus ? 1 : 0;
msg.arg2 = hasNav ? 1 : 0;
try {
messenger.send(msg);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't take screenshot: " + e);
if (completionConsumer != null) {
completionConsumer.accept(null);
}
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection = null;
handler.removeCallbacks(mScreenshotTimeout);
notifyScreenshotError();
}
}
}
};
//通过bindServiceAsUser方法进行binder进程间通信启动service
if (mContext.bindServiceAsUser(serviceIntent, conn,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
UserHandle.CURRENT)) {
mScreenshotConnection = conn;
...
}
}
}
以上代码中构造了service。如下抽取了关键的代码:
//包名
private static final String SYSUI_PACKAGE = "com.android.systemui";
//需要启动的service类名
private static final String SYSUI_SCREENSHOT_SERVICE =
"com.android.systemui.screenshot.TakeScreenshotService";
//通过ComponentName构造启动的service组件
final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
SYSUI_SCREENSHOT_SERVICE);
经过以上分析截屏逻辑转到了“com.android.systemui.screenshot.TakeScreenshotService”类中。该类为SystemUI程序的组件。
4.SystemUI中TakeScreenshotService流程分析
TakeScreenshotService类源码路径如下:
frameworks\base\packages\SystemUI\src\com\android\systemui\screenshot\TakeScreenshotService.java
该类中处理截图逻辑代码如下:
//定义mScreenshot变量
private static GlobalScreenshot mScreenshot;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
...
if (mScreenshot == null) {
//创建GlobalScreenshot对象
mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
}
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
//全屏j截图
mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
//部分截图走的逻辑
mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
break;
default:
Log.d(TAG, "Invalid screenshot option: " + msg.what);
}
}
};
以上分析可知截屏跳转到了GlobalScreenshot中的takeScreenshot方法。
5.GlobalScreenshot中的截屏流程分析
GlobalScreenShot类源文件如下:
frameworks\base\packages\SystemUI\src\com\android\systemui\screenshot\GlobalScreenShot.java
该类中takeScreenshot方法实现如下:
private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
boolean navBarVisible, Rect crop) {
...
// Take the screenshot
//调用了SurfaceControl类的screenshot实现截屏
mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
if (mScreenBitmap == null) {
notifyScreenshotError(mContext, mNotificationManager,
R.string.screenshot_failed_to_capture_text);
finisher.accept(null);
return;
}
...
}
以上关键调用变成了SurfaceControl.screenshot。
6.SurfaceControl类中的截屏流程
SurfaceControl类源码路径位于:
frameworks\base\core\java\android\view\SurfaceControl.java
类中screenshot方法的逻辑实现如下:
//调用1
public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) {
return screenshot(sourceCrop, width, height, false, rotation);
}
//调用2
public static Bitmap screenshot(Rect sourceCrop, int width, int height,
boolean useIdentityTransform, int rotation) {
...
//调用screenshotToBuffer
final ScreenshotGraphicBuffer buffer = screenshotToBuffer(displayToken, sourceCrop, width,
height, useIdentityTransform, rotation);
if (buffer == null) {
Log.w(TAG, "Failed to take screenshot");
return null;
}
return Bitmap.wrapHardwareBuffer(buffer.getGraphicBuffer(), buffer.getColorSpace());
}
//调用3
public static ScreenshotGraphicBuffer screenshotToBuffer(IBinder display, Rect sourceCrop,
int width, int height, boolean useIdentityTransform, int rotation) {
...
//调用nativeScreenshot
return nativeScreenshot(display, sourceCrop, width, height, useIdentityTransform, rotation,
false /* captureSecureLayers */);
}
//调用4 该方法为native方法,具体实现在jni层去了
private static native ScreenshotGraphicBuffer nativeScreenshot(IBinder displayToken,
Rect sourceCrop, int width, int height, boolean useIdentityTransform, int rotation,
boolean captureSecureLayers );
SurfaceControl中最终截屏调用调用了nativeScreenshot本地方法。该方法的实现在jni层。
总结
1. 通过以上对Java层截屏分析,App有访问屏幕数据权限的情况下,Java层想截屏可以通过调用如下接口:
SurfaceControl.screenshot(crop, width, height, rot);
由于SurfaceControl为非公开类,所以App中需要通过反射调用。
如果你对安卓相关的开发学习感兴趣:
可加作者的QQ群(1017017661),本群专注安卓方面的技术,欢迎加群技术交流。
扫描下方二维码关注公众号