SoFunction
Updated on 2025-03-01

Android source code analysis screenshot event process

In this article today, we will mainly talk about the screenshot event processing process in the Android system. Students who have used Android phones should know that the screenshot event will be triggered by pressing the volume reduction key and the power button of an Android phone (there will be no consideration for domestic customized machines to make modifications). So how does the screenshot event trigger here? How does the Android system implement screenshot operation after triggering? With these two questions in mind, we start our source code reading process.

We know that the screenshot event here is triggered by our key operation, so here we need to start from the key trigger module of the Android system. Since we operate the volume reduction key and power key on different App pages will trigger the system's screenshot processing, so the key trigger logic here should be the global key processing logic of the Android system.

In the Android system, since each of our Android interface is an Activity, and the display of the interface is implemented through Window objects. Each Window object is actually an instance of PhoneWindow, and each PhoneWindow object has a PhoneWindowManager object. When we perform key operations in the Activity interface, before distributing the key processing operations to the App, we will first call back the dispatchUnhandledKey method in PhoneWindowManager. This method is mainly used to perform operations before the current App processes keys. Let's take a look at the implementation of this method in detail.

/** {@inheritDoc} */
 @Override
 public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
 ...
 KeyEvent fallbackEvent = null;
 if ((() & KeyEvent.FLAG_FALLBACK) == 0) {
 final KeyCharacterMap kcm = ();
 final int keyCode = ();
 final int metaState = ();
 final boolean initialDown = () == KeyEvent.ACTION_DOWN
 && () == 0;

 // Check for fallback actions specified by the key character map.
 final FallbackAction fallbackAction;
 if (initialDown) {
 fallbackAction = (keyCode, metaState);
 } else {
 fallbackAction = (keyCode);
 }

 if (fallbackAction != null) {
 ...
 final int flags = () | KeyEvent.FLAG_FALLBACK;
 fallbackEvent = (
 (), (),
 (), ,
 (), ,
 (), (),
 flags, (), null);

 if (!interceptFallback(win, fallbackEvent, policyFlags)) {
 ();
 fallbackEvent = null;
 }

 if (initialDown) {
 (keyCode, fallbackAction);
 } else if (() == KeyEvent.ACTION_UP) {
 (keyCode);
 ();
 }
 }
 }
 ...
 return fallbackEvent;
 }

Here we focus on the interceptFallback method called in the method body. By calling this method, the operation of processing keys is sent to the method. Let's continue to look at the implementation logic of the method.

private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
 int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
 if ((actions & ACTION_PASS_TO_USER) != 0) {
 long delayMillis = interceptKeyBeforeDispatching(
 win, fallbackEvent, policyFlags);
 if (delayMillis == 0) {
 return true;
 }
 }
 return false;
 }

Then we see that in the interceptFallback method we called the interceptKeyBeforeQueueing method. By reading us, we know that this method mainly implements the processing flow of screenshot keys. So let's continue to look at the processing of the interceptKeyBeforeWueueing method:

@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: {
 if (mUseTvRouting) {
 // On TVs volume keys never go to the foreground app
 result &= ~ACTION_PASS_TO_USER;
 }
 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
 if (down) {
 if (interactive && !mScreenshotChordVolumeDownKeyTriggered
 && (() & KeyEvent.FLAG_FALLBACK) == 0) {
 mScreenshotChordVolumeDownKeyTriggered = true;
 mScreenshotChordVolumeDownKeyTime = ();
 mScreenshotChordVolumeDownKeyConsumed = false;
 cancelPendingPowerKeyAction();
 interceptScreenshotChord();
 }
 } else {
 mScreenshotChordVolumeDownKeyTriggered = false;
 cancelPendingScreenshotChordAction();
 }
 }
 ...

 return result;
 }

It can be found that here we first determine whether the current system has been booted. If it has not been started yet, all key operations will be invalid. If the startup is completed, subsequent operations will be performed. Here we are only focusing on the processing events of the combination of the volume reduction button and the power button. In addition, I would like to say more about the HOME button event, MENU button event, process list button event, etc. of the Android system are all implemented here. We will introduce this content in the future.

Go back to our interceptKeyBeforeQueueing method, when I press the volume reduction key, I go back to the: case KeyEvent.KEYCODE_VOLUME_MUTE branch and execute the corresponding logic, and then determine whether the user has pressed the power key. If the power key is pressed at the same time, execute:

