前言

涉及到的源码有

frameworksbaseservicescorejavacomandroidserverpolicyPhoneWindowManager.java

vendormediatekproprietarypackagesappsSystemUIsrccomandroidsystemuiscreenshotTakeScreenshotService.java

vendormediatekproprietarypackagesappsSystemUIsrccomandroidsystemuiscreenshotGlobalScreenshot.java

按键处理都是在 PhoneWindowManager 中,真正截屏的功能实现在 GlobalScreenshot 中, PhoneWindowManager 和 systemui 通过 bind TakeScreenshotService 来实现截屏功能

流程

一般未经过特殊定制的 Android 系统,截屏都是通过同时按住音量下键和电源键来截屏,后来我们使用的一些华为、oppo等厂商的系统你会发现可以通过三指滑动来截屏,下一篇我们会定制此功能,而且截屏显示风格类似 iphone 在左下角显示截屏缩略图,点击可跳转放大查看,3s 无操作后向左自动滑动消失。

好了,现在我们先来理一下系统截屏的流程

system_process D/WindowManager: interceptKeyTi keyCode=25 down=true repeatCount=0 keyguardOn=false mHomePressed=false canceled=false metaState:0

system_process D/WindowManager: interceptKeyTq keycode=25 interactive=true keyguardActive=false policyFlags=22000000 down =false canceled = false isWakeKey=false mVolumeDownKeyTriggered =true result = 1 useHapticFeedback = false isInjected = false

system_process D/WindowManager: interceptKeyTi keyCode=25 down=false repeatCount=0 keyguardOn=false mHomePressed=false canceled=false metaState:0

system_process D/WindowManager: interceptKeyTq keycode=26 interactive=true keyguardActive=false policyFlags=22000000 down =false canceled = false isWakeKey=false mVolumeDownKeyTriggered =false result = 1 useHapticFeedback = false isInjected = false

上面是按下音量下键和电源键的日志,音量下键对应 keyCode=25 ,电源键对应 keyCode=26,来看到 PhoneWindowManager 中的 interceptKeyBeforeQueueing() 方法,在此处处理按键操作

/** {@inheritDoc} */

@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;

}

.....

if (DEBUG_INPUT) {

Log.d(TAG, "interceptKeyTq keycode=" + keyCode

+ " interactive=" + interactive + " keyguardActive=" + keyguardActive

+ " policyFlags=" + Integer.toHexString(policyFlags));

}

.....

// Handle special keys.

switch (keyCode) {

.......

case KeyEvent.KEYCODE_VOLUME_DOWN:

case KeyEvent.KEYCODE_VOLUME_UP:

case KeyEvent.KEYCODE_VOLUME_MUTE: {

if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {

if (down) {

if (interactive && !mScreenshotChordVolumeDownKeyTriggered

&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {

mScreenshotChordVolumeDownKeyTriggered = true;

mScreenshotChordVolumeDownKeyTime = event.getDownTime();

mScreenshotChordVolumeDownKeyConsumed = false;

cancelPendingPowerKeyAction();

interceptScreenshotChord();

interceptAccessibilityShortcutChord();

}

} else {

mScreenshotChordVolumeDownKeyTriggered = false;

cancelPendingScreenshotChordAction();

cancelPendingAccessibilityShortcutAction();

}

}

....

}

看到 KEYCODE_VOLUME_DOWN 中,记录当前按下音量下键的时间 mScreenshotChordVolumeDownKeyTime,cancelPendingPowerKeyAction() 移除电源键长按消息 MSG_POWER_LONG_PRESS,来看下核心方法 interceptScreenshotChord()

// Time to volume and power must be pressed within this interval of each other.

private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150;

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);

mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());

}

}

}

只有当电源键按下时 mScreenshotChordPowerKeyTriggered 才为 true, 当两个按键的按下时间都大于 150 时,延时执行截屏任务 mScreenshotRunnable

private long getScreenshotChordLongPressDelay() {

if (mKeyguardDelegate.isShowing()) {

// Double the time it takes to take a screenshot from the keyguard

return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER *

ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());

}

return ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout();

}

若当前输入框是打开状态,则延时时间为输入框关闭时间加上系统配置的按键超时时间,若当前输入框没有打开则直接是系统配置的按键超时处理时间

