本文是在MTK4.4的系统源码基础上进行分析的,如果和你的系统代码有出入,请以自己的系统代码为主。但基本的流程应该还是一样的。下面是分析4.4系统的截图实现。即power键+volume-键实现的截图。系统是在PhoneWindowManager类中实现监听按键事件,并响应相应的按键事件去实现截图。
./base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
首先,在PhoneWindowManager.java中看下截图的实现的。
Android 实现截图的方法
// Assume this is called from the Handler thread.
private void takeScreenshot() {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
return;
}
//初始化要绑定的服务,从这里可以看出要绑定的服务是SystemUI里的TakeScreenshotService
ComponentName cn = new ComponentName("com.android.systemui",
"com.android.systemui.screenshot.TakeScreenshotService");
Intent intent = new Intent();
intent.setComponent(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 = Message.obtain(null, 1);
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) {}
};
//绑定Service
if (mContext.bindServiceAsUser(
intent, conn, Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
mScreenshotConnection = conn;
//设置超时机制,若超时就解除绑定
mHandler.postDelayed(mScreenshotTimeout, 10000);
}
}
}
final Runnable mScreenshotTimeout = new Runnable() {
@Override public void run() {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection = null;
}
}
}
};
然后,再来看takeScreenshot是在哪里被调用的。
//takeScreenshot()放在Runnable里面
private final Runnable mScreenshotRunnable = new Runnable() {
@Override
public void run() {
takeScreenshot();
}
};
系统把takeScreenshot方法放在了mScreenshotRunnable里面了,通过源码知道mScreenshotRunnable在两个地方被调用了,一是当KEYCODE_SYSRQ键按下的时候开始截图,二是当KEYCODE_POWER和KEYCODE_VOLUME_DOWN同时按下时开始截图,接下来我们看下mScreenshotRunnable实际被被调用的代码实现。
方法一:直接按下KEYCODE_SYSRQ实现截图
/** {@inheritDoc} */
@Override
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
......
else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {
if (down && repeatCount == 0) {
//监听按键KEYCODE_SYSRQ按下,实现系统截图
mHandler.post(mScreenshotRunnable);
}
return -1;
}
......
}
方法二:KEYCODE_POWER和KEYCODE_VOLUME_DOWN同时按下实现截图
private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150;
//interceptScreenshotChord通过mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay())实现截图
private void interceptScreenshotChord() {
//mScreenshotChordEnabled系统允许截屏,
//mVolumeDownKeyTriggeredyin音量-键按下
//mPowerKeyTriggered电源键按下
//!mVolumeUpKeyTriggered音量+键没有按下
if (mScreenshotChordEnabled
&& mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {
final long now = SystemClock.uptimeMillis();
//这个判断就是保证音量-键和电源键同时按下的效果,即他们间隔时间不能超过150毫秒
if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
&& now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
mVolumeDownKeyConsumedByScreenshotChord = true;
cancelPendingPowerKeyAction();
//延时调用mScreenshotRunnable
mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
}
}
}
/** {@inheritDoc} */
//监听按键消息
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
......
//音量-键
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
if (isScreenOn && !mVolumeDownKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mVolumeDownKeyTriggered = true;
mVolumeDownKeyTime = event.getDownTime();
mVolumeDownKeyConsumedByScreenshotChord = false;
cancelPendingPowerKeyAction();
//实现截图的方法
interceptScreenshotChord();
}
} else {
mVolumeDownKeyTriggered = false;
cancelPendingScreenshotChordAction();
}
}
......
//power键
case KeyEvent.KEYCODE_POWER: {
if(SystemProperties.get("sys.factorytest","close").equals("open")){//add by zonglibo
Log.d(TAG, "factorytest is open!");
result &= ACTION_PASS_TO_USER;
} else{
result &= ~ACTION_PASS_TO_USER;
if (down) {
mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn, event.getDownTime(),
isImmersiveMode(mLastSystemUiFlags));
if (isScreenOn && !mPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mPowerKeyTriggered = true;
mPowerKeyTime = event.getDownTime();
//实现截图的方法
interceptScreenshotChord();
}
......
}
通过上面的分析,可以知道,我们按下KEYCODE_SYSRQ或者同时按下KEYCODE_POWER和KEYCODE_VOLUME_DOWN都可以实现截图,那么这样我们就可以使用adb模拟按键的方式,来测试KEYCODE_SYSRQ按键的截图效果,看是否是我们分析的那样。
KEYCODE_SYSRQ对应的数值是120,所以我们可以用下面命令实现模拟点击KEYCODE_SYSRQ的效果。
模拟按键KEYCODE_SYSRQ点击截图
adb shell input keyevent 120
或者
adb shell input keyevent KEYCODE_SYSRQ
这两种方式都是可以模拟KEYCODE_SYSRQ按键点击效果的,测试证明,当我执行上面的命令后,是可以实现截图的。感兴趣的朋友可以动手实践一下。
SystemUI中真正实现截图
下面我们来分析takeScreenshot是怎么去实现截图的,通过它的源码我们知道,它其实是调用了TakeScreenshotService这个服务,我们来看下这个服务的代码。
./base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
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 (msg.what) {
case 1:
final Messenger callback = msg.replyTo;
if (mScreenshot == null) {
mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
}
mScreenshot.takeScreenshot(new Runnable() {
@Override public void run() {
Message reply = Message.obtain(null, 1);
try {
callback.send(reply);
} catch (RemoteException e) {
}
}
}, msg.arg1 > 0, msg.arg2 > 0);
}
}
};
@Override
public IBinder onBind(Intent intent) {
return new Messenger(mHandler).getBinder();
}
}
从上可以看出TakeScreenshotService又调用了GlobalScreenshot类中的takeScreenshot方法去是实现截图,那我们就看takeScreenshot的实现代码。
./base/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
/**
* 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 :!)
mDisplay.getRealMetrics(mDisplayMetrics);
float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
boolean isPlugIn =
com.mediatek.systemui.statusbar.util.SIMHelper.isSmartBookPluggedIn(mContext);
if (isPlugIn) {
dims[0] = mDisplayMetrics.heightPixels;
dims[1] = mDisplayMetrics.widthPixels;
}
float degrees = getDegreesForRotation(mDisplay.getRotation());
Xlog.d("takeScreenshot", "dims = " + dims[0] + "," + dims[1] + " of " + degrees);
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]);
Xlog.d("takeScreenshot", "reqRotate, dims = " + dims[0] + "," + dims[1]);
}
// Take the screenshot
//真正实现截图的地方,并返回截图后的bitmap,SurfaceControl.screenshot它到最后调用了C++的方法去实现截图,这里就不在往下分析了,感兴趣朋友可以自行去了解。
if (isPlugIn) {
mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1], SurfaceControl.BUILT_IN_DISPLAY_ID_HDMI);
degrees = 270f - degrees;
} else {
mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
}
//截图失败就发送错误的通知消息
if (mScreenBitmap == null) {
Xlog.d("takeScreenshot", "mScreenBitmap == null, " + dims[0] + "," + dims[1]);
notifyScreenshotError(mContext, mNotificationManager);
finisher.run();
return;
}
if (requiresRotation) {
// Rotate the screenshot to the current orientation
Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
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;
}
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
// Start the post-screenshot animation 执行截图后的动画,这里有保存截图的方法
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
statusBarVisible, navBarVisible);
}
/**
* 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
mScreenshotView.setImageBitmap(mScreenBitmap);
mScreenshotLayout.requestFocus();
// Setup the animation with the screenshot just taken
if (mScreenshotAnimation != null) {
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() {
/// M: [ALPS01233166] Check if this view is currently attached to a window.
if (!mScreenshotView.isAttachedToWindow()) return;
// 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();
}
});
}
/**
* Creates a new worker thread and saves the screenshot to the media store.
*/
private void saveScreenshotInWorkerThread(Runnable finisher) {
SaveImageInBackgroundData data = new SaveImageInBackgroundData();
data.context = mContext;
data.image = mScreenBitmap;
data.iconSize = mNotificationIconSize;
data.finisher = finisher;
if (mSaveInBgTask != null) {
mSaveInBgTask.cancel(false);
}
//开启一个AsyncTask,执行图片保存的操作
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
SCREENSHOT_NOTIFICATION_ID).execute(data);
}
SaveImageInBackgroundTask里面执行就是将截屏后的图片信息保存的Media数据库里,并在通知栏显示截屏结果,同时将图片保存在本地,它的代码就单独贴出来,内容比较多。
/**
* An AsyncTask that saves an image to the media store in the background.
*/
class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void,
SaveImageInBackgroundData> {
private static final String TAG = "SaveImageInBackgroundTask";
private static final String SCREENSHOTS_DIR_NAME = "Screenshots";
private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
private final int mNotificationId;
private final NotificationManager mNotificationManager;
private final Notification.Builder mNotificationBuilder;
private final File mScreenshotDir;
private final String mImageFileName;
private final String mImageFilePath;
private final long mImageTime;
private final BigPictureStyle mNotificationStyle;
private final int mImageWidth;
private final int mImageHeight;
// WORKAROUND: We want the same notification across screenshots that we update so that we don't
// spam a user's notification drawer. However, we only show the ticker for the saving state
// and if the ticker text is the same as the previous notification, then it will not show. So
// for now, we just add and remove a space from the ticker text to trigger the animation when
// necessary.
private static boolean mTickerAddSpace;
SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
NotificationManager nManager, int nId) {
Resources r = context.getResources();
// Prepare all the output metadata
mImageTime = System.currentTimeMillis();
String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime));
//保存的文件名
mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
String screenshotSavePath = Settings.System.getStringForUser(context.getContentResolver(), "screenshot_save_path",UserHandle.USER_CURRENT);
StorageManager storageManager = StorageManager.from(context);
if(screenshotSavePath == null || (screenshotSavePath != null && !Environment.MEDIA_MOUNTED.equals(storageManager.getVolumeState(screenshotSavePath)))) {
screenshotSavePath = StorageManagerEx.getDefaultPath();
}
File Dir = new File(screenshotSavePath);
//图片要保存的路径
mScreenshotDir = new File(Dir, SCREENSHOTS_DIR_NAME);
/*mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), SCREENSHOTS_DIR_NAME);*/
//图片要保存的绝对路径
mImageFilePath = new File(mScreenshotDir, mImageFileName).getAbsolutePath();
// Create the large notification icon
mImageWidth = data.image.getWidth();
mImageHeight = data.image.getHeight();
int iconSize = data.iconSize;
final int shortSide = mImageWidth < mImageHeight ? mImageWidth : mImageHeight;
Bitmap preview = Bitmap.createBitmap(shortSide, shortSide, data.image.getConfig());
Canvas c = new Canvas(preview);
Paint paint = new Paint();
ColorMatrix desat = new ColorMatrix();
desat.setSaturation(0.25f);
paint.setColorFilter(new ColorMatrixColorFilter(desat));
Matrix matrix = new Matrix();
matrix.postTranslate((shortSide - mImageWidth) / 2,
(shortSide - mImageHeight) / 2);
c.drawBitmap(data.image, matrix, paint);
c.drawColor(0x40FFFFFF);
c.setBitmap(null);
Bitmap croppedIcon = Bitmap.createScaledBitmap(preview, iconSize, iconSize, true);
// Show the intermediate notification
mTickerAddSpace = !mTickerAddSpace;
mNotificationId = nId;
mNotificationManager = nManager;
mNotificationBuilder = new Notification.Builder(context)
.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(System.currentTimeMillis());
mNotificationStyle = new Notification.BigPictureStyle()
.bigPicture(preview);
mNotificationBuilder.setStyle(mNotificationStyle);
Notification n = mNotificationBuilder.build();
n.flags |= Notification.FLAG_NO_CLEAR;
mNotificationManager.notify(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.
//通知栏显示截屏的缩略图
mNotificationBuilder.setLargeIcon(croppedIcon);
// But we still don't set it for the expanded view, allowing the smallIcon to show here.
mNotificationStyle.bigLargeIcon(null);
}
@Override
protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) {
if (params.length != 1) return null;
if (isCancelled()) {
params[0].clearImage();
params[0].clearContext();
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 = params[0].context;
Bitmap image = params[0].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 the screenshot to the MediaStore
//将截屏后要保存的图片的信息保存到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);
Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
String subjectDate = new SimpleDateFormat("hh:mma, MMM dd, yyyy")
.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);
Intent chooserIntent = Intent.createChooser(sharingIntent, null);
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NEW_TASK);
mNotificationBuilder.addAction(R.drawable.ic_menu_share,
r.getString(com.android.internal.R.string.share),
PendingIntent.getActivity(context, 0, chooserIntent,
PendingIntent.FLAG_CANCEL_CURRENT));
//将截屏后的bitmap保存为png图片
FileOutputStream out = new FileOutputStream(mImageFilePath);
boolean bCompressOK = image.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
out.close();
/// M: [ALPS00800619] Handle Compress Fail Case.
if (!bCompressOK) {
resolver.delete(uri, null, null);
params[0].result = 1;
return params[0];
}
// update file size in the database
values.clear();
/// M: FOR ALPS00266037 & ALPS00289039 pic taken by phone shown wrong on cumputer. @{
InputStream inputStream = resolver.openInputStream(uri);
int size = inputStream.available();
inputStream.close();
values.put(MediaStore.Images.ImageColumns.SIZE, size);
// values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
uri = uri.buildUpon().appendQueryParameter("notifyMtp", "1").build();
resolver.update(uri, values, null, null);
/// M: FOR ALPS00266037 & ALPS00289039. @}
params[0].imageUri = uri;
params[0].image = null;
params[0].result = 0;
} catch (Exception e) {
// IOException/UnsupportedOperationException may be thrown if external storage is not
// mounted
params[0].clearImage();
params[0].result = 1;
}
// Recycle the bitmap data
if (image != null) {
image.recycle();
}
return params[0];
}
@Override
protected void onPostExecute(SaveImageInBackgroundData params) {
if (isCancelled()) {
params.finisher.run();
params.clearImage();
params.clearContext();
return;
}
//通知栏显示,截屏后的执行结果
if (params.result > 0) {
// Show a message that we've failed to save the image to disk
GlobalScreenshot.notifyScreenshotError(params.context, mNotificationManager);
} else {
// Show the final notification to indicate screenshot saved
Resources r = params.context.getResources();
// Create the intent to show the screenshot in gallery
Intent launchIntent = new Intent(Intent.ACTION_VIEW);
launchIntent.setDataAndType(params.imageUri, "image/png");
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mNotificationBuilder
.setContentTitle(r.getString(R.string.screenshot_saved_title))
.setContentText(r.getString(R.string.screenshot_saved_text))
.setContentIntent(PendingIntent.getActivity(params.context, 0, launchIntent, 0))
.setWhen(System.currentTimeMillis())
.setAutoCancel(true);
Notification n = mNotificationBuilder.build();
n.flags &= ~Notification.FLAG_NO_CLEAR;
mNotificationManager.notify(mNotificationId, n);
}
//整个截屏事件结束
params.finisher.run();
params.clearContext();
}
}
整个系统的截屏流程,代码分析差不多讲完了,我们来整理一下他们之间的调用关系。
android系统基本的截图流程就是这样了,不排除个别厂商会使用自定义的一套截图方法。所以基本上,要做系统截图功能,只要知道上面关系图中的那些类和方法就可以做一定的个性化定制了。