if (interactive && !mScreenshotChordVolumeDownKeyTriggered
 && (() & KeyEvent.FLAG_FALLBACK) == 0) {
 mScreenshotChordVolumeDownKeyTriggered = true;
 mScreenshotChordVolumeDownKeyTime = ();
 mScreenshotChordVolumeDownKeyConsumed = false;
 cancelPendingPowerKeyAction();
 interceptScreenshotChord();
 }

You can find that the interceptScreenshotChrod method here is the beginning of the system preparing to start performing the screenshot operation. Let's continue to look at the implementation of the interceptCreenshotChord method.

private void interceptScreenshotChord() {
 if (mScreenshotChordEnabled
 && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered
 && !mScreenshotChordVolumeUpKeyTriggered) {
 final long now = ();
 if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
 && now <= mScreenshotChordPowerKeyTime
 + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
 mScreenshotChordVolumeDownKeyConsumed = true;
 cancelPendingPowerKeyAction();

 (mScreenshotRunnable, getScreenshotChordLongPressDelay());
 }
 }
 }

In the method body, we will eventually execute a delayed asynchronous message, requesting to perform screenshot operation, and the delay time here. If the current input box is open, the delay time is the input box closing time plus the system-configured key timeout time. If the current input box is not opened, it is directly the system-configured key timeout processing time. You can take a look at the specific implementation of the getScreenshotChordLongPressDelay method.

private long getScreenshotChordLongPressDelay() {
 if (()) {
 // Double the time it takes to take a screenshot from the keyguard
 return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER *
 (mContext).getDeviceGlobalActionKeyTimeout());
 }
 return (mContext).getDeviceGlobalActionKeyTimeout();
 }

Go back to our interceptScreenshotChord method, after sending an asynchronous message, the system will eventually be executed by the run method of the Runnable object we sent. Here, the logic for asynchronous messages can be referenced: Android source code analysis (II)->Async message mechanism

In this way, let's take a look at the implementation of the run method of mScreenshotRunnable of the Runnable type:

private final Runnable mScreenshotRunnable = new Runnable() {
 @Override
 public void run() {
 takeScreenshot();
 }
 };

Well, no other operations are performed in the method body, and the takeScreenshot method is called directly. So let's continue to look at the implementation of the takeScreenshot method.

private void takeScreenshot() {
 synchronized (mScreenshotLock) {
 if (mScreenshotConnection != null) {
 return;
 }
 ComponentName cn = new ComponentName("",
 "");
 Intent intent = new Intent();
 (cn);
 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 = (null, 1);
 final ServiceConnection myConn = this;
 Handler h = new Handler(()) {
 @Override
 public void handleMessage(Message msg) {
 synchronized (mScreenshotLock) {
  if (mScreenshotConnection == myConn) {
  (mScreenshotConnection);
  mScreenshotConnection = null;
  (mScreenshotTimeout);
  }
 }
 }
 };
  = new Messenger(h);
 msg.arg1 = msg.arg2 = 0;
 if (mStatusBar != null && ())
 msg.arg1 = 1;
 if (mNavigationBar != null && ())
 msg.arg2 = 1;
 try {
 (msg);
 } catch (RemoteException e) {
 }
 }
 }
 @Override
 public void onServiceDisconnected(ComponentName name) {}
 };
 if ((
 intent, conn, Context.BIND_AUTO_CREATE, )) {
 mScreenshotConnection = conn;
 (mScreenshotTimeout, 10000);
 }
 }
 }

It can be found that a reflection mechanism is created hereTakeScreenshotServiceThe object is then calledbindServiceAsUser,This createsTakeScreenshotServiceThe service and sent an asynchronous message after the service was created。 OK, let's take a look at the implementation logic of 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) {
 switch () {
 case 1:
 final Messenger callback = ;
 if (mScreenshot == null) {
 mScreenshot = new GlobalScreenshot();
 }
 (new Runnable() {
 @Override public void run() {
 Message reply = (null, 1);
 try {
 (reply);
 } catch (RemoteException e) {
 }
 }
 }, msg.arg1 > 0, msg.arg2 > 0);
 }
 }
 };

 @Override
 public IBinder onBind(Intent intent) {
 return new Messenger(mHandler).getBinder();
 }
}

It can be found that there is a Handler member variable in the definition of TakeScreenshotService. When we start TakeScreenshotService, we send an asynchronous message back, so that the handleMessage method of mHandler will be executed. Then in the handleMessage method, we create a GlobalScreenshow object, and then execute the takeScreenshot method. Okay, let's continue to look at the execution logic of the takeScreenshot method.