紧接着看下 mScreenshotRunnable 都做了什么操作

private class ScreenshotRunnable implements Runnable {

private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;

public void setScreenshotType(int screenshotType) {

mScreenshotType = screenshotType;

}

@Override

public void run() {

takeScreenshot(mScreenshotType);

}

}

private final ScreenshotRunnable mScreenshotRunnable = new ScreenshotRunnable();

可以看到在线程中调用了 takeScreenshot(),默认不设置截屏类型就是全屏,截屏类型有 TAKE_SCREENSHOT_SELECTED_REGION 选定的区域 和 TAKE_SCREENSHOT_FULLSCREEN 全屏两种类型

// Assume this is called from the Handler thread.

private void takeScreenshot(final int screenshotType) {

synchronized (mScreenshotLock) {

if (mScreenshotConnection != null) {

return;

}

final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,

SYSUI_SCREENSHOT_SERVICE);

final Intent serviceIntent = new Intent();

serviceIntent.setComponent(serviceComponent);

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(mHandler.getLooper()) {

@Override

public void handleMessage(Message msg) {

synchronized (mScreenshotLock) {

if (mScreenshotConnection == myConn) {

mContext.unbindService(mScreenshotConnection);

mScreenshotConnection = null;

mHandler.removeCallbacks(mScreenshotTimeout);

}

}

}

};

msg.replyTo = new Messenger(h);

msg.arg1 = msg.arg2 = 0;

if (mStatusBar != null && mStatusBar.isVisibleLw())

msg.arg1 = 1;

if (mNavigationBar != null && mNavigationBar.isVisibleLw())

msg.arg2 = 1;

try {

messenger.send(msg);

} catch (RemoteException e) {

}

}

}

@Override

public void onServiceDisconnected(ComponentName name) {

synchronized (mScreenshotLock) {

if (mScreenshotConnection != null) {

mContext.unbindService(mScreenshotConnection);

mScreenshotConnection = null;

mHandler.removeCallbacks(mScreenshotTimeout);

notifyScreenshotError();

}

}

}

};

if (mContext.bindServiceAsUser(serviceIntent, conn,

Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,

UserHandle.CURRENT)) {

mScreenshotConnection = conn;

mHandler.postDelayed(mScreenshotTimeout, 10000);

}

}

}

takeScreenshot 中通过 bind SystemUI中的 TakeScreenshotService 建立连接,连接成功后通过 Messenger 在两个进程中传递消息通行,有点类似 AIDL,关于 Messenger 的介绍可参考 Android进程间通讯之 messenger Messenger 主要传递当前的 mStatusBar 和 mNavigationBar 是否可见,再来看 TakeScreenshotService 中如何接收处理

public class TakeScreenshotService extends Service {

private static final String TAG = "TakeScreenshotService";

private static GlobalScreenshot mScreenshot;

private Handler mHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

final Messenger callback = msg.replyTo;

Runnable finisher = new Runnable() {

@Override

public void run() {

Message reply = Message.obtain(null, 1);

try {

callback.send(reply);

} catch (RemoteException e) {

}

}

};

// If the storage for this user is locked, we have no place to store

// the screenshot, so skip taking it instead of showing a misleading

// animation and error notification.

if (!getSystemService(UserManager.class).isUserUnlocked()) {

Log.w(TAG, "Skipping screenshot because storage is locked!");

post(finisher);

return;

}

if (mScreenshot == null) {

mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);

}

switch (msg.what) {

case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:

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;

}

}

};

@Override

public IBinder onBind(Intent intent) {

return new Messenger(mHandler).getBinder();

}

@Override

public boolean onUnbind(Intent intent) {

if (mScreenshot != null) mScreenshot.stopScreenshot();

return true;

}

}

可以看到通过 mHandler 接收传递的消息,获取截屏类型和是否要包含状态栏、导航栏,通过创建 GlobalScreenshot 对象(真正干活的来了),调用 takeScreenshot 执行截屏操作,继续跟进

void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {

mDisplay.getRealMetrics(mDisplayMetrics);

takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels,

mDisplayMetrics.heightPixels);

}

/**

* Takes a screenshot of the current display and shows an animation.

*/

