Android5.0系统截屏流程
来源:互联网 发布:淘宝的客服电话在那里 编辑:程序博客网 时间:2024/05/22 17:32
今天约了暗恋很久的女生去看电影,发现没带钱,值得庆幸的是,她像往常一样并没有赴约。
还是帮我踢下凳子吧谢谢
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
(1). 在AndroidManifest.xml文件中添加<uses-permissionandroid:name="android.permission.READ_FRAME_BUFFER"/>(2). 修改APK为系统权限,将APK放到源码中编译, 修改Android.mkLOCAL_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 implementscom.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);}@Overridepublic android.os.IBinder asBinder() {return this;}@Overridepublic 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 implementscom.example.screenshotsample.IScreenshotControl {private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) {mRemote = remote;}@Overridepublic android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic 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) {}}};@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.i(TAG, "I have started hehe---");return Service.START_STICKY;}@Overridepublic void onCreate() {super.onCreate();Log.e(TAG, "onCreate........");mScreenshotUtil=new ScreenshotUtil(this,mHandler);}@Overridepublic void onDestroy() {super.onDestroy();Log.e(TAG, "onDestroy........");}@Overridepublic 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() {@Overridepublic String takeScreenshot() throws RemoteException {// TODO Auto-generated method stubreturn 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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()) {@Overridepublic 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) {}}}@Overridepublic 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 := optionalLOCAL_SRC_FILES := $(call all-java-files-under,src) \src/com/example/screenshotsample/IScreenshotControl.aidlLOCAL_PACKAGE_NAME := ScreenShotSampleServerLOCAL_CERTIFICATE := platforminclude $(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() {@Overridepublic void onServiceDisconnected(ComponentName name) {mScreenshotControll = null;}@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.v(TAG, "onServiceConnected");mScreenshotControll = IScreenshotControl.Stub.asInterface(service);}};@Overrideprotected 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() {@Overridepublic 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 blocke.printStackTrace();}}});}@Override protected void onDestroy() { super.onDestroy(); unbindService(conn); }}
运行结果
完整源码位置
服务端 https://github.com/OldDriver007/ScreenShotSampleServer
客户端https://github.com/OldDriver007/ScreenShotSampleClient
- Android5.0系统截屏流程
- Android5.0系统字库
- Android5.0 呼叫流程--挂断
- Android5.0系统的优缺点
- Android5.0系统耗电分析
- Android5.0 InCall 通话界面显示流程
- Android5.0以太网流程源码情景分析
- android5.0设置模块音量调节流程
- Android5.0以太网流程源码情景分析
- Android5.0以太网流程源码情景分析
- Android5.0以太网流程源码情景分析
- Android5.0录屏
- Note2 N7100安装Android5.0系统
- 用Eclipse查看android5.0系统源代码
- Android5.0系统 知识点总结(持续更新)
- android5.0以上获取系统进程信息
- Android5.0输入系统移植方法详解
- android5.0输入系统添加自定义按键
- 关于把<script>标签放置的位置引发的关于html容错性的思考
- 对话框风格的Activity怎么设置主题:《Theme》
- NYOJ 阶乘因式分解(一)
- 游戏动作 NPC
- Function接口 – Java8中java.util.function包下的函数式接口
- Android5.0系统截屏流程
- Phoronix-test-suite benchmark toolkit
- getpid()
- POJ3074 Sudoku(Dancing Links)
- 视频播放
- poj1068
- Android: Android Light Sensor HOWTO
- 85. Maximal Rectangle
- Andoid开发技术资料搜集--ButterKnife篇