Android5.0系统截屏流程
2016-07-19 16:46
1106 查看
今天约了暗恋很久的女生去看电影,发现没带钱,值得庆幸的是,她像往常一样并没有赴约。
还是帮我踢下凳子吧谢谢
Android系统原生是自带截屏功能的,现在市面上有些三方应用可以截屏,但是一般都得Root,5.0之后已经不好root了,一般手机厂商都是自带截屏功能,除非你是系统签名的否则你不能截取任意页面的屏幕。
最简单的方法截屏,当然该方法不能截取任意应用的屏幕
第二种方法 5.0后已经可以支持截屏了 ,
http://blog.csdn.net/wds1181977/article/details/52174840
或者可以通过adb命令获取 截屏
该命令读取系统的framebuffer,需要获得系统权限:
(1). 在AndroidManifest.xml文件中添加
<uses-permissionandroid:name="android.permission.READ_FRAME_BUFFER"/>
(2). 修改APK为系统权限,将APK放到源码中编译, 修改Android.mk
LOCAL_CERTIFICATE := platform
测试发现该方法可以实现
2y
当然本文是介绍系统的截屏
既然已经有系统权限了就用不着那么好心用上面的方法了,所以跟踪一下5.0系统截屏是怎么实现的,步步高打火机哪里不会点哪里,
截屏功能So easy只需一行代码就是实现了,还真是个深思熟虑的选择
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里面监听音量下键+关机键然后开启线程执行截屏服务
音量下键+关机键然后开启线程
SystemUI专门有个截屏包,刚才启动的就是TakeScreenshotService服务,核心截屏功能在GlobalScreenshot
TakeScreenshotService服务调用GlobalScreenshot takeScreenshot()方法
我们来看看GlobalScreenshot具体流程SurfaceControl.screenshot
看看系统获取整个屏幕的宽高是怎样的
这里就是 截屏的整个核心,首先获得屏幕宽高、旋转屏幕时的宽高的获取,然后通过SurfaceControl.screenshot()获得Bitmap 最后执行截屏动画
SurfaceControl.screenshot()在往下就是 native方法了
jni往下就是C++文件,android_view_Surface.cpp中,这里就不往下跟了
继续刚才 在获取Bitmap后 执行动画时会有拍照声
下面就是 执行动画
保存截屏数据对象,刚才截屏获得的mScreenBitmap 赋值给对象SaveImageInBackgroundData
然后执行一个AsyncTask,主要是目的是把截图插入MediaStore图库,并发一个通知 ,告诉用户截图位置和截图样式
执行到这里 截图流程就完了,你是时候表演真正的技术了
如果想系统开发调用此流程可以直接想用音量键+电源键的流程直接调用 TakeScreenshotService服务就可以了,不需要重写
接下来试着去写个截屏应用了
编写两个应用
AIDL服务端ScreenShotSampleServer
(系统签名的应用)用来调用系统截屏方法
客户端ScreenShotSampleClient
为第三方应用 用来调用服务端的接口来实现截屏
首先实现服务端
配置文件系统签名
首先定义AIDL接口Eclipse插件的帮助下,编译器会自动在gen目录中生成对应的IScreenshotControl,接口中的抽象内部类Stub继承android.os.Binder类并实现IScreenshotControl接口,比较重要的方法是asInterface(IBinder)方法,该方法会将IBinder类型的对象转换成IScreenshotControl类型,必要的时候生成一个代理对象返回结果。
接下来就是我们的Service了:
把系统截屏方法集成到ScreenshotUtil里,和之前音量键+电源键的方法是一样的
编写mk 文件,并编译到源码刚才一场大雨
这样服务端就完成了
客户端的实现就简单了,
我们只需要把IScreenshotControl.aidl文件拷到相应的目录中即可,编译器同样会生成相对应的IScreenshotControl.java文件,这一部分和服务端没什么区别。这样一来,服务端和客户端就在通信协议上达到了统一。我们主要工作在MainActivity中完成。
重写ServiceConnection中的onServiceConnected方法将IBinder类型的对像转换成我们的IScreenshotControl类型。到现在我们就剩下最后一个步骤了,这个环节也是最为关键的,就是绑定我们需要的服务。我们通过服务端Service定义的“com.example.screenshotsample.start"”这个标识符来绑定其服务,这样客户端和服务端就实现了通信的连接,我们就可以调用IScreenshotControl中的系统截屏方法了。
运行结果
完整源码位置
服务端 https://github.com/OldDriver007/ScreenShotSampleServer
客户端https://github.com/OldDriver007/ScreenShotSampleClient
还是帮我踢下凳子吧谢谢
Android系统原生是自带截屏功能的,现在市面上有些三方应用可以截屏,但是一般都得Root,5.0之后已经不好root了,一般手机厂商都是自带截屏功能,除非你是系统签名的否则你不能截取任意页面的屏幕。
最简单的方法截屏,当然该方法不能截取任意应用的屏幕
View.getDrawingCache()
第二种方法 5.0后已经可以支持截屏了 ,
Android5.0免Root截屏,录屏看这里
http://blog.csdn.net/wds1181977/article/details/52174840或者可以通过adb命令获取 截屏
adb shell screencap -pfilepath
该命令读取系统的framebuffer,需要获得系统权限:
(1). 在AndroidManifest.xml文件中添加
<uses-permissionandroid: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
相关文章推荐
- 在 Linux 下截屏并编辑的最佳工具
- ScreenCloud:一个增强的截屏程序
- Delphi实现截屏存盘的方法
- Android 监听锁屏、解锁、开屏 功能代码
- Android编程之截屏实现方法(包括scrollview与listview)
- java编程实现屏幕截图(截屏)代码总结
- Python实现截屏的函数
- Python实现截屏的函数
- Android 屏幕截屏方法汇总
- Android实现截屏并保存操作功能
- Android 使用Shell脚本截屏并自动传到电脑上
- asp.net截屏功能实现截取web页面
- 使用python编写android截屏脚本双击运行即可
- Android 手绘 - 支持保存为图片
- 浅谈android截屏问题
- 使用Qt作窗口截屏(含源码)
- JAVA屏幕截图与水印添加程序-HEHEHEScreenshot
- Android静态界面截屏
- ABBYY 中的Screenshot Reader是什么含义
- 如何更好的运用ABBYY Screenshot Reader