Android完美实现截屏

来源:互联网 发布:英语6级网络课程 编辑:程序博客网 时间:2024/06/05 09:39

很多app都有截屏的需求,当你遇到产品经理给你提出这个需求时,你搜索了一下:
1.取View的cacheDrawable 来实现截屏,这种方案,没有兼容性问题,但是缺点有两个:

  • 不能截状态栏
  • 遇到SurfaceView没辙,surfaceview需要用mediaplay手动取一帧buffer才行。
  • 不能在后台serivce中使用,因为主要依托于view。

2.java代run一个 adb 命令截屏。

  • 需要root。

我们之前也有这个需求,这个需求后来分给另外一个人做,当时他给我的答复就是这样子,这个我当时也是这么想的,毕竟以前调研过这个功能。
然后,我想想,好像我记得5.0以后可以直接录制屏幕为视频的,觉得这个还是自己搜索一下吧。

于是乎,我就搜索了一下,发现android 5.0以后开放了录屏API,那么所有的5.0以后的机器都应该取视频中的一帧数据,这样子我就可以实现截屏了。
这种方式的优点:

  • 可以后台,不单单只能自己 的app里面的页面,
  • 可以截状态栏了。

缺点:

  • 无法兼容5.0之前机器,这点可以不用太在意。

  • 需要一个先弹窗让用户允许。

5.0以前的机器,我们全民TV的用户统计数据显示,有30%,但是从运营角度考虑 觉得这些用户都可以抛弃,就像多年之前我们做开发还从2.2开始兼容一样,那些用户的使用频率,话费欲望都很低,等于将是粉。

那么,我们就用这个API实现吧!

来不及解释了,上代码。

Shotter.java

