Android5.0系统截屏流程

       今天约了暗恋很久的女生去看电影,发现没带钱,值得庆幸的是,她像往常一样并没有赴约。

还是帮我踢下凳子吧谢谢

                                                 

                                                            

       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-permission android:name = "android.permission.READ_FRAME_BUFFER" />
(2). 修改APK为系统权限,将APK放到源码中编译, 修改Android.mk
 LOCAL_CERTIFICATE := platform
publicvoid takeScreenShot(){ 
    String mSavedPath = Environment.getExternalStorageDirectory()+File. separator + "screenshot.png" ; 
try {                     
           Runtime. getRuntime().exec("screencap -p " + mSavedPath); 
    } catch (Exception e) { 
           e.printStackTrace(); 
    } 

测试发现该方法可以实现

2y





当然本文是介绍系统的截屏

既然已经有系统权限了就用不着那么好心用上面的方法了,所以跟踪一下5.0系统截屏是怎么实现的,步步高打火机哪里不会点哪里,


                                                                   



截屏功能So easy只需一行代码就是实现了,还真是个深思熟虑的选择

 mScreenBitmap = SurfaceControl.screenshot((int) 屏幕高, (int) dims屏幕宽);

SurfaceControl.screenshot()方法需要系统签名才可以 ,通过反射也需要系统签名,所以第三方应用不能调用这个个方法



原生Android系统是默认是同时按下音量下键+关机键 时会截屏


手机厂商现在都改到快捷下拉位置,两者调用的都是同一种方法



接下来看看系统原生截屏的整个流程


和截屏相关的有如下几个文件:

frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindowManager.java

frameworks\base\packages\SystemUI\src\com\android\systemui\screenshot\GlobalScreenshot


PhoneWindowManager.java里面监听音量下键+关机键然后开启线程执行截屏服务



音量下键+关机键然后开启线程

 private void interceptScreenshotChord() {
        if (mScreenshotChordEnabled
                && mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {
            final long now = SystemClock.uptimeMillis();
            if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
                    && now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
                mVolumeDownKeyConsumedByScreenshotChord = true;
                cancelPendingPowerKeyAction();

                mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
            }
        }
    }
启动截屏服务其实是 SystemUI里的 TakeScreenshotService这个函数使用AIDL绑定了service服务到"com.android.systemui.screenshot.TakeScreenshotService",注意在service连接成功时,对message的msg.arg1和msg.arg2两个参数的赋值。其中在mScreenshotTimeout中对服务service做了超时处理
private void takeScreenshot() {
        synchronized (mScreenshotLock) {
            if (mScreenshotConnection != null) {
                return;
            }
            ComponentName cn = new ComponentName("com.android.systemui",
                    "com.android.systemui.screenshot.TakeScreenshotService");
            Intent intent = new Intent();
            intent.setComponent(cn);
            ServiceConnection conn = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    synchronized (mScreenshotLock) {
                        if (mScreenshotConnection != this) {
                            return;
                        }
                        Messenger messenger = new Messenger(service);
                        Message msg = Message.obtain(null, 1);
                        final ServiceConnection myConn = this;
                        Handler h = new Handler(mHandler.getLooper()) {
                            @Override
                            public void handleMessage(Message msg) {
                                synchronized (mScreenshotLock) {
                                    if (mScreenshotConnection == myConn) {
                                        mContext.unbindService(mScreenshotConnection);
                                        mScreenshotConnection = null;
                                        mHandler.removeCallbacks(mScreenshotTimeout);
                                    }
                                }
                            }
                        };
                        msg.replyTo = new Messenger(h);
                        msg.arg1 = msg.arg2 = 0;
                        if (mStatusBar != null && mStatusBar.isVisibleLw())
                            msg.arg1 = 1;
                        if (mNavigationBar != null && mNavigationBar.isVisibleLw())
                            msg.arg2 = 1;
                        try {
                            messenger.send(msg);
                        } catch (RemoteException e) {
                        }
                    }
                }
                @Override
                public void onServiceDisconnected(ComponentName name) {}
            };
            if (mContext.bindServiceAsUser(
                    intent, conn, Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
                mScreenshotConnection = conn;
                mHandler.postDelayed(mScreenshotTimeout, 10000);
            }
        }
    }

