截屏源码分析(续)

上节谈到源码截屏需要调用类PhoneWindowManager中接口函数takeScreenshot(),下面我们主要分析一下截屏操作是如何实现的:


第一步,进入函数takeScreenshot中,
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
private void takeScreenshot() {
        synchronized (mScreenshotLock) {
            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) {
                        Messenger messenger = new Messenger(service);
                        Message msg = Message.obtain(null, 1);
                        final ServiceConnection myConn = this;
                        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;
                            messenger.send(msg);

                    }
                }
            };
            if (mContext.bindService(
                    intent, conn, Context.BIND_AUTO_CREATE, UserHandle.USER_CURRENT)) {
                mScreenshotConnection = conn;
                mHandler.postDelayed(mScreenshotTimeout, 10000);
            }
        }
    }
首先通过ComponentName获悉,需要绑定的服务是TakeScreenshotService,Message.obtain(null, 1)可知msg.what设为1;当mStatusBar(状态栏)和mNavigationBar(导航条)不为空,而且isVisibleLw为true时,msg.arg1和msg.arg2分别为1。最后通过bindService启动服务TakeScreenshotService。

第二步,进入TakeScreenshotService服务
frameworks/base/packages/SystemUI/src/com/android/systemui/TakeScreenshotService.java
public class TakeScreenshotService extends Service {
    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);
            }
        }
    };
}
根据第一步的分析获悉,msg.what应该是1,由mHandler对消息进行处理,假如第一次启动服务TakeScreenshotService,则对象mScreenshot为空,则需要创建一个GlobalScreenshot的对象,通过调用对象mScreenshot中函数takeScreenshot实现截屏功能。

第三步,进入类GlobalScreenshot
frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ GlobalScreenshot.java
在介绍GlobalScreenshot之前,我们先了解一下类SaveImageInBackgroundData,该类的实现如下,
class SaveImageInBackgroundData {
    Context context;
    Bitmap image;
    Uri imageUri;
    Runnable finisher;
    int iconSize;
    int result;
}
其中,image用来保存截屏;imageUri保存截屏存放路径;finisher处理截屏的一个线程;iconSize截屏占用空间,result截屏结果,0表示截屏成功,1表示截屏失败。

public GlobalScreenshot(Context context) {
    / Setup the window that we are going to use
        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);

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mNotificationManager =
            (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        // Get the various target sizes
        mNotificationIconSize =
            r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
        // Scale has to account for both sides of the bg
        mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
        mBgPaddingScale = mBgPadding /  mDisplayMetrics.widthPixels;
        // Setup the Camera shutter sound         mCameraSound = new MediaActionSound();         mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
}
首先通过构造函数GlobalScreenshot(Context context)创建一个对象,mWindowLayoutParams为窗口布局设置参数;mNotificationManager启动通知栏服务;mNotificationIconSize通知栏图标大小设置;mBgPadding截屏边框背景样式;mBgPaddingScale边框宽度;mCameraSound截屏声音设置。

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};
        float degrees = getDegreesForRotation(mDisplay.getRotation());
        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]);
        }

        // Take the screenshot
        mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);

        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);
            mScreenBitmap = ss;
        }

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

        // Start the post-screenshot animation
        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
                statusBarVisible, navBarVisible);
    }
数组dims表示屏幕显示长宽像素数;degree表示屏幕自动旋转度数;如果屏幕自动旋转,则需要进行调整,通过Surface.screenshot((int) dims[0], (int) dims[1])将屏幕截屏,并暂时保存在变量mScreenBitmap中,紧接着调用 startAnimation,startAnimation函数主要实现两个功能:第一,实现截屏的动画效果;第二,将所获取截屏保存在指定的路径下。

第四步,下面继续分析startAnimation函数
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();
        }

        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);
            }
        });
        mScreenshotLayout.post(new Runnable() {
            @Override
            public void run() {
                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
                mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                mScreenshotView.buildLayer();
                mScreenshotAnimation.start();
            }
        });
    }
函数createScreenshotDropInAnimation和createScreenshotDropOutAnimation分别表示截屏动画进入和退出。具体实现就不再分析,随后创建了AnimatorSet对象mScreenshotAnimation,该对象增加一个接口进行监听,主要实现截屏保存等操作。由mScreenshotLayout发送消息启动该操作,进入函数saveScreenshotInWorkerThread中。

