Can't fix MediaController.show() exception

2019-04-20 05:06发布

问题:

I have an audio file playing in a foreground Service using MediaPlayer. When a user taps the notification associated with the foreground Service, I launch an Activity using the Intent like so:

Intent audioPlayIntent = new Intent(context, AudioPlayActivity.class);
audioPlayIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
audioPlayIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, audioPlayIntent, 0);

This Activity then binds to the service to show a MediaController to the user.

Here is the binding code in the Service:

public class AudioPlayerServiceBinder extends Binder{

    public AudioPlayerService getAudioService(){
        return AudioPlayerService.this; //this class is declared in AudioPlayerService.java, so it has access to the Service instance.
    }

}

..and in the Activity's onStart I have a call to this method:

private void bindAudioService()
    {
        Intent i = new Intent(this, AudioPlayerService.class);
        serviceConnection = new AudioServiceConnection();
        bindService(i, serviceConnection, 0);
    }

I'm getting an exception on the mediaController.show(5000) line below:

private class AudioServiceConnection implements ServiceConnection{

    AudioPlayerServiceBinder audioServiceBinder;
@Override
        public void onServiceConnected(ComponentName name, IBinder serviceBinder)
        {
            serviceConnected = true;
            Log.i(TAG, "Connected to audio player service.");
            audioServiceBinder = ((AudioPlayerServiceBinder) serviceBinder);
            AudioPlayActivity.this.audioService = audioServiceBinder.getAudioService();
            mediaController.show(5000);
        }

The exception being:

android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRoot.setView(ViewRoot.java:527)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
at android.view.Window$LocalWindowManager.addView(Window.java:424)
at android.widget.MediaController.show(MediaController.java:304)
at android.widget.MediaController.show(MediaController.java:249)
at com.myapp.AudioPlayActivity$AudioServiceConnection.onServiceConnected(AudioPlayActivity.java:295)

I can recreate the same exception by:

  1. Clicking the notification to open the Activity
  2. Pressing back to close the activity.
  3. Clicking the notification to open a new version of the activity.

This led me to believe that the mediaController is somehow leaking and trying to show itself in the original Activity instance. I couldn't find any reason for that to happen though, as the mediaController is instantiated within the Activity's onCreate() and only tied to the activity itself. (The activity then handles passing commands through to the service).

回答1:

I think you are calling show() too early, before the previous activity completes the lifecycle. BadTokenException can be avoided by delaying call to show() until all the lifecycle methods are called. You may post a delayed runnable for this. Or you can try following,

if (!((Activity)your_context).isFinishing()) {
    mediaController.show(5000);
}


回答2:

Fixed the issue

I too was having the same issue and fixed it by doing the following,

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    try{
        mediaController.show(0);
    }catch(Exception e){
        e.printStackTrace();
    }
}

Now it works like a charm.



回答3:

I believe the Problem is in this line.

AudioPlayActivity.this.audioService = audioServiceBinder.getAudioService();

You can look here for the details.Read all the comments in it, not just the answer.



回答4:

Inside AudioPlayActivity's onCreate(Bundle):

Instead of using setContentView(int), inflate the layout (if you are already doing this, skip ahead):

Declare a global View variable:

View mContentView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mContentView = getLayoutInflater().inflate(R.layout.your_activitys_layout, null);       

    // initialize widgets
    Button b = (Button) mContentView.findViewById(...);

    ....
    ....

    // Finally
    setContentView(mContentView);
}

Change AudioServiceConnection to the following:

private class AudioServiceConnection implements ServiceConnection{

    AudioPlayerServiceBinder audioServiceBinder;
    @Override
    public void onServiceConnected(ComponentName name, IBinder serviceBinder)
    {
        serviceConnected = true;
        Log.i(TAG, "Connected to audio player service.");
        audioServiceBinder = ((AudioPlayerServiceBinder) serviceBinder);
        AudioPlayActivity.this.audioService = audioServiceBinder.getAudioService();

        mContentView.post(new Runnable() {

            @Override
            public void run() {
                mediaController.show(5000);
            }
        });
    }

This should get rid of the WindowManager$BadTokenException.

Apologies if I totally misunderstood the question.



回答5:

From the steps you have mentioned, It seems onConnected() is being invoked on a leaked instance of previous activity created in step 1. If the service is on demand (bound service) then you should bind/unbind in onResume()/onPause() respectively.

To confirm instance leaks, place :

log.i("LEAKTEST", "Connected to instance " + this.toString());

inside onConnected().

Now, re-create the scenario, and note the object id's in logcat, it'd be like "@1246464". Check that its called only once , on a new object id, every time the activity is started.