【问题标题】:Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION媒体投影需要 ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION 类型的前台服务
【发布时间】:2020-05-25 14:40:05
【问题描述】:

我有一个用于录制屏幕的应用程序,它在 android 版本 10 上不起作用。据我了解,这是由于 Android 10 中生效的隐私更改。我在清单文件中添加了权限<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />,在服务中添加android:foregroundServiceType="mediaProjection"标签并调用startForeground()方法

Notification.Builder builder = new Notification.Builder(getApplicationContext(), "333")
                .setContentTitle(getString(R.string.app_name))
                .setContentText("Record")
                .setAutoCancel(true);

        Notification notification = builder.build();

        startForeground(123, notification);

在调用.getMediaProjection() 方法之前,但我的应用程序仍然准确地抛出错误

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.screen_recording, PID: 9572
    java.lang.RuntimeException: Unable to start service com.example.screen_recording.service.RecordService@bb31978 with Intent { cmp=com.example.screen_recording/.service.RecordService (has extras) }: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
        at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4105)
        at android.app.ActivityThread.access$1800(ActivityThread.java:219)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1891)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
     Caused by: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
        at android.os.Parcel.createException(Parcel.java:2071)
        at android.os.Parcel.readException(Parcel.java:2039)
        at android.os.Parcel.readException(Parcel.java:1987)
        at android.media.projection.IMediaProjection$Stub$Proxy.start(IMediaProjection.java:231)
        at android.media.projection.MediaProjection.<init>(MediaProjection.java:58)
        at android.media.projection.MediaProjectionManager.getMediaProjection(MediaProjectionManager.java:104)
        at com.example.screen_recording.service.RecordService.onStartRecord(RecordService.java:117)
        at com.example.screen_recording.service.RecordService.onStartCommand(RecordService.java:106)
        at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4087)
        at android.app.ActivityThread.access$1800(ActivityThread.java:219) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1891) 
        at android.os.Handler.dispatchMessage(Handler.java:107) 
        at android.os.Looper.loop(Looper.java:214) 
        at android.app.ActivityThread.main(ActivityThread.java:7356) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
     Caused by: android.os.RemoteException: Remote stack trace:
        at com.android.server.media.projection.MediaProjectionManagerService$MediaProjection.start(MediaProjectionManagerService.java:476)
        at android.media.projection.IMediaProjection$Stub.onTransact(IMediaProjection.java:135)
        at android.os.Binder.execTransactInternal(Binder.java:1021)
        at android.os.Binder.execTransact(Binder.java:994)

这是服务代码