SystemUI专门有个截屏包,刚才启动的就是TakeScreenshotService服务,核心截屏功能在GlobalScreenshot



TakeScreenshotService服务调用GlobalScreenshot takeScreenshot()方法

public class TakeScreenshotService extends Service {
    private static final String TAG = "TakeScreenshotService";

    private static GlobalScreenshot mScreenshot;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    final Messenger callback = msg.replyTo;
                    if (mScreenshot == null) {
                        mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
                    }
                    mScreenshot.takeScreenshot(new Runnable() {
                        @Override public void run() {
                            Message reply = Message.obtain(null, 1);
                            try {
                                callback.send(reply);
                            } catch (RemoteException e) {
                            }
                        }
                    }, msg.arg1 > 0, msg.arg2 > 0);
            }
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return new Messenger(mHandler).getBinder();
    }
}



我们来看看GlobalScreenshot具体流程SurfaceControl.screenshot




看看系统获取整个屏幕的宽高是怎样的

     mWindowLayoutParams = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
                WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY,
                WindowManager.LayoutParams.FLAG_FULLSCREEN
                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
                PixelFormat.TRANSLUCENT);
        mWindowLayoutParams.setTitle("ScreenshotAnimation");
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mNotificationManager =
            (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        mDisplay = mWindowManager.getDefaultDisplay();
        mDisplayMetrics = new DisplayMetrics();
        mDisplay.getRealMetrics(mDisplayMetrics);</span>


这里就是 截屏的整个核心,首先获得屏幕宽高、旋转屏幕时的宽高的获取,然后通过SurfaceControl.screenshot()获得Bitmap 最后执行截屏动画


void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
        // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
        // only in the natural orientation of the device :!)
        mDisplay.getRealMetrics(mDisplayMetrics);
        float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
        /// M: [SystemUI] Support Smartbook Feature. @{
        boolean isPlugIn =
            com.mediatek.systemui.statusbar.util.SIMHelper.isSmartBookPluggedIn(mContext);
        if (isPlugIn) {
            dims[0] = mDisplayMetrics.heightPixels;
            dims[1] = mDisplayMetrics.widthPixels;
        }
        /// @}
        float degrees = getDegreesForRotation(mDisplay.getRotation());
        Xlog.d("takeScreenshot", "dims = " + dims[0] + "," + dims[1] + " of " + degrees);
        boolean requiresRotation = (degrees > 0);
        if (requiresRotation) {
            // Get the dimensions of the device in its native orientation
            mDisplayMatrix.reset();
            mDisplayMatrix.preRotate(-degrees);
            mDisplayMatrix.mapPoints(dims);
            dims[0] = Math.abs(dims[0]);
            dims[1] = Math.abs(dims[1]);
            Xlog.d("takeScreenshot", "reqRotate, dims = " + dims[0] + "," + dims[1]);
        }

        // Take the screenshot
        /// M: [SystemUI] Support Smartbook Feature. @{
        if (isPlugIn) {
            mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1],
                            SurfaceControl.BUILT_IN_DISPLAY_ID_HDMI);
            degrees = 270f - degrees;
        }
        /// @}
         else {
            mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
        }
        if (mScreenBitmap == null) {
            Xlog.d("takeScreenshot", "mScreenBitmap == null, " + dims[0] + "," + dims[1]);
            notifyScreenshotError(mContext, mNotificationManager);
            finisher.run();
            return;
        }

        if (requiresRotation) {
            // Rotate the screenshot to the current orientation
            Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
                    mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(ss);
            c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
            c.rotate(degrees);
            c.translate(-dims[0] / 2, -dims[1] / 2);
            c.drawBitmap(mScreenBitmap, 0, 0, null);
            c.setBitmap(null);
            // Recycle the previous bitmap
            mScreenBitmap.recycle();
            mScreenBitmap = ss;
        }

        // Optimizations
        mScreenBitmap.setHasAlpha(false);
        mScreenBitmap.prepareToDraw();

        // Start the post-screenshot animation
        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
                statusBarVisible, navBarVisible);
    }


