Android 5.0 無Root權(quán)限實現(xiàn)截屏

????????Android在5.0之后提供了官方的截屏API,再也不需要root再調(diào)用adb指令,或者使用輔助服務(wù)模擬截屏按鍵實現(xiàn)截屏了片挂。本文將介紹實現(xiàn)過程睬魂,并在最后提供Demo以供下載參考终吼。

1. 實現(xiàn)流程

1.1 獲取屏幕的實時信息

?  先請求截屏的服務(wù),需要等用戶同意后氯哮,才能獲取屏幕實時信息际跪。通過調(diào)用getSystemService()方法,得到MediaProjectionManager對象喉钢。該對象我們需要關(guān)注兩個東西姆打,一個是通過createScreenCaptureIntent()方法得到的Intent對象(這里會用到),一個是通過getMediaProjection()得到的MediaProjection對象(下節(jié)會用到)肠虽。

?  在這里幔戏,我們通過MediaProjectionManager的createScreenCaptureIntent方法得到一個Intent請求,將其作為startActivityForResult的參數(shù)啟動一個截屏請求税课。此時闲延,系統(tǒng)會向用戶申請截屏權(quán)限,告知用戶接下來會有截屏操作韩玩,同時也開始截屏的準(zhǔn)備工作垒玲。代碼實現(xiàn)如下:

private void try2StartScreenShot() {
    MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
    startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
}

?  在onActivityResult方法里面可以拿到返回的Intent數(shù)據(jù),該Intent只是一個引用啸如,里面的東西是實時在改變的(因為里面記錄的是屏幕信息)侍匙,信息存儲在intent里面的bundle,bundle里面記錄的是一個用Android專用序列化方式Parcelable序列化過的一個對象。代碼如下:

@Override 
protected void onActivityResult(int requestCode, int resultCode, Intent data) {         
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode)  {
        case REQUEST_MEDIA_PROJECTION:{ 
            if (resultCode == RESULT_OK && data != null)  {
                this.data = data;
            }
            break;
        }
    }
}

1.2 創(chuàng)建虛擬屏幕

?  在這節(jié)中想暗,我們需要再次借助MediaProjectionManager類妇汗,調(diào)用getMediaProjection()方法得到mMediaProjection對象備用。
? 再初始化一個ImageReader對象说莫,這個對象會在虛擬化屏幕里面用到杨箭,這個ImageReader實際上是屏幕上面的畫面。我們可以通過ImageReader.newInstance()方法創(chuàng)建一個ImageReader對象储狭。

? 定義:ImageReader newInstance(int width, int height, int format, int maxImages)

參數(shù) 含義
int width 寬度(此處為屏幕寬度)
int height 高度(此處為屏幕高度)
int format 圖片格式(此處為PixelFormat.RGBA_8888)
int maxImages 圖片的最大數(shù)量

?  接下來就是創(chuàng)建虛擬屏幕了互婿,可以用之前拿到的mMediaProjection,調(diào)用createVirtualDisplay方法實現(xiàn)辽狈。定義:
createVirtualDisplay(@NonNull String name,int width, int height, int dpi, int flags, @Nullable Surface surface,@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler)

參數(shù) 含義
String name 虛擬屏幕名字慈参,非空
int width 虛擬屏幕寬度
int height 虛擬屏幕高度
int dpi 虛擬屏幕的DPI
int flags 虛擬屏幕的顯示標(biāo)志
Surface surface 存放虛擬屏幕圖像的UI
VirtualDisplay.Callback callback 虛擬屏幕狀態(tài)發(fā)生改變的回調(diào)
Handler handler 上面回調(diào)所運行的線程,為null上面回調(diào)會運行在主線程里面

? 實現(xiàn)代碼:

private void createVirtualDisplay() {
    mVirtualDisplay = mMediaProjection.createVirtualDisplay(
        "screen-mirror",
        getScreenWidth(),
        getScreenHeight(),
        Resources.getSystem().getDisplayMetrics().densityDpi,
        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
        mImageReader.getSurface(),
        null,
        null
    );
}

1.3 獲取虛擬屏幕的內(nèi)容,轉(zhuǎn)為Bitmap對象

?  從存儲虛擬屏幕的ImageReader對象上,拿到里面的image圖像桃熄,這里就可以得到image的字節(jié)數(shù)組信息,再新建一個bitmap對象壮锻,將字節(jié)信息傳給bitmap,就可以拿到我們需要的圖像涮阔,這個bitmap就是我們的屏幕截圖了猜绣。需要注意的是,bitmap的色彩格式要和上面給ImageReader設(shè)置的一樣敬特。
  相關(guān)代碼掰邢,這部分最好在子線程中執(zhí)行:

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);
image.close();

