今天约了暗恋很久的女生去看电影,发现没带钱,值得庆幸的是,她像往常一样并没有赴约。
还是帮我踢下凳子吧谢谢
Android系统原生是自带截屏功能的,现在市面上有些三方应用可以截屏,但是一般都得Root,5.0之后已经不好root了,一般手机厂商都是自带截屏功能,除非你是系统签名的否则你不能截取任意页面的屏幕。
最简单的方法截屏,当然该方法不能截取任意应用的屏幕
View.getDrawingCache()
第二种方法 5.0后已经可以支持截屏了 ,
Android5.0免Root截屏,录屏看这里
或者可以通过adb命令获取 截屏
adb shell screencap -pfilepath
该命令读取系统的framebuffer,需要获得系统权限:
(1). 在AndroidManifest.xml文件中添加
< uses-permission android:name = "android.permission.READ_FRAME_BUFFER" />
(2). 修改APK为系统权限,将APK放到源码中编译, 修改Android.mk
LOCAL_CERTIFICATE := platform
publicvoid takeScreenShot(){
String mSavedPath = Environment.getExternalStorageDirectory()+File. separator + "screenshot.png" ;
try {
Runtime. getRuntime().exec("screencap -p " + mSavedPath);
} catch (Exception e) {
e.printStackTrace();
}
测试发现该方法可以实现
2y
当然本文是介绍系统的截屏
既然已经有系统权限了就用不着那么好心用上面的方法了,所以跟踪一下5.0系统截屏是怎么实现的,步步高打火机哪里不会点哪里,
截屏功能So easy只需一行代码就是实现了,还真是个深思熟虑的选择
mScreenBitmap = SurfaceControl.screenshot((int) 屏幕高, (int) dims屏幕宽);
SurfaceControl.screenshot()方法需要系统签名才可以 ,通过反射也需要系统签名,所以第三方应用不能调用这个个方法
原生Android系统是默认是同时按下音量下键+关机键 时会截屏
手机厂商现在都改到快捷下拉位置,两者调用的都是同一种方法
接下来看看系统原生截屏的整个流程
和截屏相关的有如下几个文件:
frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindowManager.java
frameworks\base\packages\SystemUI\src\com\android\systemui\screenshot\GlobalScreenshot
PhoneWindowManager.java里面监听音量下键+关机键然后开启线程执行截屏服务
音量下键+关机键然后开启线程
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(mScreenshotRunnable, getScreenshotChordLongPressDelay());
}
}
}
启动截屏服务其实是 SystemUI里的 TakeScreenshotService这个函数使用AIDL绑定了service服务到"com.android.systemui.screenshot.TakeScreenshotService",注意在service连接成功时,对message的msg.arg1和msg.arg2两个参数的赋值。其中在mScreenshotTimeout中对服务service做了超时处理
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.bindServiceAsUser(
intent, conn, Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
mScreenshotConnection = conn;
mHandler.postDelayed(mScreenshotTimeout, 10000);
}
}
}
SystemUI专门有个截屏包,刚才启动的就是TakeScreenshotService服务,核心截屏功能在GlobalScreenshot
TakeScreenshotService服务调用GlobalScreenshot takeScreenshot()方法
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();
}
}
我们来看看GlobalScreenshot具体流程SurfaceControl.screenshot
看看系统获取整个屏幕的宽高是怎样的
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);</span>
这里就是 截屏的整个核心,首先获得屏幕宽高、旋转屏幕时的宽高的获取,然后通过SurfaceControl.screenshot()获得Bitmap 最后执行截屏动画
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};
/// M: [SystemUI] Support Smartbook Feature. @{
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
/// M: [SystemUI] Support Smartbook Feature. @{
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);
}
SurfaceControl.screenshot()在往下就是 native方法了
/**
* Like {@link #screenshot(int, int, int, int)} but includes all
* Surfaces in the screenshot.
*
* @hide
*/
public static native Bitmap screenshot(int width, int height);
jni往下就是C++文件,android_view_Surface.cpp中,这里就不往下跟了
static jobject doScreenshot(JNIEnv* env, jobject clazz, jint width, jint height,
jint minLayer, jint maxLayer, bool allLayers)
{
ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
if (pixels->update(width, height, minLayer, maxLayer, allLayers) != NO_ERROR) {
delete pixels;
return 0;
}
uint32_t w = pixels->getWidth();
uint32_t h = pixels->getHeight();
uint32_t s = pixels->getStride();
uint32_t f = pixels->getFormat();
ssize_t bpr = s * android::bytesPerPixel(f);
SkBitmap* bitmap = new SkBitmap();
bitmap->setConfig(convertPixelFormat(f), w, h, bpr);
if (f == PIXEL_FORMAT_RGBX_8888) {
bitmap->setIsOpaque(true);
}
if (w > 0 && h > 0) {
bitmap->setPixelRef(pixels)->unref();
bitmap->lockPixels();
} else {
// be safe with an empty bitmap.
delete pixels;
bitmap->setPixels(NULL);
}
return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
}
继续刚才 在获取Bitmap后 执行动画时会有拍照声
// Setup the Camera shutter sound
mCameraSound = new MediaActionSound();
mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
下面就是 执行动画
/**
* 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()) {
Xlog.d(TAG, "this view is currently not attached to a window");
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();
}
});
}
保存截屏数据对象,刚才截屏获得的mScreenBitmap 赋值给对象SaveImageInBackgroundData
private void saveScreenshotInWorkerThread(Runnable finisher) {
SaveImageInBackgroundData data = new SaveImageInBackgroundData();
data.context = mContext;
data.image = mScreenBitmap;
data.iconSize = mNotificationIconSize;
data.finisher = finisher;
data.previewWidth = mPreviewWidth;
data.previewheight = mPreviewHeight;
if (mSaveInBgTask != null) {
mSaveInBgTask.cancel(false);
}
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
SCREENSHOT_NOTIFICATION_ID).execute(data);
}
然后执行一个AsyncTask,主要是目的是把截图插入MediaStore图库,并发一个通知 ,告诉用户截图位置和截图样式
/**
* 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, mPublicNotificationBuilder;
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);
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;
int previewWidth = data.previewWidth;
int previewHeight = data.previewheight;
final int shortSide = mImageWidth < mImageHeight ? mImageWidth : mImageHeight;
Bitmap preview = Bitmap.createBitmap(previewWidth, previewHeight, 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((previewWidth - mImageWidth) / 2,
(previewHeight - 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;
final long now = System.currentTimeMillis();
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(now)
.setColor(r.getColor(com.android.internal.R.color.system_notification_accent_color));
mNotificationStyle = new Notification.BigPictureStyle()
.bigPicture(preview);
mNotificationBuilder.setStyle(mNotificationStyle);
// For "public" situations we want to show all the same info but
// omit the actual screenshot image.
mPublicNotificationBuilder = new Notification.Builder(context)
.setContentTitle(r.getString(R.string.screenshot_saving_title))
.setContentText(r.getString(R.string.screenshot_saving_text))
.setSmallIcon(R.drawable.stat_notify_image)
.setCategory(Notification.CATEGORY_PROGRESS)
.setWhen(now)
.setColor(r.getColor(
com.android.internal.R.color.system_notification_accent_color));
mNotificationBuilder.setPublicVersion(mPublicNotificationBuilder.build());
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
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 = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setType("image/png");
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
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);
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();
/// M: FOR ALPS00266037 & ALPS00289039. @}
resolver.update(uri, values, null, null);
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);
final long now = System.currentTimeMillis();
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(now)
.setAutoCancel(true)
.setColor(r.getColor(
com.android.internal.R.color.system_notification_accent_color));;
// Update the text in the public version as well
mPublicNotificationBuilder
.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(now)
.setAutoCancel(true)
.setColor(r.getColor(
com.android.internal.R.color.system_notification_accent_color));
mNotificationBuilder.setPublicVersion(mPublicNotificationBuilder.build());
Notification n = mNotificationBuilder.build();
n.flags &= ~Notification.FLAG_NO_CLEAR;
mNotificationManager.notify(mNotificationId, n);
}
params.finisher.run();
params.clearContext();
}
}
执行到这里 截图流程就完了,你是时候表演真正的技术了
如果想系统开发调用此流程可以直接想用音量键+电源键的流程直接调用 TakeScreenshotService服务就可以了,不需要重写
接下来试着去写个截屏应用了
编写两个应用
AIDL服务端ScreenShotSampleServer
(系统签名的应用)用来调用系统截屏方法
客户端ScreenShotSampleClient
为第三方应用 用来调用服务端的接口来实现截屏
首先实现服务端
配置文件系统签名
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.XXX.XXX"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="android.uid.system" >
首先定义AIDL接口Eclipse插件的帮助下,编译器会自动在gen目录中生成对应的IScreenshotControl,接口中的抽象内部类Stub继承android.os.Binder类并实现IScreenshotControl接口,比较重要的方法是asInterface(IBinder)方法,该方法会将IBinder类型的对象转换成IScreenshotControl类型,必要的时候生成一个代理对象返回结果。
interface IScreenshotControl{
String takeScreenshot();
}
<pre name="code" class="java">/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: F:\\workspace2\\ScreenShotSampleServer\\src\\com\\example\\screenshotsample\\IScreenshotControl.aidl
*/
package com.example.screenshotsample;
public interface IScreenshotControl extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements
com.example.screenshotsample.IScreenshotControl {
private static final java.lang.String DESCRIPTOR = "com.example.screenshotsample.IScreenshotControl";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an
* com.example.screenshotsample.IScreenshotControl interface, generating
* a proxy if needed.
*/
public static com.example.screenshotsample.IScreenshotControl asInterface(
android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.screenshotsample.IScreenshotControl))) {
return ((com.example.screenshotsample.IScreenshotControl) iin);
}
return new com.example.screenshotsample.IScreenshotControl.Stub.Proxy(
obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data,
android.os.Parcel reply, int flags)
throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_takeScreenshot: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.takeScreenshot();
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements
com.example.screenshotsample.IScreenshotControl {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.lang.String takeScreenshot()
throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_takeScreenshot, _data,
_reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_takeScreenshot = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public java.lang.String takeScreenshot() throws android.os.RemoteException;
}
接下来就是我们的Service了:
package com.example.screenshotsample;
import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class ScreenshotControlService extends Service {
private static final String BIND_SERVICE_ACTION = "com.example.screenshotsample.start";
private static final String TAG = "ScreenshotControlService";
ScreenshotUtil mScreenshotUtil;
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
}
}
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "I have started hehe---");
return Service.START_STICKY;
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate........");
mScreenshotUtil= new ScreenshotUtil(this,mHandler);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy........");
}
@Override
public IBinder onBind(Intent intent) {
Log.v(TAG,"bind action is "+ intent.getAction());
if (BIND_SERVICE_ACTION.equals(intent.getAction())) {
return mSBinder;
}
return null;
}
public IScreenshotControl.Stub mSBinder = new IScreenshotControl.Stub() {
@Override
public String takeScreenshot() throws RemoteException {
// TODO Auto-generated method stub
return mScreenshotUtil.startScreenShot();
}
};
}
把系统截屏方法集成到ScreenshotUtil里,和之前音量键+电源键的方法是一样的
package com.hitown.generalplan.control;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
/**
* @author 老司机
*
*/
public class ScreenshotUtil {
private File mScreenshotDir;
private String mImageFileName;
private String mImageFilePath;
private static String SCREENSHOTS_DIR_NAME = "Screenshots";
private Context mContext;
private Handler mHandler;
public ScreenshotUtil(Context context, Handler h) {
mContext = context;
mHandler = h;
// 截屏保存位置
mScreenshotDir = new File(
Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
SCREENSHOTS_DIR_NAME);
mImageFilePath = mScreenshotDir.getAbsolutePath();
}
public String startScreenShot() {
mHandler.post(mScreenshotRunnable);
return mImageFilePath;
}
private final Runnable mScreenshotRunnable = new Runnable() {
@Override
public void run() {
// 这里可以加上延时,等待用户跳转到其他页面
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
takeScreenShot();
}
};
final Object mScreenshotLock = new Object();
ServiceConnection mScreenshotConnection = null;
final Runnable mScreenshotTimeout = new Runnable() {
@Override
public void run() {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection = null;
}
}
}
};
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.bindServiceAsUser(intent, conn,
Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
mScreenshotConnection = conn;
mHandler.postDelayed(mScreenshotTimeout, 10000);
}
}
}
}
编写mk 文件,并编译到源码刚才一场大雨
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under,src) \
src/com/example/screenshotsample/IScreenshotControl.aidl
LOCAL_PACKAGE_NAME := ScreenShotSampleServer
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
这样服务端就完成了
客户端的实现就简单了,
我们只需要把IScreenshotControl.aidl文件拷到相应的目录中即可,编译器同样会生成相对应的IScreenshotControl.java文件,这一部分和服务端没什么区别。这样一来,服务端和客户端就在通信协议上达到了统一。我们主要工作在MainActivity中完成。
重写ServiceConnection中的onServiceConnected方法将IBinder类型的对像转换成我们的IScreenshotControl类型。到现在我们就剩下最后一个步骤了,这个环节也是最为关键的,就是绑定我们需要的服务。我们通过服务端Service定义的“com.example.screenshotsample.start"”这个标识符来绑定其服务,这样客户端和服务端就实现了通信的连接,我们就可以调用IScreenshotControl中的系统截屏方法了。
package com.example.screenshotclient;
import com.example.screenshotsample.IScreenshotControl;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
public class MainActivity extends Activity {
protected static final String TAG = "MainActivity";
private TextView tv;
IScreenshotControl mScreenshotControll;
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
mScreenshotControll = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v(TAG, "onServiceConnected");
mScreenshotControll = IScreenshotControl.Stub.asInterface(service);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent("com.example.screenshotsample.start");
intent.setPackage("com.example.screenshotsample");
boolean result = bindService(intent, conn, Context.BIND_AUTO_CREATE);
tv = (TextView) findViewById(R.id.textView1);
findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
if (mScreenshotControll != null) {
Log.v(TAG, "onServiceConnected");
String path = mScreenshotControll.takeScreenshot();
tv.setText("截图文件保存在"+path);
}
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(conn);
}
}
运行结果
完整源码位置
服务端 https://github.com/OldDriver007/ScreenShotSampleServer
客户端https://github.com/OldDriver007/ScreenShotSampleClient