SurfaceControl.screenshot()在往下就是 native方法了

    /**
     * Like {@link #screenshot(int, int, int, int)} but includes all
     * Surfaces in the screenshot.
     *
     * @hide
     */
    public static native Bitmap screenshot(int width, int height);

jni往下就是C++文件,android_view_Surface.cpp中,这里就不往下跟了

static jobject doScreenshot(JNIEnv* env, jobject clazz, jint width, jint height,
        jint minLayer, jint maxLayer, bool allLayers)
{
    ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
    if (pixels->update(width, height, minLayer, maxLayer, allLayers) != NO_ERROR) {
        delete pixels;
        return 0;
    }

    uint32_t w = pixels->getWidth();
    uint32_t h = pixels->getHeight();
    uint32_t s = pixels->getStride();
    uint32_t f = pixels->getFormat();
    ssize_t bpr = s * android::bytesPerPixel(f);

    SkBitmap* bitmap = new SkBitmap();
    bitmap->setConfig(convertPixelFormat(f), w, h, bpr);
    if (f == PIXEL_FORMAT_RGBX_8888) {
        bitmap->setIsOpaque(true);
    }

    if (w > 0 && h > 0) {
        bitmap->setPixelRef(pixels)->unref();
        bitmap->lockPixels();
    } else {
        // be safe with an empty bitmap.
        delete pixels;
        bitmap->setPixels(NULL);
    }

    return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
}



继续刚才 在获取Bitmap后 执行动画时会有拍照声

  // Setup the Camera shutter sound
        mCameraSound = new MediaActionSound();
        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
  mCameraSound.play(MediaActionSound.SHUTTER_CLICK);

下面就是 执行动画


   /**
     * Starts the animation after taking the screenshot
     */
    private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
            boolean navBarVisible) {
        // Add the view for the animation
        mScreenshotView.setImageBitmap(mScreenBitmap);
        mScreenshotLayout.requestFocus();

        // Setup the animation with the screenshot just taken
        if (mScreenshotAnimation != null) {
            mScreenshotAnimation.end();
            mScreenshotAnimation.removeAllListeners();
        }

        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
        ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
        ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
                statusBarVisible, navBarVisible);
        mScreenshotAnimation = new AnimatorSet();
        mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
        mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                // Save the screenshot once we have a bit of time now
                saveScreenshotInWorkerThread(finisher);
                mWindowManager.removeView(mScreenshotLayout);

                // Clear any references to the bitmap
                mScreenBitmap = null;
                mScreenshotView.setImageBitmap(null);
            }
        });
        mScreenshotLayout.post(new Runnable() {
            @Override
            public void run() {
                /// M: [ALPS01233166] Check if this view is currently attached to a window.
                if (!mScreenshotView.isAttachedToWindow()) {
                    Xlog.d(TAG, "this view is currently not attached to a window");
                    return;
                }
                // Play the shutter sound to notify that we've taken a screenshot
                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);

                mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                mScreenshotView.buildLayer();
                mScreenshotAnimation.start();
            }
        });
    }


保存截屏数据对象,刚才截屏获得的mScreenBitmap 赋值给对象SaveImageInBackgroundData

  private void saveScreenshotInWorkerThread(Runnable finisher) {
        SaveImageInBackgroundData data = new SaveImageInBackgroundData();
        data.context = mContext;
        data.image = mScreenBitmap;
        data.iconSize = mNotificationIconSize;
        data.finisher = finisher;
        data.previewWidth = mPreviewWidth;
        data.previewheight = mPreviewHeight;
        if (mSaveInBgTask != null) {
            mSaveInBgTask.cancel(false);
        }
        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
                SCREENSHOT_NOTIFICATION_ID).execute(data);
    }

然后执行一个AsyncTask,主要是目的是把截图插入MediaStore图库,并发一个通知 ,告诉用户截图位置和截图样式


