Android 屏幕截屏方法汇总
作者:mmsx 时间:2022-01-23 05:48:18
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" 进行后续开发。