/**
 * Takes a screenshot of the current display and shows an animation.
 */
 void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
 // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
 // only in the natural orientation of the device :!)
 (mDisplayMetrics);
 float[] dims = {, };
 float degrees = getDegreesForRotation(());
 boolean requiresRotation = (degrees > 0);
 if (requiresRotation) {
 // Get the dimensions of the device in its native orientation
 ();
 (-degrees);
 (dims);
 dims[0] = (dims[0]);
 dims[1] = (dims[1]);
 }

 // Take the screenshot
 mScreenBitmap = ((int) dims[0], (int) dims[1]);
 if (mScreenBitmap == null) {
 notifyScreenshotError(mContext, mNotificationManager);
 ();
 return;
 }

 if (requiresRotation) {
 // Rotate the screenshot to the current orientation
 Bitmap ss = (,
 , .ARGB_8888);
 Canvas c = new Canvas(ss);
 (() / 2, () / 2);
 (degrees);
 (-dims[0] / 2, -dims[1] / 2);
 (mScreenBitmap, 0, 0, null);
 (null);
 // Recycle the previous bitmap
 ();
 mScreenBitmap = ss;
 }

 // Optimizations
 (false);
 ();

 // Start the post-screenshot animation
 startAnimation(finisher, , ,
 statusBarVisible, navBarVisible);
 }

You can see the last two parameters here: statusBarVisible, whether navBarVisible is visible, and these two parameters are passed in our method:

if (mStatusBar != null && ())
 msg.arg1 = 1;
 if (mNavigationBar != null && ())
 msg.arg2 = 1;

It can be seen that if mStatusBar is visible, the passed statusBarVisible is true, and if mNavigationBar is visible, the passed navBarVisible is true. Then when we take the screenshot, we will determine whether nStatusBar is visible and whether mNavigationBar is visible. If it is visible, we will also take the screenshot. Continue back to our takeScreenshot method and call:

// Take the screenshot
mScreenBitmap = ((int) dims[0], (int) dims[1]);

Method, look at the comments, here is the specific operation of performing the screenshot event. Then I will look at the specific implementation of the method. In addition, it should be noted here that the returned Bitmap object after the screenshot. In fact, children's shoes who are familiar with the Android drawing mechanism should know that all the things that can be displayed in Android can be displayed in memory are Bitmap objects.

public static Bitmap screenshot(int width, int height) {
 // TODO: should take the display as a parameter
 IBinder displayToken = (
 SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
 return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true,
 false, Surface.ROTATION_0);
 }

Well, the native Screenshot method is called here, which is a native method. The specific implementation is in the JNI layer, so I won't introduce it too much here. Continue to return to our takeScreenshot method, and after calling the screenshot method screenshot, determine whether the screenshot is successful:

if (mScreenBitmap == null) {
 notifyScreenshotError(mContext, mNotificationManager);
 ();
 return;
 }

If the bitmap object of the screenshot is empty after the screenshot is taken, it is determined that the screenshot failed, and the notifyScreenshotError method is called to send notification notification of the screenshot failed.

static void notifyScreenshotError(Context context, NotificationManager nManager) {
 Resources r = ();

 // Clear all existing notification, compose the new notification and show it
  b = new (context)
 .setTicker((.screenshot_failed_title))
 .setContentTitle((.screenshot_failed_title))
 .setContentText((.screenshot_failed_text))
 .setSmallIcon(.stat_notify_image_error)
 .setWhen(())
 .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
 .setCategory(Notification.CATEGORY_ERROR)
 .setAutoCancel(true)
 .setColor((
 .system_notification_accent_color));
 Notification n =
 new (b)
 .bigText((.screenshot_failed_text))
 .build();
 (.notification_screenshot, n);
 }

Then continue to look at the takeScreenshot method to determine whether the screenshot image needs to be rotated. If necessary, rotate the image:

if (requiresRotation) {
 // Rotate the screenshot to the current orientation
 Bitmap ss = (,
 , .ARGB_8888);
 Canvas c = new Canvas(ss);
 (() / 2, () / 2);
 (degrees);
 (-dims[0] / 2, -dims[1] / 2);
 (mScreenBitmap, 0, 0, null);
 (null);
 // Recycle the previous bitmap
 ();
 mScreenBitmap = ss;
 }

At the end of the takeScreenshot method, if the screenshot is successful, we call:

// Start the post-screenshot animation
 startAnimation(finisher, , ,
 statusBarVisible, navBarVisible);

Let's start the screenshot animation, okay, let's take a look at the implementation of the animation effect:

