• Android50截屏无需root
    • 实现步骤
      • 拿到屏幕上的实时信息
      • 新建一个虚拟屏幕
      • 处理虚拟屏幕上的图像
  • Android截屏shell方式需要root对版本没要求

Android5.0截屏,无需root

关于截屏这一块,Android在5.0之后提供了官方的API,关于截屏也不需要使用adb,root,以及去模拟按键又或者去撸隐藏代码了。
主要用到以下几个类:
1. MediaProjection
2. MediaProjectionManager
3. MediaProjection.Callback
4. DisplayManager
5. VirtualDisplay
6. VirtualDisplay.Callback
7. ImageReader
8. Image
9. Bitmap
10. PixelFormat

实现步骤

拿到屏幕上的实时信息

先请求截屏的服务,然后拿到返回来的Intent数据.
这里实现打开一个服务,跟一般的服务不一样,这里的服务会转换为一个MediaProjectionManager,看命名是一个管理器,这个管理器持有MediaProjection,还有一个请求截屏的Intent。有两种方式拿到这个MediaProjectionManager.

Context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
or
Context.getSystemService(MediaProjectionManager.class);

拿到MediaProjectionManager后,就可以拿到它名下的一个Intent,通过启动这个Intent,我们可以拿到另外一个Intent。所以我们必须用 startActivityForResult这种方式来启动这个Intent。
启动这个Intent,系统会向用户申请权限,告知用户接下来会有截屏操作,同时也开始截屏的准备工作了。因为我们是以startActivityForResult方式启动的,所以在onActivityResult里面会返回实时的屏幕的信息(这里的信息不是以图像的形式出现,所以我们拿到信息后还需要自己处理转换成我们需要的图像信息)。(录屏也是这样做的)

    public void requestScreenShot() {        if (Build.VERSION.SDK_INT >= 21) {//            MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);  //方式一            MediaProjectionManager mediaProjectionManager = getSystemService(MediaProjectionManager.class);  //方式二            startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);//REQUEST_MEDIA_PROJECTION是我们自己定义的一个int,随便给可以。        } else {            Toast.makeText(MainActivity.this, "版本过低,无法截屏", Toast.LENGTH_SHORT).show();        }    }

onActivityResult方法里面可以拿到返回的数据,Intent类型。

@Override    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) {//这里的resultCode必须为-1(Activity.RESULT_OK),后面也会用到-1(系统返回的-1,只需要记住就可以了);                    this.data = data;  //记录这里拿到data,是一个Intent,实际上这里记录的只是一个引用,里面的东西是实时在改变的(因为里面记录的是屏幕信息),信息存储在intent里面的bundle,bundle里面记录的是一个用Android专用序列化方式Parcelable序列化过的一个对象。                }            }        }    }

拿到屏幕信息后,我们需要处理,将其转换成我们需要的PNG或者bitmap格式。
处理分为大抵分为两步,第一步,新建一个虚拟屏幕,将之前拿到的信息显示在虚拟屏幕上;第二步,拿到屏幕上的图像。

新建一个虚拟屏幕

这里还是需要借助MediaProjectionManager,上面有说过,MediaProjectionManager持有MediaProjection,还有一个请求截屏的Intent,Intent我们已经用过了,这里会用到MediaProjection。我们要拿到这个MediaProjection,方法里面有两个参数(一个是上面说的-1(Activity.RESULT_OK),一个是上面拿到的屏幕信息Intent)

if(null==mMediaProjection)    mMediaProjection = mediaProjectionManager.getMediaProjection(Activity.RESULT_OK,data); //这里的mediaProjectionManager 跟上面的不一定是同一个对象,可以自己通过Context重新请求一个MediaProjectionManager

初始化一个ImageReader对象,这个对象会在虚拟化屏幕里面用到,这个ImageReader实际上是屏幕上面的画面。初始化一个ImageReader用到四个参数会。newInstance (int width, int height, int format, int maxImages)分别代表图像的宽、高、图像色彩格式、imagereader里面最多存储几张图像(多了耗费内存),这里格式必须是ImageFormat或PixelFormat里面的,当然也并不是说里面的所有格式都支持,ImageFormat.NV21就不支持。

mImageReader = ImageReader.newInstance(                    getScreenWidth(),  //真实屏幕宽度                    getScreenHeight(),  //真实屏幕高度                    PixelFormat.RGBA_8888,// a pixel两节省一些内存 个2个字节 此处RGBA_8888 必须和下面 buffer处理一致的格式                    1);  //最多存储一张图像

下面是一些获取真实屏幕参数的方法:

//获取真实屏幕宽度(单位px)private int getScreenWidth() {        return Resources.getSystem().getDisplayMetrics().widthPixels;    }
//获取真实屏幕高度(单位px)private int getScreenHeight() {        return Resources.getSystem().getDisplayMetrics().heightPixels;    }
//获取状态栏高度(单位px)private int getStatusBatHeight(){        /**         * 获取状态栏高度——方法1         * */       //获取status_bar_height资源的ID        int resourceId = getContext().getResources().getIdentifier("status_bar_height", "dimen", "android");        if (resourceId > 0) {            //根据资源ID获取响应的尺寸值            statusBarHeight1 = getContext().getResources().getDimensionPixelSize(resourceId);            float scale = getContext().getResources().getDisplayMetrics().density;            statusBarHeight1= (int) (statusBarHeight1*scale+0.5f);            return statusBarHeight1;        }        return 0;    }

然后就是新建虚拟屏幕了,用之前拿到的mMediaProjection,这个对象下面有一个方法createVirtualDisplay ,这个方法就是创建一个虚拟屏幕。下面介绍一下各个参数的意义。

VirtualDisplay createVirtualDisplay (String name, //虚拟屏幕的名字,不能为空,可以随便取
int width, //虚拟屏幕的宽度
int height, //虚拟屏幕的高度
int dpi, //虚拟屏幕的DPI
int flags, //虚拟屏幕的显示标志,必须为DisplayManager下面的int常量
Surface surface, //存放虚拟屏幕图像的UI
VirtualDisplay.Callback callback, //虚拟屏幕状态发生改变的回调
Handler handler) //上面回调所运行的线程,为null上面回调会运行在主线程里面

mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",                getScreenWidth(),                getScreenHeight(),                getScreenDpi(),                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,                mImageReader.getSurface(), null, null);

处理虚拟屏幕上的图像

从存储虚拟屏幕的ImageReader对象上,拿到里面的image图像,这里就可以得到image的字节数组信息,在新建一个bitmap对象,将字节信息传给bitmap,就可以拿到我们需要的图像,这个bitmap就是我们的屏幕截图了。需要注意的是,bitmap的色彩格式要和上面给ImageReader设置的一样

            Image image = mImageReader.acquireLatestImage();            while(null==image)                image=mImageReader.acquireLatestImage();            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.ARGB_8888);            bitmap.copyPixelsFromBuffer(buffer);            bitmap = Bitmap.createBitmap(bitmap,0,0,width,height);//这里的bitmap为最终的截图            image.close();            File fileImage = null;            if (bitmap != null) {                try {                    fileImage = new File(mLocalUrl);//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 (bitmap != null && !bitmap.isRecycled()) {                bitmap.recycle();            }        }        if (mVirtualDisplay != null) {            mVirtualDisplay.release();        }        if (mOnShotListener != null) {            mOnShotListener.onFinish();        }        if(null!=mMediaProjection){            mMediaProjection.stop();        }

下面贴出完整的代码,由于是公司的项目,所以就不贴出整个工程了。
这段代码是上面步骤里面的后两步,之前的跳转需要自己在activity里面获取。获取方法如下:

//在oncreate或者onresume里面调用public void requestScreenShot() {        if (Build.VERSION.SDK_INT >= 21) {//            MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);  //方式一            MediaProjectionManager mediaProjectionManager = getSystemService(MediaProjectionManager.class);  //方式二            startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);//REQUEST_MEDIA_PROJECTION是我们自己定义的一个int,随便给可以。        } else {            Toast.makeText(MainActivity.this, "版本过低,无法截屏", Toast.LENGTH_SHORT).show();        }    } @Override    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) {//这里的resultCode必须为-1(Activity.RESULT_OK),后面也会用到-1(系统返回的-1,只需要记住就可以了);                    this.data = data;  //记录这里拿到data,是一个Intent,实际上这里记录的只是一个引用,里面的东西是实时在改变的(因为里面记录的是屏幕信息),信息存储在intent里面的bundle,bundle里面记录的是一个用Android专用序列化方式Parcelable序列化过的一个对象。                }            }        }    }

这段代码最好在一个子线程里面执行,因为我在项目里面已经是在子线程里面执行,所以这里没有用Thread和Handler。

package com.hskj.damnicomniplusvic.wevenation.util;import android.annotation.TargetApi;import android.app.Activity;import android.content.Context;import android.content.Intent;import android.content.res.Resources;import android.graphics.Bitmap;import android.graphics.PixelFormat;import android.graphics.Rect;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;import android.os.Build;import android.os.SystemClock;import android.text.TextUtils;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.lang.ref.SoftReference;import java.nio.ByteBuffer;/** * Created by wei on 16-12-1. */public class Shotter {    private final SoftReference mRefContext;    private ImageReader mImageReader;    private MediaProjection mMediaProjection;    private VirtualDisplay mVirtualDisplay;    int statusBarHeight1 = -1;    private String mLocalUrl = "";    private Rect mRect;//rect是我在项目里面需要截图的区域,一个图标。    public Shotter(Context context, Intent data, Rect rect) {        this.mRefContext = new SoftReference<>(context);        this.mRect=rect;        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {            if(null==mMediaProjection)                mMediaProjection = getMediaProjectionManager().getMediaProjection(Activity.RESULT_OK,                    data);            mImageReader = ImageReader.newInstance(                    getScreenWidth(),                    getScreenHeight(),                    PixelFormat.RGBA_8888,// a pixel两节省一些内存 个2个字节 此处RGBA_8888 必须和下面 buffer处理一致的格式                    1);            getStatusBatHeight();        }    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    private void virtualDisplay() {        mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",                getScreenWidth(),                getScreenHeight(),                getScreenDpi(),                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,                mImageReader.getSurface(), null, null);    }    public void startScreenShot(String loc_url) {        mLocalUrl = loc_url;        startScreenShot();    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    public void startScreenShot() {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {            virtualDisplay();            Image image = mImageReader.acquireLatestImage();            while(null==image)                image=mImageReader.acquireLatestImage();            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.ARGB_8888);            bitmap.copyPixelsFromBuffer(buffer);            if(null!=mRect)                bitmap = Bitmap.createBitmap(bitmap, mRect.left, mRect.top, mRect.width(), mRect.height());            else                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 (bitmap != null && !bitmap.isRecycled()) {                bitmap.recycle();            }        }        if (mVirtualDisplay != null) {            mVirtualDisplay.release();        }        if(null!=mMediaProjection){            mMediaProjection.stop();        }    }    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;    }    private int getScreenDpi(){        return Resources.getSystem().getDisplayMetrics().densityDpi;    }    private void getStatusBatHeight(){        /**         * 获取状态栏高度——方法1         * */       //获取status_bar_height资源的ID        int resourceId = getContext().getResources().getIdentifier("status_bar_height", "dimen", "android");        if (resourceId > 0) {            //根据资源ID获取响应的尺寸值            statusBarHeight1 = getContext().getResources().getDimensionPixelSize(resourceId);            float scale = getContext().getResources().getDisplayMetrics().density;            statusBarHeight1= (int) (statusBarHeight1*scale+0.5f);        }    }}

Android截屏,shell方式,需要root,对版本没要求

直接在代码里面执行shell命令,这个命令在电脑上面用adb方式执行无需root也能截图,放在代码里面需要root权限。
在PC上面截图,在DOS窗口输入adb shell screencap -p /sdcard/damn.png就可以截图并保存在sdcard的根目录下的damn.png文件。
在外直接调用doCmds("screencap -p /sdcard/damn.png")
这里不用前面的adb shell是因为,在pc上我们的adb是连接到手机的工具,在手机本身上不用连接这一步。

    /**     * 执行shell命令函数     * @param cmd 需要执行的shell命令     */    public static void doCmds(String cmd){        Process process = null;        try {            process = Runtime.getRuntime().exec("sh");            DataOutputStream os = new DataOutputStream(process.getOutputStream());            os.writeBytes(cmd+"\n");            os.writeBytes("exit\n");            os.flush();            os.close();            process.waitFor();        } catch (IOException e) {            e.printStackTrace();        } catch (InterruptedException e) {            e.printStackTrace();        }    }

下面提供一个执行shell脚本的工具类,从其他博客搬过来的。

package com.hskj.damnicomniplusvic.wevenation.util;import java.io.BufferedReader;import java.io.DataOutputStream;import java.io.IOException;import java.io.InputStreamReader;import java.util.List;/** * Android执行shell命令工具类 * Created by DAMNICOMNIPLUSVIC on 2017/7/26. * (c) 2017 DAMNICOMNIPLUSVIC Inc,All Rights Reserved. */public class ShellUtil {    private static final String COMMAND_SU       = "su";    private static final String COMMAND_SH       = "ls";    private static final String COMMAND_EXIT     = "exit\n";    private static final String COMMAND_LINE_END = "\n";    private ShellUtil() {        throw new AssertionError();    }    /**     * check whether has root permission     *     * @return root or not     */    public static boolean checkRootPermission() {        return execCommand("echo root", true, false).result == 0;    }    /**     * execute shell command, default return result msg     *     * @param command command     * @param isRoot whether need to run with root     * @return the result of execute command     * @see ShellUtil#execCommand(String[], boolean, boolean)     */    public static CommandResult execCommand(String command, boolean isRoot) {        return execCommand(new String[] {command}, isRoot, true);    }    /**     * execute shell commands, default return result msg     *     * @param commands command list     * @param isRoot whether need to run with root     * @return the result of execute command     * @see ShellUtil#execCommand(String[], boolean, boolean)     */    public static CommandResult execCommand(List commands, boolean isRoot) {        return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, true);    }    /**     * execute shell commands, default return result msg     *     * @param commands command array     * @param isRoot whether need to run with root     * @return the result of execute command     * @see ShellUtil#execCommand(String[], boolean, boolean)     */    public static CommandResult execCommand(String[] commands, boolean isRoot) {        return execCommand(commands, isRoot, true);    }    /**     * execute shell command     *     * @param command command     * @param isRoot whether need to run with root     * @param isNeedResultMsg whether need result msg     * @return the result of execute command     * @see ShellUtil#execCommand(String[], boolean, boolean)     */    public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) {        return execCommand(new String[] {command}, isRoot, isNeedResultMsg);    }    /**     * execute shell commands     *     * @param commands command list     * @param isRoot whether need to run with root     * @param isNeedResultMsg whether need result msg     * @return the result of execute command     * @see ShellUtil#execCommand(String[], boolean, boolean)     */    public static CommandResult execCommand(List commands, boolean isRoot, boolean isNeedResultMsg) {        return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, isNeedResultMsg);    }    /**     * execute shell commands     *     * @param commands command array     * @param isRoot whether need to run with root     * @param isNeedResultMsg whether need result msg     * @return 
    *
  • if isNeedResultMsg is false, {@link CommandResult#successMsg} is null and * {@link CommandResult#errorMsg} is null.
  • *
  • if {@link CommandResult#result} is -1, there maybe some excepiton.
  • *
*/
public static CommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) { int result = -1; if (commands == null || commands.length == 0) { return new CommandResult(result, null, null); } Process process = null; BufferedReader successResult = null; BufferedReader errorResult = null; StringBuilder successMsg = null; StringBuilder errorMsg = null; DataOutputStream os = null; try { process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH); os = new DataOutputStream(process.getOutputStream()); for (String command : commands) { if (command == null) { continue; } // donnot use os.writeBytes(commmand), avoid chinese charset error os.write(command.getBytes()); os.writeBytes(COMMAND_LINE_END); os.flush(); } os.writeBytes(COMMAND_EXIT); os.flush(); result = process.waitFor(); // get command result if (isNeedResultMsg) { successMsg = new StringBuilder(); errorMsg = new StringBuilder(); successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); String s; while ((s = successResult.readLine()) != null) { successMsg.append(s); } while ((s = errorResult.readLine()) != null) { errorMsg.append(s); } } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (os != null) { os.close(); } if (successResult != null) { successResult.close(); } if (errorResult != null) { errorResult.close(); } } catch (IOException e) { e.printStackTrace(); } if (process != null) { process.destroy(); } } return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null : errorMsg.toString()); } /** * result of command *
    *
  • {@link CommandResult#result} means result of command, 0 means normal, else means error, same to excute in * linux shell
  • *
  • {@link CommandResult#successMsg} means success message of command result
  • *
  • {@link CommandResult#errorMsg} means error message of command result
  • *
* * @author Trinea 2013-5-16 */
public static class CommandResult { /** result of command **/ public int result; /** success message of command result **/ public String successMsg; /** error message of command result **/ public String errorMsg; public CommandResult(int result) { this.result = result; } public CommandResult(int result, String successMsg, String errorMsg) { this.result = result; this.successMsg = successMsg; this.errorMsg = errorMsg; } }}

更多相关文章

  1. Android(安卓)开发的常用工具类(一)——ScreenUtils 可用于获取屏
  2. Android获取屏幕分辨率及DisplayMetrics简介
  3. android 获取屏幕高度,宽度,状态栏高度
  4. Android(安卓)如何在xmL 里面动态设置padding
  5. Android界面布局基本知识简述
  6. Android实现屏幕旋转方法总结
  7. Android(安卓)实现变色状态栏
  8. android EditText中inputType的属性列表
  9. Android获取屏幕分辨率及DisplayMetrics简介

随机推荐

  1. 万能码的作用你想不到(安全扫码专业委员会
  2. 云服务器如何通过本地安全策略阻止特定IP
  3. 创建html和常用标签
  4. 1109标签练习
  5. 我的第一个作业
  6. 2021年11月9日作业1
  7. 樱花怎么画?超详细樱花画法步骤!
  8. 用户注册作业
  9. 2021.11.9 作业
  10. 大前端第二天第一个作业