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

该命令读取系统的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 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













0 0
原创粉丝点击