媒体投影需要 Android Pie 和 Q 中类型为 ServiceInfo.FOREGROUND_SERVICE TYPE_MEDIA_PROJECTION 的前台服务

Posted

技术标签:

【中文标题】媒体投影需要 Android Pie 和 Q 中类型为 ServiceInfo.FOREGROUND_SERVICE TYPE_MEDIA_PROJECTION 的前台服务【英文标题】:Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE TYPE_MEDIA_PROJECTION in Android Pie and Q 【发布时间】:2020-07-31 06:08:51 【问题描述】:

任何人都知道为什么会发生此错误,媒体投影需要在 getMediaProjection() 上使用 ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION 类型的前台服务,即使我在调用 startforeground() 方法后调用 getMediaProjection() 方法..!

堆栈跟踪是:

  Process: com.al1.screenrecorder, PID: 24641
java.lang.RuntimeException: Unable to start service com.al1.screenrecorder.service.DisplayRecorderService@9ef2101 with Intent  cmp=com.al1.screenrecorder/.service.DisplayRecorderService (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:4661)
    at android.app.ActivityThread.access$2900(ActivityThread.java:292)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2246)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loop(Looper.java:213)
    at android.app.ActivityThread.main(ActivityThread.java:8147)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101)
 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:75)
    at android.media.projection.MediaProjectionManager.getMediaProjection(MediaProjectionManager.java:104)
    at com.al1.screenrecorder.service.DisplayRecorderService.createMediaProjection(DisplayRecorderService.java:118)
    at com.al1.screenrecorder.service.DisplayRecorderService.onStartCommand(DisplayRecorderService.java:107)
    at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4641)
    at android.app.ActivityThread.access$2900(ActivityThread.java:292) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2246) 
    at android.os.Handler.dispatchMessage(Handler.java:107) 
    at android.os.Looper.loop(Looper.java:213) 
    at android.app.ActivityThread.main(ActivityThread.java:8147) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1101) 
 Caused by: android.os.RemoteException: Remote stack trace:
    at com.android.server.media.projection.MediaProjectionManagerService$MediaProjection.start(MediaProjectionManagerService.java:498)
    at android.media.projection.IMediaProjection$Stub.onTransact(IMediaProjection.java:135)
    at android.os.Binder.execTransactInternal(Binder.java:1028)
    at android.os.Binder.execTransact(Binder.java:1001)

强文本 私有 void startForeground()

    Intent activityIntent = new Intent(this, MainActivity.class);
    activityIntent.setAction("stop");
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, activityIntent, PendingIntent.FLAG_UPDATE_CURRENT);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 

        String channelId = "001";
        String channelName = "myChannel";
        NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE);
        channel.setLightColor(Color.BLUE);
        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);

        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        if (manager != null) 
            manager.createNotificationChannel(channel);
            Notification notification = new Notification.
                    Builder(getApplicationContext(), channelId)
                    .setOngoing(true)
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setCategory(Notification.CATEGORY_SERVICE)
                    .setContentTitle(getString(R.string.ClickToCancel))
                    .setContentIntent(contentIntent)
                    .build();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 
                startForeground(0, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
             else 
                startForeground(0, notification);
            
        
     else 
        startForeground(0, new Notification());
    


onStartCommand 是:

public int onStartCommand(Intent intent, int flags, int startId) 
    Log.i(TAG, "Service onStartCommand() is called");

    startForeground();

    mResultCode = intent.getIntExtra("code", -1);
    mResultData = intent.getParcelableExtra("data");
    mScreenWidth = intent.getIntExtra("width", 720);
    mScreenHeight = intent.getIntExtra("height", 1280);
    mScreenDensity = intent.getIntExtra("density", 1);
    isVideoSd = intent.getBooleanExtra("quality", true);
    isAudio = intent.getBooleanExtra("audio", true);


    mMediaProjection = createMediaProjection();
    mMediaRecorder = createMediaRecorder();
    mVirtualDisplay = createVirtualDisplay();
    mMediaRecorder.start();

    return Service.START_STICKY;

getMediaProjection 的这个方法中有异常:

 private MediaProjection createMediaProjection() 
    Log.i(TAG, "Create MediaProjection");
    return ((MediaProjectionManager) Objects.requireNonNull(getSystemService(Context.MEDIA_PROJECTION_SERVICE))).
            getMediaProjection(mResultCode, mResultData);

【问题讨论】:

【参考方案1】:

android:foregroundServiceType="mediaProjection" 添加到服务清单中的&lt;service&gt; 元素。

【讨论】:

请再次查看我编辑了我的帖子并添加了堆栈跟踪 嘿@MirzaAli,你找到解决方案了吗?【参考方案2】:

请确保您已授予所需的 FOREGROUND_SERVICE 权限。如果您的targetSdkVersion = 28 使用前台服务必须请求FOREGROUND_SERVICE 权限。 因此,向您的清单文件添加 FOREGROUND_SERVICE 权限,否则它会引发 SecurityException

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

您可以阅读有关此迁移说明的更多信息:https://developer.android.com/about/versions/pie/android-9.0-migration#tya

希望对你有所帮助.....

【讨论】:

【参考方案3】:

getMediaProjection(mResultCode, mResultData) 包裹在Handler().postDelayed() 中为我解决了这个问题。

override fun onServiceConnected(className: ComponentName, service: IBinder) 
        // We've bound to LocalService, cast the IBinder and get LocalService instance
        val binder = service as MediaProjectionService.LocalBinder
        mediaProjectionService = binder.getService()

        // delay needed because getMediaProjection() throws an error if it's called too soon
        Handler().postDelayed(
            mediaProjection = mediaProjectionManager.getMediaProjection(activityResultCode, activityData)
            startStreaming()
            serviceBound = true
        , 1000)
    

【讨论】:

【参考方案4】:

试试这个:https://***.com/a/68343645/5182769。关键是你必须先在 onStartCommand 中创建通知通道。

【讨论】:

【参考方案5】:

您必须在创建媒体投影之前启动前台服务位。

如果您已经有服务,即使您在初始化媒体投影之前启动服务,大多数时候也会延迟启动服务。启动服务后稍作延迟后启动投影。我认为这可以解决问题。

代码示例:

startService(new Intent(AppController.getInstance(),
            ScreenCaptureService.class));

 new Handler().postDelayed(new Runnable() 
        @Override
        public void run() 
            MediaProjectionManager projectionManager = (MediaProjectionManager)
                    context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);

           if (isServiceActive(context, ScreenCaptureService.class))
               mMediaProjection =
                       projectionManager.getMediaProjection(resultCode, data);
               processAndSaveScreenshotFromDevice(context);
           
        
    , 200);