/**
 * An AsyncTask that saves an image to the media store in the background.
 */
class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void,
        SaveImageInBackgroundData> {
    private static final String TAG = "SaveImageInBackgroundTask";

    private static final String SCREENSHOTS_DIR_NAME = "Screenshots";
    private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
    private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";

    private final int mNotificationId;
    private final NotificationManager mNotificationManager;
    private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
    private final File mScreenshotDir;
    private final String mImageFileName;
    private final String mImageFilePath;
    private final long mImageTime;
    private final BigPictureStyle mNotificationStyle;
    private final int mImageWidth;
    private final int mImageHeight;

    // WORKAROUND: We want the same notification across screenshots that we update so that we don't
    // spam a user's notification drawer.  However, we only show the ticker for the saving state
    // and if the ticker text is the same as the previous notification, then it will not show. So
    // for now, we just add and remove a space from the ticker text to trigger the animation when
    // necessary.
    private static boolean mTickerAddSpace;

    SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
            NotificationManager nManager, int nId) {
        Resources r = context.getResources();

        // Prepare all the output metadata
        mImageTime = System.currentTimeMillis();
        String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime));
        mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);

        mScreenshotDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), SCREENSHOTS_DIR_NAME);
        mImageFilePath = new File(mScreenshotDir, mImageFileName).getAbsolutePath();

        // Create the large notification icon
        mImageWidth = data.image.getWidth();
        mImageHeight = data.image.getHeight();
        int iconSize = data.iconSize;
        int previewWidth = data.previewWidth;
        int previewHeight = data.previewheight;

        final int shortSide = mImageWidth < mImageHeight ? mImageWidth : mImageHeight;
        Bitmap preview = Bitmap.createBitmap(previewWidth, previewHeight, data.image.getConfig());
        Canvas c = new Canvas(preview);
        Paint paint = new Paint();
        ColorMatrix desat = new ColorMatrix();
        desat.setSaturation(0.25f);
        paint.setColorFilter(new ColorMatrixColorFilter(desat));
        Matrix matrix = new Matrix();
        matrix.postTranslate((previewWidth - mImageWidth) / 2,
                            (previewHeight - mImageHeight) / 2);
        c.drawBitmap(data.image, matrix, paint);
        c.drawColor(0x40FFFFFF);
        c.setBitmap(null);

        Bitmap croppedIcon = Bitmap.createScaledBitmap(preview, iconSize, iconSize, true);

        // Show the intermediate notification
        mTickerAddSpace = !mTickerAddSpace;
        mNotificationId = nId;
        mNotificationManager = nManager;
        final long now = System.currentTimeMillis();

        mNotificationBuilder = new Notification.Builder(context)
            .setTicker(r.getString(R.string.screenshot_saving_ticker)
                    + (mTickerAddSpace ? " " : ""))
            .setContentTitle(r.getString(R.string.screenshot_saving_title))
            .setContentText(r.getString(R.string.screenshot_saving_text))
            .setSmallIcon(R.drawable.stat_notify_image)
            .setWhen(now)
            .setColor(r.getColor(com.android.internal.R.color.system_notification_accent_color));

        mNotificationStyle = new Notification.BigPictureStyle()
            .bigPicture(preview);
        mNotificationBuilder.setStyle(mNotificationStyle);

        // For "public" situations we want to show all the same info but
        // omit the actual screenshot image.
        mPublicNotificationBuilder = new Notification.Builder(context)
                .setContentTitle(r.getString(R.string.screenshot_saving_title))
                .setContentText(r.getString(R.string.screenshot_saving_text))
                .setSmallIcon(R.drawable.stat_notify_image)
                .setCategory(Notification.CATEGORY_PROGRESS)
                .setWhen(now)
                .setColor(r.getColor(
                        com.android.internal.R.color.system_notification_accent_color));

        mNotificationBuilder.setPublicVersion(mPublicNotificationBuilder.build());

        Notification n = mNotificationBuilder.build();
        n.flags |= Notification.FLAG_NO_CLEAR;
        mNotificationManager.notify(nId, n);

        // On the tablet, the large icon makes the notification appear as if it is clickable (and
        // on small devices, the large icon is not shown) so defer showing the large icon until
        // we compose the final post-save notification below.
        mNotificationBuilder.setLargeIcon(croppedIcon);
        // But we still don't set it for the expanded view, allowing the smallIcon to show here.
        mNotificationStyle.bigLargeIcon(null);
    }

    @Override
    protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) {
        if (params.length != 1) return null;
        if (isCancelled()) {
            params[0].clearImage();
            params[0].clearContext();
            return null;
        }

        // By default, AsyncTask sets the worker thread to have background thread priority, so bump
        // it back up so that we save a little quicker.
        Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);

        Context context = params[0].context;
        Bitmap image = params[0].image;
        Resources r = context.getResources();

        try {
            // Create screenshot directory if it doesn't exist
            mScreenshotDir.mkdirs();

            // media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds
            // for DATE_TAKEN
            long dateSeconds = mImageTime / 1000;

            // Save the screenshot to the MediaStore
            ContentValues values = new ContentValues();
            ContentResolver resolver = context.getContentResolver();
            values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
            values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
            values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
            values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
            values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
            values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
            values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
            values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
            values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

            String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
            String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
            sharingIntent.setType("image/png");
            sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);

            Intent chooserIntent = Intent.createChooser(sharingIntent, null);
            chooserIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
                    | Intent.FLAG_ACTIVITY_NEW_TASK);

            mNotificationBuilder.addAction(R.drawable.ic_menu_share,
                     r.getString(com.android.internal.R.string.share),
                     PendingIntent.getActivity(context, 0, chooserIntent,
                             PendingIntent.FLAG_CANCEL_CURRENT));

            OutputStream out = resolver.openOutputStream(uri);
            boolean bCompressOK = image.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.flush();
            out.close();
            /// M: [ALPS00800619] Handle Compress Fail Case.
            if (!bCompressOK) {
                resolver.delete(uri, null, null);
                params[0].result = 1;
                return params[0];
            }

            // update file size in the database
            values.clear();
            /// M: FOR ALPS00266037 & ALPS00289039 pic taken by phone shown wrong on cumputer. @{
            InputStream inputStream = resolver.openInputStream(uri);
            int size = inputStream.available();
            inputStream.close();
            values.put(MediaStore.Images.ImageColumns.SIZE, size);

            // values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
            uri = uri.buildUpon().appendQueryParameter("notifyMtp", "1").build();
            /// M: FOR ALPS00266037 & ALPS00289039. @}
            resolver.update(uri, values, null, null);

            params[0].imageUri = uri;
            params[0].image = null;
            params[0].result = 0;
        } catch (Exception e) {
            // IOException/UnsupportedOperationException may be thrown if external storage is not
            // mounted
            params[0].clearImage();
            params[0].result = 1;
        }

        // Recycle the bitmap data
        if (image != null) {
            image.recycle();
        }

        return params[0];
    }

    @Override
    protected void onPostExecute(SaveImageInBackgroundData params) {
        if (isCancelled()) {
            params.finisher.run();
            params.clearImage();
            params.clearContext();
            return;
        }

        if (params.result > 0) {
            // Show a message that we've failed to save the image to disk
            GlobalScreenshot.notifyScreenshotError(params.context, mNotificationManager);
        } else {
            // Show the final notification to indicate screenshot saved
            Resources r = params.context.getResources();

            // Create the intent to show the screenshot in gallery
            Intent launchIntent = new Intent(Intent.ACTION_VIEW);
            launchIntent.setDataAndType(params.imageUri, "image/png");
            launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

            final long now = System.currentTimeMillis();

            mNotificationBuilder
                .setContentTitle(r.getString(R.string.screenshot_saved_title))
                .setContentText(r.getString(R.string.screenshot_saved_text))
                .setContentIntent(PendingIntent.getActivity(params.context, 0, launchIntent, 0))
                .setWhen(now)
                .setAutoCancel(true)
                .setColor(r.getColor(
                        com.android.internal.R.color.system_notification_accent_color));;

            // Update the text in the public version as well
            mPublicNotificationBuilder
                .setContentTitle(r.getString(R.string.screenshot_saved_title))
                .setContentText(r.getString(R.string.screenshot_saved_text))
                .setContentIntent(PendingIntent.getActivity(params.context, 0, launchIntent, 0))
                .setWhen(now)
                .setAutoCancel(true)
                .setColor(r.getColor(
                        com.android.internal.R.color.system_notification_accent_color));

            mNotificationBuilder.setPublicVersion(mPublicNotificationBuilder.build());

            Notification n = mNotificationBuilder.build();
            n.flags &= ~Notification.FLAG_NO_CLEAR;
            mNotificationManager.notify(mNotificationId, n);
        }
        params.finisher.run();
        params.clearContext();
    }
}

