Can't fix MediaController.show() exception - android

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:
Clicking the notification to open the Activity
Pressing back to close the activity.
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).

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);
}

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.

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.

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.

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.

Related

Listener behavior in relation to Activity lifecycle

I have an Activity with a private OnSharedPreferenceChangeListener, the listener's work is defined on the onCreate() method of the Activity. The listener is registered to the sharedPreferences of the application.
The change itself is triggered by a Service in response to an sms received intent.
Will the listener receive the callback when the Activity itself has died? are there cases where it will not?
The listener is defined (roughly):
private OnSharedPreferenceChangeListener _sharedPreferenceListener;
public void onCreate(Bundle bundle){
...
_prefs = PreferenceManager.getDefaultSharedPreferences(this);
_prefs.registerOnSharedPreferenceChangeListener(_sharedPreferenceListener);
...
_sharedPreferenceListener = new SharedPreferences.OnSharedPreferenceChangeListener(){ /*doing some work here*/};
...
}
please igonre the logic here if correct or not, assume that the code works, my main concern is how the listener reacts to changes in the lifecycle of the activity.
Thanks,
actually, since the listener doesn't know anything about the activity (and as such you can use it anywhere , not just in an activity), you will get notified no matter where you use it.
Also, since you can't know for sure what it does with the context , you should use the application context instead in this case (so that you won't have memory leaks, though I doubt it needs a reference to the activity).
Of course, if the listener itself is referenced by weak reference, and the activity doesn't have any reference to itself on any other class, the listener can be GC-ed too. You can see in the code of Android (or at least of API 19) that in the class "android.app.SharedPreferencesImpl" (example link here) , you have a WeakHashMap of listeners, so it might mean that the activity that hosts the listener can be GC-ed and so the listener will stop from being called. Here is the relavant code of Android:
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
...
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(this) {
mListeners.put(listener, mContent);
}
}
So, as I've written, best if you just put the application context in case you wish to keep listening to this event.
Or, in case you do wish to stop listening to this event, just unregister it when the activity is being destroyed.
to prove it, you can simply run your app...
here's my proof app:
MainActivity.java
public class MainActivity extends Activity {
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
preferences.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
android.util.Log.d("AppLog", "changed!");
}
});
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
startActivity(new Intent(MainActivity.this, Activity2.class));
}
}, 1000);
finish();
}
}
Activity2.java
public class Activity2 extends Activity {
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_activity2);
//if you call here System.gc(); , you have a good chance that the listener won't be called
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//this may or may not cause the listener to write to the log
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(Activity2.this);
preferences.edit().putBoolean("test", true).commit();
}
}, 1000);
}
}
Will the listener receive the callback when the Activity itself has died?
-> No, it won't. Because when your activity dies, the _prefs and _sharedPreferenceListener fields will be destroyed.
You could check this question for more details on OnSharedPreferenceChangeListener :
SharedPreferences.onSharedPreferenceChangeListener not being called consistently
You must un-register the listener in onDestroy() of activity, else Activity object will stay in memory.

How to start a mediaplayer by pressing a button in android?

I am new to android and I am now doing an exercise which the application has already stored a audio file(mp4) in /res/raw/ folder and this file can be referenced in android Service class as R.raw.audiofile. In the Service class I have created three methods
onCreate();
onStartCommand();
onDestroy();
in onCreate() I have created a MediaPlayer and in onStartCommand() I have started the MediaPlayer
mp.start() and
returned STRT_STICKY
to play it and in onDestroy() I have done this:
mp.stop();
in the xml layout I have created a Button with this attribute:
android:onClick="onClickStart"
which calls a method in the MainActivity and this action now shoud playback the audio. however, I am now stuck here on how to link this to the music file so that this button should start the audio. can anyone please give me some idea?
so I have one Main activity class and one (My)Service class (extends to Servcie) and one xml file for layout to perform this action.
You can communicate between the Activity and Service using an Intent or by binding to the Service and sending a Message. In this case, binding to the Service and sending a Message in the Button's click listener is probably the cleanest approach. That also gives you the opportunity to appropriately update UI by passing a Messenger/Handler in the replyTo field of the Message. I'll edit with code snippets when I get to a PC.
Edit:
Most of the code you need is in the Android docs here.
The basic idea is that you need to create a Handler class inside your Service. The proper way to do this (to avoid leaking the Handler and the accompanying Lint warning) is as follows:
private static class MyHandler extends Handler {
private WeakReference<MyService> mService;
public MyHandler(MyService service) {
mService = service;
}
#Override
public void handleMessage(Message msg) {
// Your message handling here...
// You can use the members/methods of the Service with
// mService.get().____
}
}
Then you would create a Messenger member and override the onBind method in the Service, like so:
private Messenger mMessenger = new Messenger(new MyHandler(this));
#Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
Then you need to call bindService with an appropriate Intent and ServiceConnection from your Activity. See the link for a code example. In the onServiceConnected method of the ServiceConnection you can stash a Messenger for the Service with:
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
mMessenger = new Messenger(service);
}
You can likewise create a Handler and Messenger in the Activity and supply it as the replyTo field of any Message objects sent to the Service. In that way, you can tell the Activity whether the MediaPlayer started successfully or not and update UI as appropriate.
You need a OnclickListener for your Button like
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
}
Look here for the MediaPlayer