【讨论】:

【参考方案6】:

OP的代码的问题在于他使用了

startForeground(0, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);

文档明确表示:

Params: id – 根据 NotificationManager.notify(int, Notification) 的通知标识符; 不得为 0

【讨论】:

最正确的答案都在这里。谢谢你兄弟你救了我的一天【参考方案7】:

添加前台权限&lt;uses-permission android:name="android.permission.FOREGROUND_SERVICE" /&gt;后,需要创建一个服务类。所以在一个新的Java文件中有这个:

public class BackgroundService extends Service 

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 
        createNotificationChannel();
        Intent intent1 = new Intent(this, ClassWithError.class);
        Intent intent2 = new Intent(this, AnotherClassWithError.class);

        PendingIntent pendingIntent1 = PendingIntent.getActivity(this, 0, intent1, 0);
        PendingIntent pendingIntent2 = PendingIntent.getActivity(this, 0, intent2, 0);

        Notification notification1 = new NotificationCompat.Builder(this, "ScreenRecorder")
                .setContentTitle("yNote studios")
                .setContentText("Filming...")
                .setContentIntent(pendingIntent1).build();

        Notification notification2 = new NotificationCompat.Builder(this, "ScreenRecorder")
                .setContentTitle("yNote studios")
                .setContentText("Filming...")
                .setContentIntent(pendingIntent1).build();

        startForeground(1, notification1);
        startForeground(2, notification2);
        return super.onStartCommand(intent, flags, startId);
    


    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        return null;
    

    private void createNotificationChannel() 
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
            NotificationChannel channel = new NotificationChannel("ScreenRecorder", "Foreground notification",
                    NotificationManager.IMPORTANCE_DEFAULT);
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        
    

    @Override
    public void onDestroy() 
        stopForeground(true);
        stopSelf();

        super.onDestroy();
    

然后回到清单中,将名称部分的服务段添加为上述服务类。它应该在应用程序段内。

<service
            android:name=".Services.BackgroundService"
            android:enabled="true"
            android:exported="true"
            android:stopWithTask="true"
            android:foregroundServiceType="mediaProjection" />

这样设置后,您可以在需要时访问此后台服务(在onCreate 方法中),如下所示:

Intent intent = new Intent(this, BackgroundService.class);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 
            startForegroundService(intent);
         else 
            startService(intent);
        

但一定要管理好它。这可能是您项目中其他功能出现多个异常的原因。在此服务运行时调用媒体记录器之类的东西会崩溃。为此,请使用onDestroy 方法关闭服务。

【讨论】:

我尝试了上述解决方案,但仍然获得媒体投影需要 ServiceInfo.FOREGROUND_SERVICE TYPE_MEDIA_PROJECTION 类型的前台服务。当我调用 capturer.StartCapture() 时。 super.onStartCommand(intent, flags, startId) 也在无限循环中运行。我们如何才能停止服务? 你应该改用ContextCompat.startForegroundService()

以上是关于媒体投影需要 Android Pie 和 Q 中类型为 ServiceInfo.FOREGROUND_SERVICE TYPE_MEDIA_PROJECTION 的前台服务的主要内容,如果未能解决你的问题,请参考以下文章

Android Q(10) 文件存储适配

[POI2015]PIE

用于 Android Q 上的辅助存储的媒体扫描仪

投影媒体查询:浏览器支持和解决方法?

互动投影系统主要有哪些类别

Android(9)Pie中如何允许所有网络连接类型HTTP和HTTPS?