执行到这里 截图流程就完了,你是时候表演真正的技术了


如果想系统开发调用此流程可以直接想用音量键+电源键的流程直接调用 TakeScreenshotService服务就可以了,不需要重写




接下来试着去写个截屏应用了




                                                                              

编写两个应用 

AIDL服务端ScreenShotSampleServer

(系统签名的应用)用来调用系统截屏方法


客户端ScreenShotSampleClient

为第三方应用  用来调用服务端的接口来实现截屏

首先实现服务端



配置文件系统签名

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.XXX.XXX"
    android:versionCode="1"
    android:versionName="1.0"
    android:sharedUserId="android.uid.system" >


首先定义AIDL接口Eclipse插件的帮助下,编译器会自动在gen目录中生成对应的IScreenshotControl,接口中的抽象内部类Stub继承android.os.Binder类并实现IScreenshotControl接口,比较重要的方法是asInterface(IBinder)方法,该方法会将IBinder类型的对象转换成IScreenshotControl类型,必要的时候生成一个代理对象返回结果。

interface IScreenshotControl{


     String takeScreenshot();


} 

<pre name="code" class="java">/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: F:\\workspace2\\ScreenShotSampleServer\\src\\com\\example\\screenshotsample\\IScreenshotControl.aidl
 */
package com.example.screenshotsample;