Wait with operation until Fragment's Views are created

I am programming a Music Player and the Music plays in a background Service. When the user kills the Activity which hosts 3 Fragments, and then restarts the Activity again, I send a Broadcast from the Service that contains information about the current playing song, and the list of songs that the user added to his session.
The problem is, every time I want to set the last information into the Fragments nothing happens because their creation takes too long, and the Broadcast doesn't get handled like they should.
How can I let the Service or the Broadcast wait until the Fragments are created so they are handled appropriately?
Here are the relevant code snippets:
//When the activity binds to the service, refresh the fragments
private ServiceConnection conn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
LocalBinder binder = (LocalBinder) service;
myService = binder.getService();
myService.setBound(true);
if(myService.startedOnce) {
myService.refreshFragments();
}
myService.startedOnce = true;
}
}
//This is the broadcast receiver of the Fragment
//it receives the broadcast too soon, and I can't set the
//Views so they are always empty.
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(MusicService.REFRESH_ALL)) {
Song current = intent.getParcelableExtra("song");
setCurrentSong(current);
}
}
The easiest thing to do would simply be hold on to the information until the Fragment is ready to display it. Use the Fragment's setArguments() method to attach the information into the Fragment.
#Override
public void onReceive() {
String action = intent.getAction();
if(action.equals(MusicService.REFRESH_ALL)) {
// Creating a new Bundle so the Fragment can control its own object
Bundle args = new Bundle(intent.getExtras());
Fragment fr = getUsingFragment();
fr.setArguments(fr);
}
}
Then, in the Fragment's onCreateView() simply pull the arguments from getArguments() and build the view with the values.
#Override
public void onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle args = getArguments();
if(args != null) {
// code to set values if arguments are set
} else {
// code to set values if arguments are not set
}
}
Another way to do it would be to use setter methods in which the Fragment itself puts values into a Bundle for setArguments(). That way, you can update the views whenever the View has been created on top of setting the arguments for the possible event when the Fragment's View is destroyed and must be recreated.
Note: You can only call setArguments() before the Fragment has been attached to the Activity. You can however update the Bundle that you pass in by setArguments by retrieving a reference to it from getArguments(), then simply putting in the values. So instead of calling setArguments() from your receiver, do something like this:
public void setCurrentSong(Song extra) {
Bundle args = getArguments();
args.putParcable(KEY_MAP, extra);
if(/* Views are created */) {
// update and invalidate the views
}
}
How I fixed this
As I was using a Service for Media Playback, I wanted to bring up last listened songs from the service so I could directly play it. This was old logic, but I actually built my code around it. Until thusfar I bumped into it.
This was happening
FragmentActivity is created
Service gets started and bound to
Meanwhile the Fragments get created asynchronously
As soon as the Service starts, it sends out a Broadcast with latest information
Because the both the Service and the Fragment creations are asynchronous, the broadcast would be sent from the service, but because the BroadcastReceivers in the Fragments weren't even initialized yet, they would not receive the Intent.
What I did to fix it
I somehow had to use a callback that made sure that
the Service was created and bound to
the fragments created and views are set
So I used the ServiceConnection and to be precise, the onServiceConnected() method. There I got the preferences in which the last song was saved, and then send out the Broadcast and the Fragments received it and the Views were appropiately set. This also worked for orientation changes.
The code
//This is the code in the FragmentActivity
private ServiceConnection conn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
LocalBinder binder = (LocalBinder) service;
myService = binder.getService();
myService.setBound(true);
if (myService.startedOnce) {
myService.refreshFragments();
} else {
sendLastSavedSong();
}
myService.startedOnce = true;
}
public void onServiceDisconnected(ComponentName className) {
myService.setBound(false);
}
};
Can't you do something in the fragment like
Song current=null;
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(MusicService.REFRESH_ALL)) {
current = intent.getParcelableExtra("song");
}
}
#Override
public void onResume()
{
if(current!=null) setCurrentSong(current);
super.onResume();
}
my solution was just creating your own callback interfaces. At the very end of onCreateView method of ur fragment just call your callback method, which tells ur mainactivity that the creation is done.
It worked for me, hope helps u too.