private void saveScreenshotInWorkerThread(Runnable finisher) {
        SaveImageInBackgroundData data = new SaveImageInBackgroundData();
        data.context = mContext;
        data.image = mScreenBitmap;
        data.iconSize = mNotificationIconSize;
        data.finisher = finisher;
        new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
                SCREENSHOT_NOTIFICATION_ID).execute(data);
    }
在函数saveScreenshotInWorkerThread中,首先创建一个SaveImageInBackgroundData对象,我们之前已经介绍过,为该对象中的变量进行赋值,并调用SaveImageInBackgroundTask构造函数。

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ GlobalScreenshot.java
SaveImageInBackgroundTask类与GlobalScreenshot在同一个java文件下

class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void,
        SaveImageInBackgroundData> {

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));   

        String imageDir = Environment.getExternalStoragePublicDirectory(
               Environment.DIRECTORY_PICTURES).getAbsolutePath();   

        mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
        mImageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, imageDir,
                SCREENSHOTS_DIR_NAME, mImageFileName);

        // Create the large notification icon
        int imageWidth = data.image.getWidth();
        int imageHeight = data.image.getHeight();
        int iconSize = data.iconSize;

        final int shortSide = imageWidth < imageHeight ? imageWidth : imageHeight;
        Bitmap preview = Bitmap.createBitmap(shortSide, shortSide, 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((shortSide - imageWidth) / 2,
                            (shortSide - imageHeight) / 2);
        c.drawBitmap(data.image, matrix, paint);
        c.drawColor(0x40FFFFFF);

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

        // Show the intermediate notification
        mTickerAddSpace = !mTickerAddSpace;
        mNotificationId = nId;
        mNotificationManager = nManager;
        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(System.currentTimeMillis());

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

        Notification n = mNotificationBuilder.build();
        n.flags |= Notification.FLAG_NO_CLEAR;
        mNotificationManager.notify(nId, n);
        mNotificationBuilder.setLargeIcon(croppedIcon);
        mNotificationStyle.bigLargeIcon(null);
    }
imageDate截屏文件名称;imageDir 截屏文件保存路径;shortSide手机屏幕宽度;preview创建长宽都为shortSide的Bitmap对象;mNotificationBuilder通知栏通知信息设置;

protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) {
        if (params.length != 1) 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 {
            // 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, mImageTime);
            values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, mImageTime);
            values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
            /// M: FOR ALPS00266037 & ALPS00289039 pic taken by phone shown wrong on cumputer. @{
            values.put(MediaStore.Images.ImageColumns.WIDTH, image.getWidth());
            values.put(MediaStore.Images.ImageColumns.HEIGHT, image.getHeight());
            /// M: FOR ALPS00266037 & ALPS00289039. @}
            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

            OutputStream out = resolver.openOutputStream(uri);
            image.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.flush();
            out.close();

            // update file size in the database
            values.clear();            
            params[0].imageUri = uri;
            params[0].result = 0;
        } catch (Exception e) {
            // IOException/UnsupportedOperationException may be thrown if external storage is not
            // mounted
            params[0].result = 1;
        }

        return params[0];
}
由于保存文件需要耗时,故该操作在doInBackground函数中进行,即在后台运行。设置该线程的优先级为THREAD_PRIORITY_FOREGROUND,创建values变量,由ContentResolver调用insert将该截屏文件加入到media数据库中,以方便在图库中进行查找;将截屏文件所保存路径/大小/截屏结果等信息保存在params[0]中,并返回给界面进行处理,如果截屏图片出错,则params[0].result = 1;

截屏函数takeScreenshot分析到此结束了,其中与底层进行交互的位于第三步Surface.screenshot((int) dims[0], (int) dims[1])。Framework层的Surface.java提供一个native方法,实际实现在JNI处的android_view_Surface.cpp中的nativeScreenshot(...)方法。
该函数主要功能包括以下几个:
第一,启动TakeScreenshotService服务
第二,创建GlobalScreenshot对象,由该对象进行手机截屏操作。
第三,将所获取的截屏加入到media数据库中,以方便图库中查找。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值