public interface IScreenshotControl extends android.os.IInterface {
	/** Local-side IPC implementation stub class. */
	public static abstract class Stub extends android.os.Binder implements
			com.example.screenshotsample.IScreenshotControl {
		private static final java.lang.String DESCRIPTOR = "com.example.screenshotsample.IScreenshotControl";

		/** Construct the stub at attach it to the interface. */
		public Stub() {
			this.attachInterface(this, DESCRIPTOR);
		}

		/**
		 * Cast an IBinder object into an
		 * com.example.screenshotsample.IScreenshotControl interface, generating
		 * a proxy if needed.
		 */
		public static com.example.screenshotsample.IScreenshotControl asInterface(
				android.os.IBinder obj) {
			if ((obj == null)) {
				return null;
			}
			android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
			if (((iin != null) && (iin instanceof com.example.screenshotsample.IScreenshotControl))) {
				return ((com.example.screenshotsample.IScreenshotControl) iin);
			}
			return new com.example.screenshotsample.IScreenshotControl.Stub.Proxy(
					obj);
		}

		@Override
		public android.os.IBinder asBinder() {
			return this;
		}

		@Override
		public boolean onTransact(int code, android.os.Parcel data,
				android.os.Parcel reply, int flags)
				throws android.os.RemoteException {
			switch (code) {
			case INTERFACE_TRANSACTION: {
				reply.writeString(DESCRIPTOR);
				return true;
			}
			case TRANSACTION_takeScreenshot: {
				data.enforceInterface(DESCRIPTOR);
				java.lang.String _result = this.takeScreenshot();
				reply.writeNoException();
				reply.writeString(_result);
				return true;
			}
			}
			return super.onTransact(code, data, reply, flags);
		}

		private static class Proxy implements
				com.example.screenshotsample.IScreenshotControl {
			private android.os.IBinder mRemote;

			Proxy(android.os.IBinder remote) {
				mRemote = remote;
			}

			@Override
			public android.os.IBinder asBinder() {
				return mRemote;
			}

			public java.lang.String getInterfaceDescriptor() {
				return DESCRIPTOR;
			}

			@Override
			public java.lang.String takeScreenshot()
					throws android.os.RemoteException {
				android.os.Parcel _data = android.os.Parcel.obtain();
				android.os.Parcel _reply = android.os.Parcel.obtain();
				java.lang.String _result;
				try {
					_data.writeInterfaceToken(DESCRIPTOR);
					mRemote.transact(Stub.TRANSACTION_takeScreenshot, _data,
							_reply, 0);
					_reply.readException();
					_result = _reply.readString();
				} finally {
					_reply.recycle();
					_data.recycle();
				}
				return _result;
			}
		}

		static final int TRANSACTION_takeScreenshot = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
	}

