【问题标题】:App crash on headset button click耳机按钮单击时应用程序崩溃
【发布时间】:2020-10-27 23:30:39
【问题描述】:

我已经构建了一个音频播放器,它部署在 android google playstore 中。我正在使用 crashlytics 来监控崩溃和 ANR。最近我遇到了很多崩溃 MediaButtonReceiver。耳机咔哒声在许多设备中都能正常工作。但是有些设备会出现这个问题。

Crashlytics 报告 -

Fatal Exception: java.lang.RuntimeException: Unable to start receiver android.support.v4.media.session.MediaButtonReceiver: java.lang.IllegalStateException: Could not find any Service that handles android.intent.action.MEDIA_BUTTON or implements a media browser service.
       at android.app.ActivityThread.handleReceiver(ActivityThread.java:2866)
       at android.app.ActivityThread.access$1700(ActivityThread.java:182)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1551)
       at android.os.Handler.dispatchMessage(Handler.java:111)
       at android.os.Looper.loop(Looper.java:194)
       at android.app.ActivityThread.main(ActivityThread.java:5706)
       at java.lang.reflect.Method.invoke(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1033)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:828)

MediaSession 代码 -

private void initMediaSession() throws RemoteException {
        if (mediaSessionManager != null) return; //mediaSessionManager exists

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mediaSessionManager = (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE);
        }
        // Create a new MediaSession
        mediaSession = new MediaSessionCompat(this, "AudioPlayer");
        //Get MediaSessions transport controls
        transportControls = mediaSession.getController().getTransportControls();
        //set MediaSession -> ready to receive media commands
        mediaSession.setActive(true);
        //indicate that the MediaSession handles transport control commands
        // through its MediaSessionCompat.Callback.
        mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS|MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);


        //Set mediaSession's MetaData
        updateMetaData();


        mediaSession.setCallback(new MediaSessionCompat.Callback() {
            @Override
            public void onPlay() {
                super.onPlay();

                resumeMedia();
            }

            @Override
            public void onPause() {
                super.onPause();

                pauseMedia();
            }

            @Override
            public void onSkipToNext() {
                super.onSkipToNext();

            }

            @Override
            public void onSkipToPrevious() {
                super.onSkipToPrevious();

            }

            @Override
            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {

                if (su.getHeadsetEnableSwitch()) {

                    String intentAction = mediaButtonIntent.getAction();
                    if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
                        KeyEvent event = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);

                        if (event != null) {


                            int action = event.getAction();
                            Log.e("Headset key: ", String.valueOf(action));
                            if (action == KeyEvent.ACTION_DOWN) {

                                Log.e("Headset: ", "Action down");

                                headsetClickCount++;

                                new Handler().postDelayed(new Runnable() {
                                    @Override
                                    public void run() {

                                        if (headsetClickCount == 1) {

                                            if (isPng()) pauseMedia();
                                            else resumeMedia();

                                            headsetClickCount = 0;

                                        } else if (headsetClickCount == 2) {

                                            if (su.getDoubleClickAction() == 0) {
                                            } else if (su.getDoubleClickAction() == 1)
                                                skipToPrevious();
                                            else if (su.getDoubleClickAction() == 2) skipToNext();
                                            headsetClickCount = 0;
                                        } else if (headsetClickCount == 3) {

                                            if (su.getTripleClickAction() == 0) {
                                            } else if (su.getTripleClickAction() == 1)
                                                skipToPrevious();
                                            else if (su.getTripleClickAction() == 2) skipToNext();
                                            headsetClickCount = 0;
                                        }
                                    }
                                }, 750);
                            }

                            if (action == KeyEvent.FLAG_LONG_PRESS) {

                                if (su.getLongClickAction() == 0) {
                                } else if (su.getLongClickAction() == 1) skipToPrevious();
                                else if (su.getLongClickAction() == 2) skipToNext();

                            }


                            if (action == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {

                                Log.e("Headset: ", "headset sethook");
                                if (isPng()) pauseMedia();
                                else resumeMedia();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_NEXT) {

                                skipToNext();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_PREVIOUS) {

                                skipToPrevious();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_PAUSE) {

                                pauseMedia();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_PLAY) {

                                resumeMedia();
                            }


                        }
                    }

                    return true;

                }

            return true;
            }
        });



    }

可能是什么问题以及如何解决?

我的想法 - 可能是因为用户在我的应用仍在播放时打开了其他具有此功能的音乐应用。

【问题讨论】:

    标签: android android-mediaplayer audio-player headset android-mediasession


    【解决方案1】:

    你必须创建你自己的媒体按钮接收器类,比如MyMediaButtonReceiver.java,它扩展MediaButtonReceiver,除了你必须重写的onReceive方法之外它将是空的,在尝试之间调用super.onReceive(...) -catch 捕获IllegalStateException:

    public class MyMediaButtonReceiver extends MediaButtonReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            try {
                super.onReceive(context, intent);
            } catch (IllegalStateException e) {
                Log.d(this.getClass().getName(), e.getMessage());
            }
        }
    }
    

    然后你必须在你的 Manifest 中声明那个接收器类(或者替换你之前的 MediaButtonReceiver 类声明,如果你有的话),比如:

    <receiver android:name=".MyMediaButtonReceiver" >
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_BUTTON" />
        </intent-filter>
    </receiver>
    

    【讨论】:

    • 那么 OnReceive() 收到了什么?按钮点击的意图?
    • 是的,它会捕获物理按钮的点击,如果您按照指示实现它,您的应用将不再因该异常而崩溃。相信我,它有效,我在自己的应用程序中遇到了同样的问题;)很高兴看到这个问题从 crashlytics 控制台中消失了。
    • 我会尝试这个解决方案并回复您。可能需要一些时间,因为该应用已经在 Playstore 上。
    • 当然,我也做了同样的事情,发布了我的应用程序的新版本并修复了该问题。这样做,你会看到你的 crashlytics 无崩溃统计图表真的上升了!
    • 完全没问题。我想补充一点,这个实现不会干扰你当前对按钮的处理(你发布的代码),因为它只是拦截了原始扩展类(MediaButtonReceiver)抛出的异常,它只能防止崩溃但然后它调用super,因此不会干扰您预期的按钮行为。 “次要影响”将是偶尔(当崩溃发生时),音量按钮将不起作用,只是为了那个点击,从用户体验的角度来看,这比应用程序完全崩溃要好.
    【解决方案2】:
    class MyMediaButtonReceiver : MediaButtonReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            if (intent.action == Intent.ACTION_MEDIA_BUTTON) {
                val event = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
                if (event.action == KeyEvent.ACTION_UP || event.action == 
    KeyEvent.ACTION_DOWN) {
                when (event.keyCode) {
                    // handle cancel button
                    KeyEvent.KEYCODE_MEDIA_STOP -> context.sendIntent(ACTION_FINISH)
                   // handle play button
                    KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PAUSE -> context.sendIntent(ACTION_PLAY_PAUSE)
                    }
                }
            }
        }
    }
    

    用于向媒体服务发送事件的 kotlin 扩展

    fun Context.sendIntent(action: String) {
        Intent(this, MediaPlayerService::class.java).apply {
            this.action = action
            try {
                if (isOreoPlus()) {
                    startForegroundService(this)
                } else {
                    startService(this)
                }
            } catch (ignored: Exception) {
            }
        }
    }
    

    在清单中添加接收器

     <receiver android:name=".player.receivers.MyMediaButtonReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_BUTTON" />
            </intent-filter>
        </receiver>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多