如何使用 MediaProjection API 从后台服务类中截取屏幕截图?

Posted

技术标签:

【中文标题】如何使用 MediaProjection API 从后台服务类中截取屏幕截图?【英文标题】:How to take a Screenshot from a background-service class using MediaProjection API? 【发布时间】:2020-11-06 08:58:49 【问题描述】:

在对该主题进行了大量研究后,虽然我找到了一些答案,但我无法理解 MediaProjection API 的工作原理。

我想从后台服务类中截取设备的屏幕截图。有没有可能做到。我有一个 MainActivity.java 启动一个 serviceIntent 到另一个类,它是一个服务(不是一个活动)。所以我想在这个服务类中实现这个API。请帮帮我

【问题讨论】:

【参考方案1】:

这是实现这一目标的棘手方法。

首先你需要创建透明的背景主题。

    <style name="transparentTheme" parent="Theme.AppCompat.NoActionBar">
        <item name="android:background">#00000000</item> <!-- Or any transparency or color you need -->
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowAnimationStyle">@android:style/Animation</item>
        <item name="android:navigationBarColor" tools:ignore="NewApi">#00000000</item>
        <item name="android:statusBarColor" tools:ignore="NewApi">#00000000</item>
    </style>

现在您需要在 Manifest 文件中的 ScreenShotActivity 上添加此应用此主题。

  <activity
            android:name=".Activities.ScreenShotActivity"
            android:theme="@style/transparentTheme" />
        <activity

你的 ScreenShotActivity 类。