	public java.lang.String takeScreenshot() throws android.os.RemoteException;
}


 

接下来就是我们的Service了:

package com.example.screenshotsample;

import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class ScreenshotControlService extends Service {
	
	private static final String BIND_SERVICE_ACTION = "com.example.screenshotsample.start";
	private static final String TAG = "ScreenshotControlService";
	 ScreenshotUtil mScreenshotUtil;
	
	
	
	private Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			switch (msg.what) {
			
			}

		}
	};
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.i(TAG, "I have started hehe---");
		return Service.START_STICKY;
	}
	
	@Override
	public void onCreate() {
		super.onCreate();
		Log.e(TAG, "onCreate........");
		mScreenshotUtil=	new ScreenshotUtil(this,mHandler);
		

	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		Log.e(TAG, "onDestroy........");
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		Log.v(TAG,"bind action is "+ intent.getAction());
		if (BIND_SERVICE_ACTION.equals(intent.getAction())) {
			return mSBinder;
		}
		return null;
	}
	
	public IScreenshotControl.Stub mSBinder = new IScreenshotControl.Stub() {

		@Override
		public String takeScreenshot() throws RemoteException {
			// TODO Auto-generated method stub
			return mScreenshotUtil.startScreenShot();
		}


	};	

}

把系统截屏方法集成到ScreenshotUtil里,和之前音量键+电源键的方法是一样的


package com.hitown.generalplan.control;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;

/**
 * @author 老司机
 *
 */
public class ScreenshotUtil {

	private File mScreenshotDir;
	private String mImageFileName;
	private String mImageFilePath;
	private static String SCREENSHOTS_DIR_NAME = "Screenshots";
	private Context mContext;
	private Handler mHandler;

	public ScreenshotUtil(Context context, Handler h) {
		mContext = context;
		mHandler = h;
		// 截屏保存位置
		mScreenshotDir = new File(
				Environment
						.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
				SCREENSHOTS_DIR_NAME);
		mImageFilePath = mScreenshotDir.getAbsolutePath();
	}

	public String startScreenShot() {

		mHandler.post(mScreenshotRunnable);

		return mImageFilePath;
	}

	private final Runnable mScreenshotRunnable = new Runnable() {
		@Override
		public void run() {

			// 这里可以加上延时,等待用户跳转到其他页面

			// try {
			// Thread.sleep(1000);
			// } catch (InterruptedException e) {
			// // TODO Auto-generated catch block
			// e.printStackTrace();
			// }

			takeScreenShot();
		}
	};

	final Object mScreenshotLock = new Object();
	ServiceConnection mScreenshotConnection = null;

	final Runnable mScreenshotTimeout = new Runnable() {
		@Override
		public void run() {
			synchronized (mScreenshotLock) {
				if (mScreenshotConnection != null) {
					mContext.unbindService(mScreenshotConnection);
					mScreenshotConnection = null;
				}
			}
		}
	};