import android.hardware.display.DisplayManager;import android.hardware.display.VirtualDisplay;import android.media.Image;import android.media.ImageReader;import android.media.projection.MediaProjection;import android.media.projection.MediaProjectionManager;/** * Created by wei on 16-12-1. */public class Shotter {    private final SoftReference<Context> mRefContext;    private ImageReader mImageReader;    private MediaProjection mMediaProjection;    private VirtualDisplay mVirtualDisplay;    private String mLocalUrl = "";    private OnShotListener mOnShotListener;    public Shotter(Context context, Intent data) {        this.mRefContext = new SoftReference<>(context);        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {            mMediaProjection = getMediaProjectionManager().getMediaProjection(Activity.RESULT_OK,                    data);            mImageReader = ImageReader.newInstance(                    getScreenWidth(),                    getScreenHeight(),                    PixelFormat.RGB_565,// a pixel两节省一些内存 个2个字节 此处RGB_565 必须和下面 buffer处理一致的格式                    1);        }    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    private void virtualDisplay() {        mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",                getScreenWidth(),                getScreenHeight(),                Resources.getSystem().getDisplayMetrics().densityDpi,                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,                mImageReader.getSurface(), null, null);    }    public void startScreenShot(OnShotListener onShotListener, String loc_url) {        mLocalUrl = loc_url;        startScreenShot(onShotListener);    }    @TargetApi(Build.VERSION_CODES.KITKAT)    public void startScreenShot(OnShotListener onShotListener) {        mOnShotListener = onShotListener;        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {            virtualDisplay();            Handler handler = new Handler();            handler.postDelayed(new Runnable() {                                    @Override                                    public void run() {                                        Image image = mImageReader.acquireLatestImage();                                        AsyncTaskCompat.executeParallel(new SaveTask(), image);                                    }                                },                    300);        }    }    public class SaveTask extends AsyncTask<Image, Void, Bitmap> {        @TargetApi(Build.VERSION_CODES.KITKAT)        @Override        protected Bitmap doInBackground(Image... params) {            if (params == null || params.length < 1 || params[0] == null) {                return null;            }            Image image = params[0];            int width = image.getWidth();            int height = image.getHeight();            final Image.Plane[] planes = image.getPlanes();            final ByteBuffer buffer = planes[0].getBuffer();            //每个像素的间距            int pixelStride = planes[0].getPixelStride();            //总的间距            int rowStride = planes[0].getRowStride();            int rowPadding = rowStride - pixelStride * width;            Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height,                    Bitmap.Config.RGB_565);            bitmap.copyPixelsFromBuffer(buffer);            bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);            image.close();            File fileImage = null;            if (bitmap != null) {                try {                    if (TextUtils.isEmpty(mLocalUrl)) {                        mLocalUrl = getContext().getExternalFilesDir("screenshot").getAbsoluteFile()                                +                                "/"                                +                                SystemClock.currentThreadTimeMillis() + ".png";                    }                    fileImage = new File(mLocalUrl);                    if (!fileImage.exists()) {                        fileImage.createNewFile();                    }                    FileOutputStream out = new FileOutputStream(fileImage);                    if (out != null) {                        bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);                        out.flush();                        out.close();                    }                } catch (FileNotFoundException e) {                    e.printStackTrace();                    fileImage = null;                } catch (IOException e) {                    e.printStackTrace();                    fileImage = null;                }            }            if (fileImage != null) {                return bitmap;            }            return null;        }        @TargetApi(Build.VERSION_CODES.KITKAT)        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            if (bitmap != null && !bitmap.isRecycled()) {                bitmap.recycle();            }            if (mVirtualDisplay != null) {                mVirtualDisplay.release();            }            if (mOnShotListener != null) {                mOnShotListener.onFinish();            }        }    }    private MediaProjectionManager getMediaProjectionManager() {        return (MediaProjectionManager) getContext().getSystemService(                Context.MEDIA_PROJECTION_SERVICE);    }    private Context getContext() {        return mRefContext.get();    }    private int getScreenWidth() {        return Resources.getSystem().getDisplayMetrics().widthPixels;    }    private int getScreenHeight() {        return Resources.getSystem().getDisplayMetrics().heightPixels;    }    // a  call back listener    public interface OnShotListener {        void onFinish();    }}

ScreenShotActivity.java

/** * Created by wei on 16-9-18. * <p> * 完全透明 只是用于弹出权限申请的窗而已 * */public class ScreenShotActivity extends Activity {    public static final int REQUEST_MEDIA_PROJECTION = 0x2893;    @Override    protected void onCreate(Bundle savedInstanceState) {//        setTheme(android.R.style.Theme_Dialog);//这个在这里设置 之后导致 的问题是 背景很黑        super.onCreate(savedInstanceState);        //如下代码 只是想 启动一个透明的Activity 而上一个activity又不被pause        requestWindowFeature(Window.FEATURE_NO_TITLE);        getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));        getWindow().setDimAmount(0f);        requestScreenShot();    }    public void requestScreenShot() {        if (Build.VERSION.SDK_INT >= 21) {            startActivityForResult(                    ((MediaProjectionManager) getSystemService("media_projection")).createScreenCaptureIntent(),                    REQUEST_MEDIA_PROJECTION            );        }        else        {            toast("版本过低,无法截屏");        }    }    private void toast(String str) {        Toast.makeText(ScreenShotActivity.this,str,Toast.LENGTH_LONG).show();    }    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        super.onActivityResult(requestCode, resultCode, data);        switch (requestCode) {            case REQUEST_MEDIA_PROJECTION: {                if (resultCode == -1 && data != null) {                    Shotter shotter=new Shotter(ScreenShotActivity.this,data);                    shotter.startScreenShot(new Shotter.OnShotListener() {                        @Override                        public void onFinish() {                            toast("shot finish!");                            finish(); // don't forget finish activity                        }                    });                }            }        }    }}

原理比较简单,启动5.0的屏幕捕捉,使用MediaProjection创建一个虚拟桌面,将捕捉的数据传递到虚拟桌面,然后取虚拟桌面的一帧画面。
VirtualDisplay这个类在android.hardware这个包下面,很明显这个是需要硬件支持。

代码仓库

这个代码我挂在github:https://github.com/weizongwei5/AndroidScreenShot_SysApi

使用

我做了一些封装方便大家使用:

1. 直接后台service启动ScreenShotActivity

这个activity是透明的,而且不会引起当前activity onPuase,
ScreenShotActivity并自动实现弹出用户提示并截屏保存到外置存储空间下

注意:这个app使用外部存储,并不需要在mainfast中生明权限也没有在activity中动态申请,请不要惊讶,不会崩溃的。

2. 自定义使用方案,选择不使用Activity

直接使用Shotter,使用方法参考ScreenShotActivity。但是终究捕捉屏幕的权限申请还是要靠弹窗提示,必须得使用acitivity,但是你想要做改变的话 可以拿我的这个activity做修改。

扫我,我给你讲段子。

0 0
原创粉丝点击