void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,

int x, int y, int width, int height) {

// We need to orient the screenshot correctly (and the Surface api seems to take screenshots

// only in the natural orientation of the device :!)

mDisplay.getRealMetrics(mDisplayMetrics);

float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};

float degrees = getDegreesForRotation(mDisplay.getRotation());

boolean requiresRotation = (degrees > 0);

if (requiresRotation) {

// Get the dimensions of the device in its native orientation

mDisplayMatrix.reset();

mDisplayMatrix.preRotate(-degrees);

mDisplayMatrix.mapPoints(dims);

dims[0] = Math.abs(dims[0]);

dims[1] = Math.abs(dims[1]);

}

// Take the screenshot

mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);

if (mScreenBitmap == null) {

notifyScreenshotError(mContext, mNotificationManager,

R.string.screenshot_failed_to_capture_text);

finisher.run();

return;

}

if (requiresRotation) {

// Rotate the screenshot to the current orientation

Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,

mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888,

mScreenBitmap.hasAlpha(), mScreenBitmap.getColorSpace());

Canvas c = new Canvas(ss);

c.translate(ss.getWidth() / 2, ss.getHeight() / 2);

c.rotate(degrees);

c.translate(-dims[0] / 2, -dims[1] / 2);

c.drawBitmap(mScreenBitmap, 0, 0, null);

c.setBitmap(null);

// Recycle the previous bitmap

mScreenBitmap.recycle();

mScreenBitmap = ss;

}

if (width != mDisplayMetrics.widthPixels || height != mDisplayMetrics.heightPixels) {

// Crop the screenshot to selected region

Bitmap cropped = Bitmap.createBitmap(mScreenBitmap, x, y, width, height);

mScreenBitmap.recycle();

mScreenBitmap = cropped;

}

// Optimizations

mScreenBitmap.setHasAlpha(false);

mScreenBitmap.prepareToDraw();

// Start the post-screenshot animation

startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,

statusBarVisible, navBarVisible);

}

获取屏幕的宽高和当前屏幕方向以确定是否需要旋转图片,然后通过 SurfaceControl.screenshot 截屏,好吧,再继续往下看到

public static Bitmap screenshot(int width, int height) {

// TODO: should take the display as a parameter

IBinder displayToken = SurfaceControl.getBuiltInDisplay(

SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);

return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true,

false, Surface.ROTATION_0);

}

这里调用的是 nativeScreenshot 方法,它是一个 native 方法,具体的实现在JNI层,这里就不做过多的介绍了。继续回到我们的 takeScreenshot 方法,在调用了截屏方法 screentshot 之后,判断是否截屏成功:

截屏失败则调用 notifyScreenshotError 发送通知。截屏成功,则调用 startAnimation 播放动画,来分析下动画,后面我们会改这个动画的效果

/**

* Starts the animation after taking the screenshot

*/

private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,

boolean navBarVisible) {

// If power save is on, show a toast so there is some visual indication that a screenshot

// has been taken.

PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);

if (powerManager.isPowerSaveMode()) {

Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();

}

// Add the view for the animation

mScreenshotView.setImageBitmap(mScreenBitmap);

mScreenshotLayout.requestFocus();

// Setup the animation with the screenshot just taken

if (mScreenshotAnimation != null) {

if (mScreenshotAnimation.isStarted()) {

mScreenshotAnimation.end();

}

mScreenshotAnimation.removeAllListeners();

}

mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);

ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();

ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,

statusBarVisible, navBarVisible);

mScreenshotAnimation = new AnimatorSet();

mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);

mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationEnd(Animator animation) {

// Save the screenshot once we have a bit of time now

saveScreenshotInWorkerThread(finisher);

mWindowManager.removeView(mScreenshotLayout);

// Clear any references to the bitmap

mScreenBitmap = null;

mScreenshotView.setImageBitmap(null);

}

});

mScreenshotLayout.post(new Runnable() {

@Override

public void run() {

// Play the shutter sound to notify that we've taken a screenshot

mCameraSound.play(MediaActionSound.SHUTTER_CLICK);

mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);

mScreenshotView.buildLayer();

mScreenshotAnimation.start();

}

});

}