Finish Activity() from a separate myJavaClass.java

I have tried almost all the solutions from SO but no success :(.
I have a simple myJavaClass.java with a couple of functions.
One of the functions in myJavaClass : startActivity() starts MyCustomActivity
public startActivity(Context context)
{
Intent intent = new Intent(context, MyCustomActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
}
This launches MyCustomActivity() as expected.
Now I have another function in myJavaClass.java to close/finish MyCustomActivity but it is not able to do so!
I have tried
Making MyCustomActivity SingleTop in manifest and creating the activity via an intent as above
Passing an activity instance to "this" in onCreate() of MyCustomActivity and calling MyCustomActivity.activity.finish() from myJava.class but that doesnt work as well
Please help me. I have been stuck here for hours now. I know the solution is very simple and conceptual but I am a newbie. Just building Java/Android concepts!
EDIT
MyCustomActivity
public Activity activity;
OnCreate()
{
...
this = activity;
}
MyJavaClass
public closeActivity(Context context)
{
Activity customActivity = MyCustomActivity.activity;
customActivity.finish();
}
I think that what you are trying to do is fundamentally bad. For a start, outside of the Activity code, there are no guarantees that the activity still exists - the memory manager may have cleaned it up, the user may have pressed Back etc. Think of Activities as independent entities - you can start them, and you can optionally get a result back when they finish what they're doing, but that's it.
Think about whether you really have to programmatically close the activity from outside it - I'd say this is an unusual design, but there are circumstances where it may be appropriate.
If so, what I think you want is a publish/subscribe system whereby MyCustomActivity can register a listener with MyJavaClass, and then receive a callback whereupon it can 'finish' itself.
public Activity activity implements FinishListener
{
public void onCreate(...)
{
//where does MyJavaClass come from? see in a minute
MyJavaClass myjava = getMyJavaclass();
myJava.addFinishListener( this );
}
public void onFinishCallback()
{
this.finish();
}
}
and
public class MyJavaClass
{
private List<FinishListener> finishListeners = ...;
public void addFinishListener( FinishListener fl )
{
this.finishListeners.add(fl);
}
public closeActivity(Context context)
{
for ( FinishListener fl : finishListeners )
{
fl.onFinishCallback();
}
}
}
and
public interface FinishListener
{
void onFinishCallback();
}
Now the only remaining issue is how to get MyJavaClass from the Activity. That's up to you - you may already know how, you may be able to put it in your Application implementation, it could be a singleton (bad), the listeners could be static (bad) or various other options.
Oh, and don't forget to remove the listener again in the Activity's onDestroy() method!
Just try this....
public closeActivity(Activity _activity)
{
_activity.finish();
}
you can't finish activity from other class until you have the reference of instance of Activity in that class, give the reference in that class and call finish() method to stop the activity.
activity.finish();

How to force main Acivity to wait for subactivity in Android?

I am calling a subactivity from main activity. This subactivity should take few numbers from user (i'm using Edit text control to achieve this), save them to static variable in another class and terminate. I want main activity to wait for subactivity but both are just running simultaneously. Even doing sth like that doesn't help:
Thread t = new Thread(new Runnable(){
public void run(){
Log.v("==================", "run "+new Date());
startActivityForResult(new Intent(ctx,myCustomSubactivity.class),1);
} });
Log.v("==================", "calling run "+new Date());
t.start();
try {
t.join();
} catch (InterruptedException e) {Log.v("==================", "can't join");}
Log.v("==================", "back from activity "+new Date());
do you know how to force main activity to wait? Thread.wait() method is not supported in Android(program throws error).
May be I'm missing something but why don't just use startActivityForResult and onActivityResult mechanism? You could get result from you subactivity from intent it was resulted with.
Edit: BTW as far as I understand, if you will run Object.wait() from Activity code if will hold UI tread whitch can result in Application not responding error.
I agree with Nikolay this is definitely the android way to do this.
Start the subactivity with startActivityForResult in the sub activity use setResult to add an result code and an intent with all the numbers you need in the data bundle.
In your first activity overwrite onActivityResult and retrieve the numbers from the Intent.
If you use the static variable this seems easier in the first moment but it is very insecure and there are some cases this may not work. If your program is send to the background your activities will be saved but if the phone runs low on memory the system will close your program and after the user resumes it everything looks like the moment the user left it but the static variables will be recreated to their initialization value.
Try to get used to the way the android activity lifecycle works. Using this approach will result in fewer used memory and a much better user experience.
Check out the Notepad example, it covers exactly this situation. And as others have said, the Android way is to have your first activity start up your second activity (not sub-activity!) and asynchronously listen for a response (not pause or wait, no need for joining, etc.).
Well... you can do it like this (btw, there's not straight forward way):
Have a singleton class, let's call it Monitor:
public class Singleton
{
private Singleton() { }
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
public class ParentActivity extends Activity
{
private void startAndWait()
{
Intent i = new Intent();
// initialize i
startActivityForResult(i);
Singleton si = Singleton.getInstance();
synchronized(si)
{
si.wait();
}
//do remaining work
}
}
public class ChildActivity extends Activity
{
protected void onCreate(Bundle savedInstance)
{
//do all the work
Singleton si = Singleton.getInstance();
synchronized(si)
{
si.notify();
}
}
}
I'm not here to judge if it's a good pattern or not but if you really need an activity to wait for a sub-activity, you can try this approach:
define an object (lock) over which the two activities get synchronized; this can (should) also work as the object to exchange data between those two activities and thus should be defined as static
in parent activity, start an async task (as the UI main thread cannot be in waiting state)
in the async task, start your sub-activity
the async task waits on the lock till it gets notified
the sub-activity does whatever it needs and notifies the waiting thread when it finishes
I did a similar thing in my app and IMHO had a good reason for this (not to bother a user with login screen upon app start or resume, the app tries to re-use credentials stored in a secured place and only in case it fails, it shows this login screen. So yes, basically any activity in my app can get "paused" and waits till the user provides correct credentials in the login activity upon which the login screen finishes and the app continues exactly where it got paused (in the parent activity).
In the code it would be something like this:
ParentActivity:
public class ParentActivity extends Activity {
private static final String TAG = ParentActivity.class.getSimpleName();
public static class Lock {
private boolean condition;
public boolean conditionMet() {
return condition;
}
public void setCondition(boolean condition) {
this.condition = condition;
}
}
public static final Lock LOCK = new Lock();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.parent_layout);
// do whatever logic you need and anytime you need to stat sub-activity
new ParentAsyncTask().execute(false);
}
private class ParentAsyncTask extends AsyncTask<Boolean, Void, Boolean> {
#Override
protected Boolean doInBackground(Boolean... params) {
// do what you need and if you decide to stop this activity and wait for the sub-activity, do this
Intent i = new Intent(ParentActivity.this, ChildActivity.class);
startActivity(i);
synchronized (LOCK) {
while (!LOCK.conditionMet()) {
try {
LOCK.wait();
} catch (InterruptedException e) {
Log.e(TAG, "Exception when waiting for condition", e);
return false;
}
}
}
return true;
}
}
}
ChildActivity:
public class ChildActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.child_layout);
// do whatever you need in child activity, but once you want to finish, do this and continue in parent activity
synchronized (ParentActivity.LOCK) {
ParentActivity.LOCK.setCondition(true);
ParentActivity.LOCK.notifyAll();
}
finish();
// if you need the stuff to run in background, use AsyncTask again, just please note that you need to
// start the async task using executeOnExecutor method as you need more executors (one is already occupied), like this:
// new ChildAsyncTask().executeOnExecutor(ChildAsyncTask.THREAD_POOL_EXECUTOR, false);
}
}

Categories

Resources