	private void takeScreenShot() {

		synchronized (mScreenshotLock) {
			if (mScreenshotConnection != null) {
				return;
			}
			ComponentName cn = new ComponentName("com.android.systemui",
					"com.android.systemui.screenshot.TakeScreenshotService");
			Intent intent = new Intent();
			intent.setComponent(cn);
			ServiceConnection conn = new ServiceConnection() {
				@Override
				public void onServiceConnected(ComponentName name,
						IBinder service) {
					synchronized (mScreenshotLock) {
						if (mScreenshotConnection != this) {
							return;
						}
						Messenger messenger = new Messenger(service);
						Message msg = Message.obtain(null, 1);
						final ServiceConnection myConn = this;
						Handler h = new Handler(mHandler.getLooper()) {
							@Override
							public void handleMessage(Message msg) {
								synchronized (mScreenshotLock) {
									if (mScreenshotConnection == myConn) {
										mContext.unbindService(mScreenshotConnection);
										mScreenshotConnection = null;
										mHandler.removeCallbacks(mScreenshotTimeout);
									}
								}
							}
						};
						msg.replyTo = new Messenger(h);
						msg.arg1 = msg.arg2 = 0;
						// if (mStatusBar != null && mStatusBar.isVisibleLw())
						// msg.arg1 = 1;
						// if (mNavigationBar != null &&
						// mNavigationBar.isVisibleLw())
						// msg.arg2 = 1;
						try {
							messenger.send(msg);
						} catch (RemoteException e) {
						}
					}
				}

				@Override
				public void onServiceDisconnected(ComponentName name) {
				}
			};
			if (mContext.bindServiceAsUser(intent, conn,
					Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
				mScreenshotConnection = conn;
				mHandler.postDelayed(mScreenshotTimeout, 10000);
			}
		}
	}
}

编写mk 文件,并编译到源码刚才一场大雨

                                                           

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

LOCAL_SRC_FILES := $(call all-java-files-under,src) \
					src/com/example/screenshotsample/IScreenshotControl.aidl

LOCAL_PACKAGE_NAME := ScreenShotSampleServer

LOCAL_CERTIFICATE := platform

include $(BUILD_PACKAGE)



这样服务端就完成了


客户端的实现就简单了,


我们只需要把IScreenshotControl.aidl文件拷到相应的目录中即可,编译器同样会生成相对应的IScreenshotControl.java文件,这一部分和服务端没什么区别。这样一来,服务端和客户端就在通信协议上达到了统一。我们主要工作在MainActivity中完成。

重写ServiceConnection中的onServiceConnected方法将IBinder类型的对像转换成我们的IScreenshotControl类型。到现在我们就剩下最后一个步骤了,这个环节也是最为关键的,就是绑定我们需要的服务。我们通过服务端Service定义的“com.example.screenshotsample.start"”这个标识符来绑定其服务,这样客户端和服务端就实现了通信的连接,我们就可以调用IScreenshotControl中的系统截屏方法了。

package com.example.screenshotclient;

import com.example.screenshotsample.IScreenshotControl;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;

public class MainActivity extends Activity {
	protected static final String TAG = "MainActivity";
	private TextView tv;
	IScreenshotControl mScreenshotControll;
	ServiceConnection conn = new ServiceConnection() {

		@Override
		public void onServiceDisconnected(ComponentName name) {
			mScreenshotControll = null;
		}

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			Log.v(TAG, "onServiceConnected");
			mScreenshotControll = IScreenshotControl.Stub.asInterface(service);
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		Intent intent = new Intent("com.example.screenshotsample.start");
		intent.setPackage("com.example.screenshotsample");
    	boolean result = bindService(intent, conn, Context.BIND_AUTO_CREATE);
		tv = (TextView) findViewById(R.id.textView1);
		findViewById(R.id.button1).setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {

				try {
					if (mScreenshotControll != null) {
						Log.v(TAG, "onServiceConnected");
						String path = mScreenshotControll.takeScreenshot();

						tv.setText("截图文件保存在"+path);
					}
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
	}
	
	@Override
    protected void onDestroy() {
    	super.onDestroy();
    	unbindService(conn);
    }
	

}



运行结果





完整源码位置

    服务端 https://github.com/OldDriver007/ScreenShotSampleServer

    客户端https://github.com/OldDriver007/ScreenShotSampleClient













  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值