先判断是否是低电量模式,若是发出已抓取屏幕截图的 toast,然后通过 WindowManager 在屏幕中间添加一个装有截屏缩略图的 view,同时创建两个动画组合,通过 mCameraSound 播放截屏咔嚓声并执行动画,动画结束后移除刚刚添加的 view,同时调用 saveScreenshotInWorkerThread 保存图片到媒体库,我们直接来看 SaveImageInBackgroundTask

class SaveImageInBackgroundTask extends AsyncTask {

.....

SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,

NotificationManager nManager) {

......

mNotificationBuilder = new Notification.Builder(context, NotificationChannels.SCREENSHOTS)

.setTicker(r.getString(R.string.screenshot_saving_ticker)

+ (mTickerAddSpace ? " " : ""))

.setContentTitle(r.getString(R.string.screenshot_saving_title))

.setContentText(r.getString(R.string.screenshot_saving_text))

.setSmallIcon(R.drawable.stat_notify_image)

.setWhen(now)

.setShowWhen(true)

.setColor(r.getColor(com.android.internal.R.color.system_notification_accent_color))

.setStyle(mNotificationStyle)

.setPublicVersion(mPublicNotificationBuilder.build());

mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);

SystemUI.overrideNotificationAppName(context, mNotificationBuilder);

mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT,

mNotificationBuilder.build());

}

@Override

protected Void doInBackground(Void... params) {

if (isCancelled()) {

return null;

}

// By default, AsyncTask sets the worker thread to have background thread priority, so bump

// it back up so that we save a little quicker.

Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);

Context context = mParams.context;

Bitmap image = mParams.image;

Resources r = context.getResources();

try {

// Create screenshot directory if it doesn't exist

mScreenshotDir.mkdirs();

// media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds

// for DATE_TAKEN

long dateSeconds = mImageTime / 1000;

// Save

OutputStream out = new FileOutputStream(mImageFilePath);

image.compress(Bitmap.CompressFormat.PNG, 100, out);

out.flush();

out.close();

// Save the screenshot to the MediaStore

ContentValues values = new ContentValues();

ContentResolver resolver = context.getContentResolver();

values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);

values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);

values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);

values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);

values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);

values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);

values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");

values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);

values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);

values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());

Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

// Create a share intent

String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));

String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);

Intent sharingIntent = new Intent(Intent.ACTION_SEND);

sharingIntent.setType("image/png");

sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);

sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);

// Create a share action for the notification. Note, we proxy the call to ShareReceiver

// because RemoteViews currently forces an activity options on the PendingIntent being

// launched, and since we don't want to trigger the share sheet in this case, we will

// start the chooser activitiy directly in ShareReceiver.

PendingIntent shareAction = PendingIntent.getBroadcast(context, 0,

new Intent(context, GlobalScreenshot.ShareReceiver.class)

.putExtra(SHARING_INTENT, sharingIntent),

PendingIntent.FLAG_CANCEL_CURRENT);

Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(

R.drawable.ic_screenshot_share,

r.getString(com.android.internal.R.string.share), shareAction);

mNotificationBuilder.addAction(shareActionBuilder.build());

// Create a delete action for the notification

PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,

new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)

.putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),

PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);

Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(

R.drawable.ic_screenshot_delete,

r.getString(com.android.internal.R.string.delete), deleteAction);

mNotificationBuilder.addAction(deleteActionBuilder.build());

mParams.imageUri = uri;

mParams.image = null;

mParams.errorMsgResId = 0;

} catch (Exception e) {

// IOException/UnsupportedOperationException may be thrown if external storage is not

// mounted

Slog.e(TAG, "unable to save screenshot", e);

mParams.clearImage();

mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;

}

// Recycle the bitmap data

if (image != null) {

image.recycle();

}

return null;

}

@Override

protected void onPostExecute(Void params) {

if (mParams.errorMsgResId != 0) {

// Show a message that we've failed to save the image to disk

GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager,

mParams.errorMsgResId);

} else {

// Show the final notification to indicate screenshot saved

Context context = mParams.context;

Resources r = context.getResources();

// Create the intent to show the screenshot in gallery

Intent launchIntent = new Intent(Intent.ACTION_VIEW);

launchIntent.setDataAndType(mParams.imageUri, "image/png");

launchIntent.setFlags(

Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);

final long now = System.currentTimeMillis();

// Update the text and the icon for the existing notification

mPublicNotificationBuilder

.setContentTitle(r.getString(R.string.screenshot_saved_title))

.setContentText(r.getString(R.string.screenshot_saved_text))

.setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))