1.4 代碼封裝

筆者將上述步驟封裝在ScreenShotHelper類中,代碼如下:

package com.example.testscreenshot;

import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
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.AsyncTask;
import android.os.Handler;

import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;

public class ScreenShotHelper {

    interface OnScreenShotListener {
        void onFinish(Bitmap bitmap);
    }

    private OnScreenShotListener mOnScreenShotListener;

    private ImageReader mImageReader;
    private MediaProjection mMediaProjection;
    private VirtualDisplay mVirtualDisplay;
    private final SoftReference<Context> mRefContext;

    public ScreenShotHelper(Context context, int resultCode, Intent data, OnScreenShotListener onScreenShotListener) {
        this.mOnScreenShotListener = onScreenShotListener;
        this.mRefContext = new SoftReference<Context>(context);

        mMediaProjection = getMediaProjectionManager().getMediaProjection(resultCode, data);
        mImageReader = ImageReader.newInstance(getScreenWidth(), getScreenHeight(), PixelFormat.RGBA_8888, 1);
    }

    public void startScreenShot() {
        createVirtualDisplay();

        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                new CreateBitmapTask().execute();
            }
        }, 1000);
    }

    public class CreateBitmapTask extends AsyncTask<Image, Void, Bitmap> {

        @Override
        protected Bitmap doInBackground(Image... params) {
            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);
            image.close();

            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            mVirtualDisplay.release();
            mMediaProjection.stop();

            if (mOnScreenShotListener != null) {
                mOnScreenShotListener.onFinish(bitmap);
            }
        }
    }

    private MediaProjectionManager getMediaProjectionManager() {
        return (MediaProjectionManager) getContext().getSystemService(
                Context.MEDIA_PROJECTION_SERVICE);
    }

    private void createVirtualDisplay() {
        mVirtualDisplay = mMediaProjection.createVirtualDisplay(
                "screen-mirror",
                getScreenWidth(),
                getScreenHeight(),
                Resources.getSystem().getDisplayMetrics().densityDpi,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                mImageReader.getSurface(),
                null,
                null
        );
    }

    private Context getContext() {
        return mRefContext.get();
    }

    public static int getScreenWidth() {
        return Resources.getSystem().getDisplayMetrics().widthPixels;
    }

    public static int getScreenHeight() {
        return Resources.getSystem().getDisplayMetrics().heightPixels;
    }
}

?  用法:

ScreenShotHelper screenShotHelper = new ScreenShotHelper(MainActivity.this, resultCode, data, new ScreenShotHelper.OnScreenShotListener() {
    @Override
    public void onFinish(Bitmap bitmap) {
        mImageView.setImageBitmap(bitmap);
    }
});
screenShotHelper.startScreenShot();

2.GitHub鏈接

https://github.com/AchillesLzg/TestScreenShot

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末擅羞,一起剝皮案震驚了整個濱河市尸变,隨后出現(xiàn)的幾起案子义图,更是在濱河造成了極大的恐慌减俏,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碱工,死亡現(xiàn)場離奇詭異娃承,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)怕篷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門历筝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人廊谓,你說我怎么就攤上這事梳猪。” “怎么了蒸痹?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵春弥,是天一觀的道長呛哟。 經(jīng)常有香客問我,道長匿沛,這世上最難降的妖魔是什么扫责? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮逃呼,結(jié)果婚禮上鳖孤,老公的妹妹穿的比我還像新娘。我一直安慰自己抡笼,他們只是感情好苏揣,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著推姻,像睡著了一般腿准。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拾碌,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天吐葱,我揣著相機(jī)與錄音,去河邊找鬼校翔。 笑死弟跑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的防症。 我是一名探鬼主播孟辑,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蔫敲!你這毒婦竟也來了饲嗽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤奈嘿,失蹤者是張志新(化名)和其女友劉穎貌虾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裙犹,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡尽狠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了叶圃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袄膏。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖掺冠,靈堂內(nèi)的尸體忽然破棺而出沉馆,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布斥黑,位于F島的核電站闽瓢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏心赶。R本人自食惡果不足惜扣讼,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缨叫。 院中可真熱鬧椭符,春花似錦、人聲如沸耻姥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琐簇。三九已至蒸健,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間婉商,已是汗流浹背似忧。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留丈秩,地道東北人盯捌。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像蘑秽,于是被迫代替她去往敵國和親饺著。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355