Android 屏幕截屏方法汇总
1、直接使用getWindow().getDecorView().getRootView()
直接使用getWindow().getDecorView().getRootView()是获取当前屏幕的activity。然而对于系统状态栏的信息是截不了,出现一条空白的。如下图:
主要到没,有一条白色边就是系统状态栏。看一下代码,很简单都加了注释了。
//这种方法状态栏是空白,显示不了状态栏的信息 private void saveCurrentImage() { //获取当前屏幕的大小 int width = getWindow().getDecorView().getRootView().getWidth(); int height = getWindow().getDecorView().getRootView().getHeight(); //生成相同大小的图片 Bitmap temBitmap = Bitmap.createBitmap( width, height, Config.ARGB_8888 ); //找到当前页面的跟布局 View view = getWindow().getDecorView().getRootView(); //设置缓存 view.setDrawingCacheEnabled(true); view.buildDrawingCache(); //从缓存中获取当前屏幕的图片 temBitmap = view.getDrawingCache(); //输出到sd卡 if (FileIOUtil.getExistStorage()) { FileIOUtil.GetInstance().onFolderAnalysis(FileIOUtil.GetInstance().getFilePathAndName()); File file = new File(FileIOUtil.GetInstance().getFilePathAndName()); try { if (!file.exists()) { file.createNewFile(); } FileOutputStream foStream = new FileOutputStream(file); temBitmap.compress(Bitmap.CompressFormat.PNG, 100, foStream); foStream.flush(); foStream.close(); } catch (Exception e) { Log.i("Show", e.toString()); } } }
2、自定义view控件的截图
自定义view控件都是继承view的吗,那么就有可以获取宽度,高度。生成图片,把它绘制出来的。我拿了直接写的自定义随机验证码的例子来截图。
//保存自定义view的截图 private void saveCustomViewBitmap() { //获取自定义view图片的大小 Bitmap temBitmap = Bitmap.createBitmap(mCodeView.getWidth(), mCodeView.getHeight(), Config.ARGB_8888); //使用Canvas,调用自定义view控件的onDraw方法,绘制图片 Canvas canvas = new Canvas(temBitmap); mCodeView.onDraw(canvas); //输出到sd卡 if (FileIOUtil.getExistStorage()) { FileIOUtil.GetInstance().onFolderAnalysis(FileIOUtil.GetInstance().getFilePathAndName()); File file = new File(FileIOUtil.GetInstance().getFilePathAndName()); try { if (!file.exists()) { file.createNewFile(); } FileOutputStream foStream = new FileOutputStream(file); temBitmap.compress(Bitmap.CompressFormat.PNG, 100, foStream); foStream.flush(); foStream.close(); Toast.makeText(MainActivity.this, "截屏文件已保存至" + FileIOUtil.GetInstance().getFilePathAndName(), Toast.LENGTH_LONG).show(); } catch (Exception e) { Log.i("Show", e.toString()); } } }
有人根据这种思路,自定义整个布局的view,然后来截图。也是够拼的,我有点不赞成这样使用。
3、基于android ddmlib截屏
这个是java写的一个类,入口是mian函数。那么这种实现方式是要android连接着电脑,还需要android设备调试。这种不多说,搞android开发都懂。太麻烦了,我们也不使用。
package com.example.screenshot; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import com.android.ddmlib.AdbCommandRejectedException; import com.android.ddmlib.AndroidDebugBridge; import com.android.ddmlib.IDevice; import com.android.ddmlib.RawImage; import com.android.ddmlib.TimeoutException; public class ScreenShoddmlib { private BufferedImage image = null; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub AndroidDebugBridge.init(false); // ScreenShoddmlib screenshot = new ScreenShoddmlib(); IDevice device = screenshot.getDevice(); for (int i = 0; i < 10; i++) { Date date=new Date(); SimpleDateFormat df=new SimpleDateFormat("MM-dd-HH-mm-ss"); String nowTime = df.format(date); screenshot.getScreenShot(device, "Robotium" + nowTime); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public void getScreenShot(IDevice device,String filename) { RawImage rawScreen = null; try { rawScreen = device.getScreenshot(); } catch (TimeoutException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (AdbCommandRejectedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (rawScreen != null) { Boolean landscape = false; int width2 = landscape ? rawScreen.height : rawScreen.width; int height2 = landscape ? rawScreen.width : rawScreen.height; if (image == null) { image = new BufferedImage(width2, height2, BufferedImage.TYPE_INT_RGB); } else { if (image.getHeight() != height2 || image.getWidth() != width2) { image = new BufferedImage(width2, height2, BufferedImage.TYPE_INT_RGB); } } int index = 0; int indexInc = rawScreen.bpp >> 3; for (int y = 0; y < rawScreen.height; y++) { for (int x = 0; x < rawScreen.width; x++, index += indexInc) { int value = rawScreen.getARGB(index); if (landscape) image.setRGB(y, rawScreen.width - x - 1, value); else image.setRGB(x, y, value); } } try { ImageIO.write((RenderedImage) image, "PNG", new File("D:/" + filename + ".jpg")); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * 获取得到device对象 * @return */ private IDevice getDevice(){ IDevice device; AndroidDebugBridge bridge = AndroidDebugBridge .createBridge("adb", true);//如果代码有问题请查看API,修改此处的参数值试一下 waitDevicesList(bridge); IDevice devices[] = bridge.getDevices(); device = devices[0]; return device; } /** * 等待查找device * @param bridge */ private void waitDevicesList(AndroidDebugBridge bridge) { int count = 0; while (bridge.hasInitialDeviceList() == false) { try { Thread.sleep(500); count++; } catch (InterruptedException e) { } if (count > 240) { System.err.print("等待获取设备超时"); break; } } }
4、使用adb命令
需要系统权限,在APK中调用“adb shell screencap -pfilepath” 命令
需要获得系统权限:
1、 在AndroidManifest.xml文件中添加 <uses-permissionandroid:name="android.permission.READ_FRAME_BUFFER"/>
2、修改APK为系统权限,将APK放到源码中编译, 修改Android.mk LOCAL_CERTIFICATE := platform
在这里我要说一下,搞过jni调用就知道Android.mk的作用。此举也是麻烦,效果也不是很好。
public void takeScreenShot(){ String mSavedPath = Environment.getExternalStorageDirectory()+File. separator + "screenshot.png" ; try { Runtime. getRuntime().exec("screencap -p " + mSavedPath); } catch (Exception e) { e.printStackTrace(); }
5、看一下系统截屏是怎样的
相信大家都知道,三星的机子是同时按下 home键 + 电源键 3秒截图。
如果没有home键的机子是按下 音量键向下那个 + 电源键 3秒截图的。
获取物理键盘按下的源码:PhoneWindowManager.java
// 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 (isScreenOn && !mVolumeDownKeyTriggered && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { mVolumeDownKeyTriggered = true; mVolumeDownKeyTime = event.getDownTime(); mVolumeDownKeyConsumedByScreenshotChord = false; cancelPendingPowerKeyAction(); interceptScreenshotChord(); } } else { mVolumeDownKeyTriggered = false; cancelPendingScreenshotChordAction(); } } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { if (down) { if (isScreenOn && !mVolumeUpKeyTriggered && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { mVolumeUpKeyTriggered = true; cancelPendingPowerKeyAction(); cancelPendingScreenshotChordAction(); } } else { mVolumeUpKeyTriggered = false; cancelPendingScreenshotChordAction(); } } if (down) { ITelephony telephonyService = getTelephonyService(); if (telephonyService != null) { try { if (telephonyService.isRinging()) { // If an incoming call is ringing, either VOLUME key means // "silence ringer". We handle these keys here, rather than // in the InCallScreen, to make sure we'll respond to them // even if the InCallScreen hasn't come to the foreground yet. // Look for the DOWN event here, to agree with the "fallback" // behavior in the InCallScreen. Log.i(TAG, "interceptKeyBeforeQueueing:" + " VOLUME key-down while ringing: Silence ringer!"); // Silence the ringer. (It's safe to call this // even if the ringer has already been silenced.) telephonyService.silenceRinger(); // And *don't* pass this key thru to the current activity // (which is probably the InCallScreen.) result &= ~ACTION_PASS_TO_USER; break; } if (telephonyService.isOffhook() && (result & ACTION_PASS_TO_USER) == 0) { // If we are in call but we decided not to pass the key to // the application, handle the volume change here. handleVolumeKey(AudioManager.STREAM_VOICE_CALL, keyCode); break; } } catch (RemoteException ex) { Log.w(TAG, "ITelephony threw RemoteException", ex); } } if (isMusicActive() && (result & ACTION_PASS_TO_USER) == 0) { // If music is playing but we decided not to pass the key to the // application, handle the volume change here. handleVolumeKey(AudioManager.STREAM_MUSIC, keyCode); break; } } break; } case KeyEvent.KEYCODE_ENDCALL: { result &= ~ACTION_PASS_TO_USER; if (down) { ITelephony telephonyService = getTelephonyService(); boolean hungUp = false; if (telephonyService != null) { try { hungUp = telephonyService.endCall(); } catch (RemoteException ex) { Log.w(TAG, "ITelephony threw RemoteException", ex); } } interceptPowerKeyDown(!isScreenOn || hungUp); } else { if (interceptPowerKeyUp(canceled)) { if ((mEndcallBehavior & Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0) { if (goHome()) { break; } } if ((mEndcallBehavior & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) { result = (result & ~ACTION_WAKE_UP) | ACTION_GO_TO_SLEEP; } } } break; } case KeyEvent.KEYCODE_POWER: { result &= ~ACTION_PASS_TO_USER; if (down) { if (isScreenOn && !mPowerKeyTriggered && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { mPowerKeyTriggered = true; mPowerKeyTime = event.getDownTime(); interceptScreenshotChord(); } ITelephony telephonyService = getTelephonyService(); boolean hungUp = false; if (telephonyService != null) { try { if (telephonyService.isRinging()) { // Pressing Power while there's a ringing incoming // call should silence the ringer. telephonyService.silenceRinger(); } else if ((mIncallPowerBehavior & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 && telephonyService.isOffhook()) { // Otherwise, if "Power button ends call" is enabled, // the Power button will hang up any current active call. hungUp = telephonyService.endCall(); } } catch (RemoteException ex) { Log.w(TAG, "ITelephony threw RemoteException", ex); } } interceptPowerKeyDown(!isScreenOn || hungUp || mVolumeDownKeyTriggered || mVolumeUpKeyTriggered); } else { mPowerKeyTriggered = false; cancelPendingScreenshotChordAction(); if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) { result = (result & ~ACTION_WAKE_UP) | ACTION_GO_TO_SLEEP; } mPendingPowerKeyUpCanceled = false; } break; }
响应截屏的方法:
private void interceptScreenshotChord() { if (mScreenshotChordEnabled && mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) { final long now = SystemClock.uptimeMillis(); if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS && now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) { mVolumeDownKeyConsumedByScreenshotChord = true; cancelPendingPowerKeyAction(); mHandler.postDelayed(mScreenshotChordLongPress, getScreenshotChordLongPressDelay()); } } } private long getScreenshotChordLongPressDelay() { if (mKeyguardMediator.isShowing()) { // Double the time it takes to take a screenshot from the keyguard return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER * ViewConfiguration.getGlobalActionKeyTimeout()); } else { return ViewConfiguration.getGlobalActionKeyTimeout(); } }
接受响应的服务
private final Runnable mScreenshotChordLongPress = new Runnable() { public void run() { takeScreenshot(); } }; private void takeScreenshot() { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { return; } 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) {} }; if (mContext.bindService( intent, conn, Context.BIND_AUTO_CREATE, UserHandle.USER_CURRENT)) { mScreenshotConnection = conn; mHandler.postDelayed(mScreenshotTimeout, 10000); } } }
启动时这个服务 ComponentName cn = new ComponentName("com.android.systemui", "com.android.systemui.screenshot.TakeScreenshotService");
package com.android.systemui.screenshot; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; 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(); }
再往下就是底层库,需要编译出来才能看得到了。这些就先不研究了。
6、还有部分系统源码是截图的
/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.screenshot; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.Notification; import android.app.Notification.BigPictureStyle; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PointF; import android.media.MediaActionSound; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; import android.os.Process; import android.provider.MediaStore; import android.util.DisplayMetrics; import android.view.Display; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.Interpolator; import android.widget.ImageView; import com.android.systemui.R; import java.io.File; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; /** * POD used in the AsyncTask which saves an image in the background. */ class SaveImageInBackgroundData { Context context; Bitmap image; Uri imageUri; Runnable finisher; int iconSize; int result; } /** * An AsyncTask that saves an image to the media store in the background. */ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void, SaveImageInBackgroundData> { private static final String SCREENSHOTS_DIR_NAME = "Screenshots"; private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png"; private static final String SCREENSHOT_FILE_PATH_TEMPLATE = "%s/%s/%s"; private int mNotificationId; private NotificationManager mNotificationManager; private Notification.Builder mNotificationBuilder; private String mImageFileName; private String mImageFilePath; private long mImageTime; private BigPictureStyle mNotificationStyle; // 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)); String imageDir = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES).getAbsolutePath(); mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate); mImageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, imageDir, SCREENSHOTS_DIR_NAME, mImageFileName); // Create the large notification icon int imageWidth = data.image.getWidth(); int imageHeight = data.image.getHeight(); int iconSize = data.iconSize; final int shortSide = imageWidth < imageHeight ? imageWidth : imageHeight; 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 - imageWidth) / 2, (shortSide - imageHeight) / 2); c.drawBitmap(data.image, matrix, paint); c.drawColor(0x40FFFFFF); 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; // 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 { // 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, mImageTime); values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, mImageTime); values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png"); Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setType("image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); 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)); OutputStream out = resolver.openOutputStream(uri); image.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); out.close(); // update file size in the database values.clear(); values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length()); resolver.update(uri, values, null, null); params[0].imageUri = uri; params[0].result = 0; } catch (Exception e) { // IOException/UnsupportedOperationException may be thrown if external storage is not // mounted params[0].result = 1; } return params[0]; } @Override protected void onPostExecute(SaveImageInBackgroundData params) { 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(); } } /** * TODO: * - Performance when over gl surfaces? Ie. Gallery * - what do we say in the Toast? Which icon do we get if the user uses another * type of gallery? */ class GlobalScreenshot { private static final int SCREENSHOT_NOTIFICATION_ID = 789; private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130; private static final int SCREENSHOT_DROP_IN_DURATION = 430; private static final int SCREENSHOT_DROP_OUT_DELAY = 500; private static final int SCREENSHOT_DROP_OUT_DURATION = 430; private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370; private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320; private static final float BACKGROUND_ALPHA = 0.5f; private static final float SCREENSHOT_SCALE = 1f; private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f; private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f; private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f; private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f; private Context mContext; private WindowManager mWindowManager; private WindowManager.LayoutParams mWindowLayoutParams; private NotificationManager mNotificationManager; private Display mDisplay; private DisplayMetrics mDisplayMetrics; private Matrix mDisplayMatrix; private Bitmap mScreenBitmap; private View mScreenshotLayout; private ImageView mBackgroundView; private ImageView mScreenshotView; private ImageView mScreenshotFlash; private AnimatorSet mScreenshotAnimation; private int mNotificationIconSize; private float mBgPadding; private float mBgPaddingScale; private MediaActionSound mCameraSound; /** * @param context everything needs a context :( */ public GlobalScreenshot(Context context) { Resources r = context.getResources(); mContext = context; LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // Inflate the screenshot layout mDisplayMatrix = new Matrix(); mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null); mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background); mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot); mScreenshotFlash = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_flash); mScreenshotLayout.setFocusable(true); mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // Intercept and ignore all touch events return true; } }); // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, PixelFormat.TRANSLUCENT); mWindowLayoutParams.setTitle("ScreenshotAnimation"); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); mDisplay = mWindowManager.getDefaultDisplay(); mDisplayMetrics = new DisplayMetrics(); mDisplay.getRealMetrics(mDisplayMetrics); // Get the various target sizes mNotificationIconSize = r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height); // Scale has to account for both sides of the bg mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding); mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels; // Setup the Camera shutter sound mCameraSound = new MediaActionSound(); mCameraSound.load(MediaActionSound.SHUTTER_CLICK); } /** * 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; new SaveImageInBackgroundTask(mContext, data, mNotificationManager, SCREENSHOT_NOTIFICATION_ID).execute(data); } /** * @return the current display rotation in degrees */ private float getDegreesForRotation(int value) { switch (value) { case Surface.ROTATION_90: return 360f - 90f; case Surface.ROTATION_180: return 360f - 180f; case Surface.ROTATION_270: return 360f - 270f; } return 0f; } /** * 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}; 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 = Surface.screenshot((int) dims[0], (int) dims[1]); if (mScreenBitmap == null) { 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); 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(); } 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); } }); 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(); } }); } private ValueAnimator createScreenshotDropInAnimation() { final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION) / SCREENSHOT_DROP_IN_DURATION); final float flashDurationPct = 2f * flashPeakDurationPct; final Interpolator flashAlphaInterpolator = new Interpolator() { @Override public float getInterpolation(float x) { // Flash the flash view in and out quickly if (x <= flashDurationPct) { return (float) Math.sin(Math.PI * (x / flashDurationPct)); } return 0; } }; final Interpolator scaleInterpolator = new Interpolator() { @Override public float getInterpolation(float x) { // We start scaling when the flash is at it's peak if (x < flashPeakDurationPct) { return 0; } return (x - flashDurationPct) / (1f - flashDurationPct); } }; ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setDuration(SCREENSHOT_DROP_IN_DURATION); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { mBackgroundView.setAlpha(0f); mBackgroundView.setVisibility(View.VISIBLE); mScreenshotView.setAlpha(0f); mScreenshotView.setTranslationX(0f); mScreenshotView.setTranslationY(0f); mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale); mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale); mScreenshotView.setVisibility(View.VISIBLE); mScreenshotFlash.setAlpha(0f); mScreenshotFlash.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(android.animation.Animator animation) { mScreenshotFlash.setVisibility(View.GONE); } }); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float t = (Float) animation.getAnimatedValue(); float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale) - scaleInterpolator.getInterpolation(t) * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE); mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA); mScreenshotView.setAlpha(t); mScreenshotView.setScaleX(scaleT); mScreenshotView.setScaleY(scaleT); mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t)); } }); return anim; } private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible, boolean navBarVisible) { ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mBackgroundView.setVisibility(View.GONE); mScreenshotView.setVisibility(View.GONE); mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); } }); if (!statusBarVisible || !navBarVisible) { // There is no status bar/nav bar, so just fade the screenshot away in place anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float t = (Float) animation.getAnimatedValue(); float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) - t * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE); mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); mScreenshotView.setAlpha(1f - t); mScreenshotView.setScaleX(scaleT); mScreenshotView.setScaleY(scaleT); } }); } else { // In the case where there is a status bar, animate to the origin of the bar (top-left) final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION / SCREENSHOT_DROP_OUT_DURATION; final Interpolator scaleInterpolator = new Interpolator() { @Override public float getInterpolation(float x) { if (x < scaleDurationPct) { // Decelerate, and scale the input accordingly return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f)); } return 1f; } }; // Determine the bounds of how to scale float halfScreenWidth = (w - 2f * mBgPadding) / 2f; float halfScreenHeight = (h - 2f * mBgPadding) / 2f; final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET; final PointF finalPos = new PointF( -halfScreenWidth + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth, -halfScreenHeight + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight); // Animate the screenshot to the status bar anim.setDuration(SCREENSHOT_DROP_OUT_DURATION); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float t = (Float) animation.getAnimatedValue(); float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) - scaleInterpolator.getInterpolation(t) * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE); mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t)); mScreenshotView.setScaleX(scaleT); mScreenshotView.setScaleY(scaleT); mScreenshotView.setTranslationX(t * finalPos.x); mScreenshotView.setTranslationY(t * finalPos.y); } }); } return anim; } static void notifyScreenshotError(Context context, NotificationManager nManager) { Resources r = context.getResources(); // Clear all existing notification, compose the new notification and show it Notification n = new Notification.Builder(context) .setTicker(r.getString(R.string.screenshot_failed_title)) .setContentTitle(r.getString(R.string.screenshot_failed_title)) .setContentText(r.getString(R.string.screenshot_failed_text)) .setSmallIcon(R.drawable.stat_notify_image_error) .setWhen(System.currentTimeMillis()) .setAutoCancel(true) .getNotification(); nManager.notify(SCREENSHOT_NOTIFICATION_ID, n); }
可以从这部分源码中,去寻找解决办法。
其中有一个很重要的方法就是
mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
这个是可以截图的,但是是隐藏的代码,不提供外面调用的。
注意需要在AndroidManifest.xml中加入代码:android:sharedUserId="android.uid.system" 进行后续开发。