.setWhen(now)

.setAutoCancel(true)

.setColor(context.getColor(

com.android.internal.R.color.system_notification_accent_color));

mNotificationBuilder

.setContentTitle(r.getString(R.string.screenshot_saved_title))

.setContentText(r.getString(R.string.screenshot_saved_text))

.setContentIntent(PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))

.setWhen(now)

.setAutoCancel(true)

.setColor(context.getColor(

com.android.internal.R.color.system_notification_accent_color))

.setPublicVersion(mPublicNotificationBuilder.build())

.setFlag(Notification.FLAG_NO_CLEAR, false);

mNotificationManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT,

mNotificationBuilder.build());

}

mParams.finisher.run();

mParams.clearContext();

}

@Override

protected void onCancelled(Void params) {

// If we are cancelled while the task is running in the background, we may get null params.

// The finisher is expected to always be called back, so just use the baked-in params from

// the ctor in any case.

mParams.finisher.run();

mParams.clearImage();

mParams.clearContext();

// Cancel the posted notification

mNotificationManager.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);

}

}

简单说下, SaveImageInBackgroundTask 构造方法中做了大量的准备工作,截屏图片的时间命名格式、截屏通知对象创建,在 doInBackground 中将截屏图片通过 ContentResolver 存储至 MediaStore,再创建两个 PendingIntent,用于分享和删除截屏图片,在 onPostExecute 中发送刚刚创建的 Notification 至 statuBar 显示,到此截屏的流程就结束了。

其它

我们再回到 PhoneWindowManager 中看下,通过上面我们知道要想截屏只需通过如下两行代码即可

mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);

mHandler.post(mScreenshotRunnable);

通过搜索上面的关键代码,我们发现还有另外两处也调用了截屏的代码,一起来看下

@Override

public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {

final boolean keyguardOn = keyguardOn();

final int keyCode = event.getKeyCode();

.....

else if (keyCode == KeyEvent.KEYCODE_S && event.isMetaPressed()

&& event.isCtrlPressed()) {

if (down && repeatCount == 0) {

int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION

: TAKE_SCREENSHOT_FULLSCREEN;

mScreenshotRunnable.setScreenshotType(type);

mHandler.post(mScreenshotRunnable);

return -1;

}

}

....

else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {

if (down && repeatCount == 0) {

mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);

mHandler.post(mScreenshotRunnable);

}

return -1;

}

......

}

也是在拦截按键消息分发之前的方法中,查看 KeyEvent 源码,第一种情况大概网上搜索了下,应该是接外设时,同时按下 S 键 + Meta键 + Ctrl键即可截屏,关于 Meta 介绍可参考Meta键始末 第二种情况是按下截屏键时,对应 keyCode 为 120,可以用 adb shell input keyevent 120 模拟发现也能截屏

/** Key code constant: 'S' key. */

public static final int KEYCODE_S = 47;

/** Key code constant: System Request / Print Screen key. */

public static final int KEYCODE_SYSRQ = 120;

常用按键对应值

这样文章开头提到的三指截屏操作,我们就可以加在 PhoneWindowManager 中,当手势监听获取到三指时,只需调用截屏的两行代码即可

总结

在 PhoneWindowManager 的 dispatchUnhandledKey 方法中处理App无法处理的按键事件,当然也包括音量减少键和电源按键的组合按键

通过一系列的调用启动 TakeScreenshotService 服务,并通过其执行截屏的操作。

具体的截屏代码是在 native 层实现的。

截屏操作时候,若截屏失败则直接发送截屏失败的 notification 通知。

截屏之后,若截屏成功,则先执行截屏的动画,并在动画效果执行完毕之后,发送截屏成功的 notification 的通知。

参考文章

内容来源于网络如有侵权请私信删除