public class RecordService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    SharedPreferences p;
    WindowManager window;

    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
    private boolean isRecord = false;
    private String videoUri = "";

    private MediaProjectionManager mediaProjectionManager;
    private MediaProjection mediaProjection;
    private VirtualDisplay virtualDisplay;
    private MediaRecorder mediaRecorder;

    private int mScreenDensity;
    private static int DISPLAY_WIDTH;
    private static int DISPLAY_HEIGHT;
    private int resultCode;
    private Intent resultData;

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

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onCreate() {
        super.onCreate();

        Notification.Builder builder = new Notification.Builder(getApplicationContext(), "333")
                .setContentTitle(getString(R.string.app_name))
                .setContentText("Record")
                .setAutoCancel(true);

        Notification notification = builder.build();

        startForeground(123, notification);

        p = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

        DisplayMetrics metrics = new DisplayMetrics();
        window.getDefaultDisplay().getMetrics(metrics);
        mScreenDensity = metrics.densityDpi;
        DISPLAY_WIDTH = metrics.widthPixels;
        DISPLAY_HEIGHT = metrics.heightPixels;


        mediaRecorder = new MediaRecorder();
        mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (intent.getAction() == null) {
            resultCode = intent.getIntExtra("code", 1337);
            resultData = intent.getParcelableExtra("data");
        }

        onStartRecord();
        return (START_NOT_STICKY);
    }

    @Override
    public void onDestroy() {
        onStopRecord();
        super.onDestroy();
    }

    private void onStartRecord() {
        mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData);

        int quality = p.getInt("quality", 1080);
        boolean micro = p.getBoolean("micro", false);
        int fps = p.getInt("FPS", 60);

        initRecorder(quality, micro, fps);
        virtualDisplay = createVirtualDisplay();

        isRecord = true;
        p.edit().putBoolean("isRecord", isRecord).apply();
        mediaRecorder.start();
    }

    private void onStopRecord() {
        mediaRecorder.stop();
        mediaRecorder.reset();

        stopRecordScreen();


        isRecord = false;
        p.edit().putBoolean("isRecord", isRecord).apply();
    }

    private VirtualDisplay createVirtualDisplay() {
        return mediaProjection.createVirtualDisplay("MainFragment", DISPLAY_WIDTH, DISPLAY_HEIGHT, mScreenDensity,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                mediaRecorder.getSurface(), null, null);
    }

    private void initRecorder(int QUALITY, boolean isMicro, int fps) {
        try {
            int bitrateVideo = 0;

            switch (QUALITY) {
                case 1080:
                    bitrateVideo = 7000000;
                    break;
                case 720:
                    bitrateVideo = 4000000;
                    break;
                default:
                    bitrateVideo = 2000000;
                    break;
            }

            if (isMicro) {
                mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            }

            mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);

            if (isMicro) {
                mediaRecorder.setAudioSamplingRate(44100);
                mediaRecorder.setAudioEncodingBitRate(16 * 44100);
                mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            }


            videoUri = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
                    + new StringBuilder("/FreeRecord_").append(new SimpleDateFormat("dd-MM-yyyy-hh-mm-ss")
                    .format(new Date())).append(".mp4").toString();

            mediaRecorder.setOutputFile(videoUri);
            mediaRecorder.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
            mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
            mediaRecorder.setVideoEncodingBitRate(bitrateVideo);
            mediaRecorder.setCaptureRate(fps);
            mediaRecorder.setVideoFrameRate(fps);

            int rotation = window.getDefaultDisplay().getRotation();
            int orientation = ORIENTATIONS.get(rotation + 90);
            mediaRecorder.setOrientationHint(orientation);
            mediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void stopRecordScreen() {
        if (virtualDisplay == null) {
            return;
        }
        virtualDisplay.release();
        destroyMediaProject();
    }

    private void destroyMediaProject() {
        if (mediaProjection != null) {
            mediaProjection.stop();
            mediaProjection = null;
        }
    }
}

【问题讨论】:

    标签: java android


    【解决方案1】:

    使用通知渠道启动foreground service

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                // create notification channel
                new NotificationManager(this,11111);
                // Create notification builder.
                Intent notificationIntent = new Intent();
                PendingIntent pendingIntent = PendingIntent.getService(context, 1212, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
                NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationManager.CHANNEL_ID);
                builder.setSmallIcon(R.drawable.notification);
                builder.setColor(ContextCompat.getColor(this, R.color.white));
                builder.setContentTitle(getString(title));
                builder.setContentIntent(pendingIntent);
                Notification notification = builder.build();
                startForeground(100000, notification);
                Log.d(TAG,"Start foreground service");
            }
    

    foreground中找到以下链接以启动服务供您参考。 Oreo - Starting a service in the foreground

    【讨论】:

    • @ziotyr:你搞定了吗?你能分享一下 ScreenCaptureManager 课程吗? Github proj 也会很棒。
    • 是的,它正在工作。如果您遇到任何与此相关的问题,请告诉我。
    • 我尝试了stackoverflow.com/questions/61276730/…,但似乎仍然无法正常工作。必须将 mediaprojection 添加到服务中。
    • @user2801184,是的,必须在清单中使用 forgroundType。您是否已将服务创建为在清单中声明为 forgroundType:mediaprojection 的前台服务?
    • 如果可能,请发送服务类代码或logcat错误消息。
    猜你喜欢
    • 2020-07-31
    • 2021-05-30
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-18
    • 1970-01-01
    相关资源
    最近更新 更多