/**
 * Starts the animation after taking the screenshot
 */
 private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
 boolean navBarVisible) {
 // Add the view for the animation
 (mScreenBitmap);
 ();

 // Setup the animation with the screenshot just taken
 if (mScreenshotAnimation != null) {
 ();
 ();
 }

 (mScreenshotLayout, mWindowLayoutParams);
 ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
 ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
 statusBarVisible, navBarVisible);
 mScreenshotAnimation = new AnimatorSet();
 (screenshotDropInAnim, screenshotFadeOutAnim);
 (new AnimatorListenerAdapter() {
 @Override
 public void onAnimationEnd(Animator animation) {
 // Save the screenshot once we have a bit of time now
 saveScreenshotInWorkerThread(finisher);
 (mScreenshotLayout);

 // Clear any references to the bitmap
 mScreenBitmap = null;
 (null);
 }
 });
 (new Runnable() {
 @Override
 public void run() {
 // Play the shutter sound to notify that we've taken a screenshot
 (MediaActionSound.SHUTTER_CLICK);

 (View.LAYER_TYPE_HARDWARE, null);
 ();
 ();
 }
 });
 }

Well, after some column operations, we have achieved the animation effect after screenshot. We will not analyze the animation effect for the time being. Let’s take a look at what the animation effect has been done after it? If you still can’t remember, usually we will receive a notification notification after taking a screenshot? This should also be implemented in the onAnimationEnd method of its AnimatorListenerAdapter, that is, after the animation is executed, let's take a look at the implementation of its saveScreenshotInWorkerThread method:

/**
 * Creates a new worker thread and saves the screenshot to the media store.
 */
 private void saveScreenshotInWorkerThread(Runnable finisher) {
 SaveImageInBackgroundData data = new SaveImageInBackgroundData();
  = mContext;
  = mScreenBitmap;
  = mNotificationIconSize;
  = finisher;
  = mPreviewWidth;
  = mPreviewHeight;
 if (mSaveInBgTask != null) {
 (false);
 }
 mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
 .notification_screenshot).execute(data);
 }

Well, the main logic here is to construct a SaveImageInBackgroundTask object. It seems that the notification of the successful screenshot should be implemented here. Let's take a look at the implementation logic of the SaveImageInBackgroundTask construction method:

SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
 NotificationManager nManager, int nId) {
 ...

 // Show the intermediate notification
 mTickerAddSpace = !mTickerAddSpace;
 mNotificationId = nId;
 mNotificationManager = nManager;
 final long now = ();

 mNotificationBuilder = new (context)
 .setTicker((.screenshot_saving_ticker)
 + (mTickerAddSpace ? " " : ""))
 .setContentTitle((.screenshot_saving_title))
 .setContentText((.screenshot_saving_text))
 .setSmallIcon(.stat_notify_image)
 .setWhen(now)
 .setColor((.system_notification_accent_color));

 mNotificationStyle = new ()
 .bigPicture(());
 (mNotificationStyle);

 // For "public" situations we want to show all the same info but
 // omit the actual screenshot image.
 mPublicNotificationBuilder = new (context)
 .setContentTitle((.screenshot_saving_title))
 .setContentText((.screenshot_saving_text))
 .setSmallIcon(.stat_notify_image)
 .setCategory(Notification.CATEGORY_PROGRESS)
 .setWhen(now)
 .setColor((
 .system_notification_accent_color));

 (());

 Notification n = ();
  |= Notification.FLAG_NO_CLEAR;
 (nId, n);

 // On the tablet, the large icon makes the notification appear as if it is clickable (and
 // on small devices, the large icon is not shown) so defer showing the large icon until
 // we compose the final post-save notification below.
 (());
 // But we still don't set it for the expanded view, allowing the smallIcon to show here.
 ((Bitmap) null);
 }

You can find that a NotificationBuilder object is found behind the construction method, and then a screenshot of Notification is sent.

In this way, we received a notification from Notification after taking the screenshot.

Summarize:

Handle key events that the App cannot handle in the dispatchUnhandledKey method of PhoneWindowManager, of course, it also includes a combination of the volume reduction key and the power key

Start the TakeScreenshotService service through a series of calls and perform screenshot operations through it.

The specific screenshot code is implemented in the native layer.

During screenshot operation, if the screenshot fails, the notification notification of the screenshot failed will be sent directly.

After the screenshot is taken, if the screenshot is successful, the screenshot animation will be executed first, and after the animation effect is completed, a notification of the successful notification of the screenshot will be sent.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.