android 8.1 截屏,Android8.1 MTK平台 截屏功能分析相关推荐

  1. Android8.1 MTK平台 截屏功能分析

    前言 涉及到的源码有 frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java vend ...

  2. Android8.1 MTK平台 增加三指截屏(仿IOS左下角显示缩略图点击放大显示)

    效果图 修改后动画如下 系统原动画如下 三指截屏 PhoneWindowManager 同级目录下的 SystemGesturesPointerEventListener.java 主要负责处理界面的 ...

  3. android 改变来电号码,Android8.1 MTK平台 Dialer修改(来电全屏、归属地显示)

    来电默认全屏 默认情况下,来电android是以通知窗口的形式显示,只在屏幕的顶部显示,现在改为全屏显示 修改位置 alps\vendor\mediatek\proprietary\packages\ ...

  4. Android8.1 MTK平台 Dialer修改(来电全屏、归属地显示)

    来电默认全屏 默认情况下,来电android是以通知窗口的形式显示,只在屏幕的顶部显示,现在改为全屏显示 修改位置 alps\vendor\mediatek\proprietary\packages\ ...

  5. Android8.1 MTK平台 Dialer修改(通话常亮、按钮接听)

    通话保持常亮(不息屏) 在网上搜索的 Android保持屏幕常亮的方法,一种是 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SC ...

  6. Android8.1 MTK平台 系统需求定制

    1.清空短信记录接口 vendor\mediatek\proprietary\packages\apps\Mms\src\com\android\mms\transaction\NoneService ...

  7. Android 读取CPU/GPU运行参数(MTK平台)

    一.使用场景 Android运行一段世时间后,系统出现卡顿 二.分析&定位问题 系统卡顿,同时又发现芯片温度很高,怀疑是温度过高导致CPU降频,因此我们要将一段时间内CPU的运行信息打印出来. ...

  8. Android8.1 MTK平台 修改 Volte 视频通话我方视角为矩形

    效果图 修改位置 vendor\mediatek\proprietary\packages\apps\Dialer\java\com\android\incallui\video\impl\Video ...

  9. Android8.1 MTK平台 修改蓝牙默认名称

    8.1 的默认蓝牙名称为 Android Bluedroid 通过搜索你会找到如下文件 device/generic/common/bluetooth/bdroid_buildcfg.h #ifnde ...

最新文章

  1. swift_043(Swift 懒加载(lazy) )
  2. linux shell 判断字符串是否为数字
  3. html 使用符号设置固定链接,让woocommerce使用数字id的固定链接插件
  4. CobaltStrike的使用
  5. C#: switch语句的重构
  6. 趣味编程:C#中Specification模式的实现
  7. 吴恩达机器学习 -- 多变量线性回归
  8. h5的语义化部分_Html5新增的语义化标签(部分)
  9. 即插即用,基于阿里云Ganos快速构建云上开源GIS方案
  10. 大众汽车和鸿蒙,鸿蒙系统下个月即将与大众见面,首发平台并非手机
  11. Android开发实战二之Hello Android实例
  12. 社交媒体广告看不出来?Instagram加标签让你一目了然
  13. 图解十大经典机器学习算法
  14. python读取nc文件并转换成csv_在Python3中读取crystal report.rpt文件并将其转换为.csv或.xlsx...
  15. 英语基础语法 (十三) it 的用法
  16. 常用传输术语OC/STM_SDH/SONET
  17. 户外佩戴哪款耳机好、户外运动耳机推荐
  18. php redis sadd 多个,Redis Sadd 命令
  19. Web前端:React Native Web与React — 比较指南
  20. 食品如何寄国际快递到美国

热门文章

  1. 读“计较,是贫穷的开始”有感
  2. 献给自己技术成长的第三年
  3. 利用css3的3d旋转透视加动画做的一个骰子动态效果
  4. node环境变量配置,npm环境变量配置
  5. 人才篇-如何识人用人
  6. asynchttpclient 超时_韩服正式服3.28版本:新手模式超时空漩涡实装amp;新竞技副本...
  7. 怎么从饭局看自己在领导心中地位?别傻傻喝酒,高人看这6个举动
  8. 服务器开关机文档,服务器开关机
  9. 大型央企集团财务经营分析框架系列(二)
  10. 一边学计算机一边上班累的说说,上班心情说说和句子大全