public class ScreenShotActivity extends Activity 

    private static final int videoTime = 5000;
    private static final int REQUEST_CODE = 1000;
    private static final int REQUEST_PERMISSION = 1000;
    private static final SparseIntArray ORIENTATION = new SparseIntArray();
    private MediaProjectionManager mediaProjectionManager;
    private MediaProjection mediaProjection;
    private VirtualDisplay virtualDisplay;
    private ScreenShotActivity.MediaProjectionCallback mediaProjectionCallback;
    private MediaRecorder mediaRecorder;
    PostWebAPIData postWebAPIData;
    private int mScreenDensity;
    private static int DISPLAY_WIDTH = 720;
    private static int DISPLAY_HEIGHT = 1280;

    static 
        ORIENTATION.append(Surface.ROTATION_0, 90);
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    

    private String screenShotUri = "";

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_screen_shot);
        init();
    

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void init() 
        //Screen tracking Code Started here..............................
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        mScreenDensity = metrics.densityDpi;
        postWebAPIData = new PostWebAPIData();
        DISPLAY_HEIGHT = metrics.heightPixels;
        DISPLAY_WIDTH = metrics.widthPixels;

        mediaRecorder = new MediaRecorder();
        mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        if (ContextCompat.checkSelfPermission(ScreenShotActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                + ContextCompat.checkSelfPermission(ScreenShotActivity.this, Manifest.permission.RECORD_AUDIO)
                != PackageManager.PERMISSION_GRANTED) 
            if (ActivityCompat.shouldShowRequestPermissionRationale(ScreenShotActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ||
                    ActivityCompat.shouldShowRequestPermissionRationale(ScreenShotActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) 
             else 
                ActivityCompat.requestPermissions(ScreenShotActivity.this, new String[]
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.RECORD_AUDIO
                , REQUEST_PERMISSION);
            
         else 
            new Handler().postDelayed(new Runnable() 
                @Override
                public void run() 
                    toogleScreenShare();
                
            , 500);
        
    


    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void toogleScreenShare() 
        initRecorder();
        recordScreen();
    


    public void getPathScreenShot(String filePath) 
        FFmpegMediaMetadataRetriever med = new FFmpegMediaMetadataRetriever();

        med.setDataSource(filePath);
        Bitmap bmp = med.getFrameAtTime(2 * 1000000, FFmpegMediaMetadataRetriever.OPTION_CLOSEST);
        String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + new StringBuilder("/screenshot").append(".bmp").toString();

        File myDir = new File(myPath);
        myDir.mkdirs();
        Random generator = new Random();
        int n = 10000;
        n = generator.nextInt(n);
        String fname = "Image-" + n + ".jpg";
        File file = new File(myDir, fname);
        Log.i(TAG, "" + myDir);
        if (myDir.exists())
            myDir.delete();
        try 
            FileOutputStream out = new FileOutputStream(myDir);
            bmp.compress(Bitmap.CompressFormat.JPEG, 90, out);
            out.flush();
            out.close();
         catch (Exception e) 
            e.printStackTrace();
        
        postScreenShot(myPath);
    

   
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void recordScreen() 
        if (mediaProjection == null) 
            startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
         else 
            virtualDisplay = createVirtualDisplay();
            mediaRecorder.start();
            onBackPressed();
            new Handler().postDelayed(new Runnable() 
                @Override
                public void run() 
                    mediaRecorder.stop();
                    mediaRecorder.reset();
                    stopRecordScreen();
                    destroyMediaProjection();
                    new Handler().postDelayed(new Runnable() 
                        @Override
                        public void run() 
                            getPathScreenShot(screenShotUri);
                        
                    , 2000);
                
            , videoTime);
        
    

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private VirtualDisplay createVirtualDisplay() 
        return mediaProjection.createVirtualDisplay("MainActivity", DISPLAY_WIDTH, DISPLAY_HEIGHT, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                mediaRecorder.getSurface(), null, null);
    

    private void initRecorder() 
        try 
            mediaRecorder.setAudiosource(MediaRecorder.AudioSource.MIC);
            mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);

            screenShotUri = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + new StringBuilder("/screenshot").append(".mp4").toString();

            mediaRecorder.setOutputFile(screenShotUri);
            mediaRecorder.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
            mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
            mediaRecorder.setVideoEncodingBitRate(512 * 1000);
            mediaRecorder.setVideoFrameRate(5);

            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            int orientation = ORIENTATION.get(rotation + 90);
            mediaRecorder.setOrientationHint(orientation);
            mediaRecorder.prepare();
         catch (IOException e) 
            e.printStackTrace();
            Log.d("ExceptionOccured", "" + e.getMessage());
        
    

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) 
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode != REQUEST_CODE) 
            stopService(new Intent(this, BackgroundService.class));
            startService(new Intent(this, BackgroundService.class));
            Toast.makeText(ScreenShotActivity.this, "Unknown Error", Toast.LENGTH_SHORT).show();
            Log.d("Livetracking", "ScreenShot" + requestCode + "  " + resultCode + " " + data);
            return;
        
        if (resultCode != RESULT_OK) 
            stopService(new Intent(this, BackgroundService.class));
            startService(new Intent(this, BackgroundService.class));
            Toast.makeText(ScreenShotActivity.this, "Permission denied" + requestCode, Toast.LENGTH_SHORT).show();
            Log.d("Livetracking", "Screenshot" + requestCode + "  " + resultCode + " " + data);
            return;
        
        Log.d("Livetracking", "Screenshot" + requestCode + "  " + resultCode + " " + data);

        mediaProjectionCallback = new ScreenShotActivity.MediaProjectionCallback();
        mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
        mediaProjection.registerCallback(mediaProjectionCallback, null);
        virtualDisplay = createVirtualDisplay();
        mediaRecorder.start();
        onBackPressed();
        new Handler().postDelayed(new Runnable() 
            @Override
            public void run() 
                mediaRecorder.stop();
                mediaRecorder.reset();
                stopRecordScreen();
                destroyMediaProjection();
                new Handler().postDelayed(new Runnable() 
                    @Override
                    public void run() 
                        getPathScreenShot(screenShotUri);
                    
                , 2000);
            
        , videoTime);
        ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        if (am != null) 
            List<ActivityManager.AppTask> tasks = am.getAppTasks();
            if (tasks != null && tasks.size() > 0) 
                Log.d("RemovingApp", "recent");
                tasks.get(0).setExcludeFromRecents(true);
            
        
    

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private class MediaProjectionCallback extends MediaProjection.Callback 
        @Override
        public void onStop() 
            mediaRecorder.stop();
            mediaRecorder.reset();
            mediaProjection = null;
            stopRecordScreen();
            destroyMediaProjection();
            if (mediaProjection != null) 
                destroyMediaProjection();
            
            super.onStop();
        
    

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void stopRecordScreen() 
        if (virtualDisplay == null) 
            virtualDisplay.release();
            if (mediaProjection != null) 
                destroyMediaProjection();
            
            return;

        
    

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void destroyMediaProjection() 
        if (mediaProjection != null) 
            mediaProjection.unregisterCallback(mediaProjectionCallback);
            mediaProjection.stop();
            mediaProjection = null;
        
    

在您的清单文件中添加这些权限。

   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

现在魔术从这里开始,您需要像这样从您的服务中调用 ScreenShotActivity。

      Intent dialogIntent = new Intent(BackgroundService.this, ScreenShotActivity.class);
      dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      startActivity(dialogIntent);

【讨论】:

哦,好吧。这是一种非常聪明的做法。谢谢 我应该如何导入 PostWebAPIData 以及它有什么作用? @Saatwik 只需从您的代码中删除 PostWebAPIData 并运行 好的,但是 postScreenShot 方法呢? @Saatwik 你想在服务器上的某个地方发布这个截图,比如使用 Larave; API 的..?

以上是关于如何使用 MediaProjection API 从后台服务类中截取屏幕截图?的主要内容,如果未能解决你的问题,请参考以下文章

使用 MediaProjection API 从后台服务记录:这种方法是唯一可能的吗?

是否可以使用 MediaProjection API 并在录制过程中处理每个录制的帧?

mediaprojection : 在某些情况下黑屏

android Q中无法识别MediaProjection服务类型

Android录屏功能实现——MediaProjection

Android 音视频开发 -- Android Mediaprojection 截屏和录屏