How to make WakefulService (IntentService) wait until MediaPlayer finishes? - android

I'm trying to make an app, which plays series of sounds using MediaPlayer, at scheduled times. To properly handle the wake lock and schedule the playback I used CommonsWare's WakefulIntentService.
Unfortunately, the IntentService's worker thread quits right after I call MediaPlayer.play() and neither MediaPlayer registered listeners are called. Instead, the exception is logged:
W/MessageQueue(6727): Handler (android.media.MediaPlayer$EventHandler) {4160d820} sending message to a Handler on a dead thread
W/MessageQueue(6727): java.lang.RuntimeException: Handler (android.media.MediaPlayer$EventHandler) {4160d820} sending message to a Handler on a dead thread
W/MessageQueue(6727): at android.os.MessageQueue.enqueueMessage(MessageQueue.java:294)
W/MessageQueue(6727): at android.os.Handler.sendMessageAtTime(Handler.java:473)
W/MessageQueue(6727): at android.os.Handler.sendMessageDelayed(Handler.java:446)
W/MessageQueue(6727): at android.os.Handler.sendMessage(Handler.java:383)
W/MessageQueue(6727): at android.media.MediaPlayer.postEventFromNative(MediaPlayer.java:2063)
W/MessageQueue(6727): at dalvik.system.NativeStart.run(Native Method)
As far as I understand, it is caused by the worker thread being already dead when MediaPlayer completes. If I pause the thread by means of the debugger and let the player complete, everything works fine.
In my listeners I not only release MediaPlayer's resources, but also use OnCompletionListener to do consecutive MediaPlayer.play() calls until the sound queue is empty.
I tried putting a wait loop right after the initial play() call, checking for a custom completion flag, but it seems to freeze because MediaPlayer's callbacks are called on the same thread play() was called.
The question is, how can I make the worker thread not quit before I let it do so (i.e. the has been processed and the onCompletion() method has been called for the last time?
Here is the code of my service:
public class SoundService extends WakefulIntentService {
private static final TAG = "SoundService";
private final Queue<SoundDescriptor> soundQueue = new ConcurrentLinkedQueue<SoundDescriptor>();
private final OnCompletionListener onComediaPlayerletionListener = new OnComediaPlayerletionListener() {
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
mediaPlayer.reset();
try {
if (!playNextFromQueue(mediaPlayer)) {
Log.v(TAG, "Reached end of queue. Cleaning up.");
release();
}
} catch (Exception e) {
Log.e(TAG, "Exception!", e)
release();
}
}
};
private final OnErrorListener onErrorListener = new OnErrorListener() {
#Override
public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
Log.v(TAG, "Error!!");
release();
return false;
}
};
public SoundService() {
// populate soundQueue
}
protected void doWakefulWork(Intent intent) {
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setOnComediaPlayerletionListener(onComediaPlayerletionListener);
mediaPlayer.setOnErrorListener(onErrorListener);
playNextFromQueue(mediaPlayer);
}
private boolean playNextFromQueue(MediaPlayer mediaPlayer) throws IllegalArgumentException, IllegalStateException, IOException {
SoundDescriptor descriptor = soundQueue.poll();
if (descriptor != null) {
mediaPlayer.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
descriptor.close();
mediaPlayer.prepare();
mediaPlayer.start();
return true;
} else {
return false;
}
}
}

To properly handle the wake lock and schedule the playback I used CommonsWare's WakefulIntentService.
That's not an appropriate choice.
Unfortunately, the IntentService's worker thread quits right after I call MediaPlayer.play() and neither MediaPlayer registered listeners are called.
That's why it's not an appropriate choice. :-)
The question is, how can I make the worker thread not quit before I let it do so (i.e. the has been processed and the onCompletion() method has been called for the last time?
Don't use WakefulIntentService. Don't use IntentService. Use Service. Manage the WakeLock yourself, and call stopSelf() on the service when the audio is finished.

The problem with MediaPlayer is that it caches a Handler to the current thread when it is instantiated. In case the current thread has no Looper, it defaults to the main thread. It will use this handler to execute the listeners callbacks.
Lets say you instantiate the player and use it inside a service's thread. If you call the player methods through the service, and then this service thread dies, the player will try to call the callbacks to the registered listeners using a handler pointing to the deceased thread, and it will throw the exception shown in the question.
I've find MediaPlayer very tricky and unreliable. The simplest solution is to use the player in the main thread. They are short calls, shouldn't raise ANR's.

Related

Do I need to run MediaPlayer in a service in Android Studio?

The following code is from the project .
Maybe it's a long time operation when I use the MediaRecorder control, so the author run MediaRecorder in a service, you can see Code B.
Maybe it's a long time operation to play a audio too, so I think the author should run MediaPlayer in a service, but why doesn't Code A do that?
Code A
public final class MediaPlayerHolder implements PlayerAdapter {
public static final int PLAYBACK_POSITION_REFRESH_INTERVAL_MS = 1000;
private MediaPlayer mMediaPlayer;
#Override
public void play() {
if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
mMediaPlayer.start();
if (mPlaybackInfoListener != null) {
mPlaybackInfoListener.onStateChanged(PlaybackInfoListener.State.PLAYING);
}
startUpdatingCallbackWithPosition();
}
}
...
}
Code B
public class RecordingService extends Service {
public class LocalBinder extends Binder {
public RecordingService getService() {
return RecordingService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return myBinder;
}
public void startRecording(int duration) {
setFileNameAndPath();
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setOutputFile(mFilePath);
mRecorder.setMaxDuration(duration); // set the max duration, after which the Service is stopped
mRecorder.setAudioChannels(1);
mRecorder.setAudioSamplingRate(44100);
mRecorder.setAudioEncodingBitRate(192000);
...
}
}
The purpose of using a Service is to have your code run on the background, you can do operations that don't require a user interface and even have your code run beyond the onDestoy() method of an activity. This is how music players allow you to listen to music even after you have closed the Application's Activity.
There are 3 types of services:
Foreground Service
Background Service
Bound Service
Why is Code B in a Service but Code A is not?
From the Bound services overview:
A bound service is the server in a client-server interface. It allows
components (such as activities) to bind to the service, send requests,
receive responses, and perform interprocess communication (IPC). A
bound service typically lives only while it serves another application
component and does not run in the background indefinitely.
In other words it allows communication with other applications or across separate processes. And that's the main reason the author would use the Service. It has nothing to do with performance.
About performance:
Code B does not account for performance.
From the Service overview:
Caution: A service runs in the main thread of its hosting process; the
service does not create its own thread and does not run in a separate
process unless you specify otherwise. You should run any blocking
operations on a separate thread within the service to avoid
Application Not Responding (ANR) errors.
So merely using a Service does not guarantee performance. In Code B we have the method startRecording(), which initializes MediaRecorder and sets some paramaters for the Recording. This doesn't not mean that this method will run as soon as the service starts. The Author has used a Bound Service, you can tell by the method:
#Override
public IBinder onBind(Intent intent) {
return myBinder;
}
Which means that any component that binds to it (such as an activity or another process can call its method startRecording() ). Please check the Bound Service link for more info. In the project this gets called from RecordViewModel.startRecording().
If you are worried about performance. Code B should start a new Thread inside startRecording(int duration). There are many ways to do it. Here is one:
public void startRecording(int duration) {
( new Thread( new Runnable() {
#Override
public void run() {
setFileNameAndPath();
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setOutputFile(mFilePath);
mRecorder.setMaxDuration(duration); // set the max duration, after which the Service is stopped
mRecorder.setAudioChannels(1);
mRecorder.setAudioSamplingRate(44100);
mRecorder.setAudioEncodingBitRate(192000);
...
}
}) ).start();
}
As for Code A the only thing happening there is the call to MediaPlayer.start(), which already starts a new thread internally.
From the MediaPlayer class source code:
public void start() throws IllegalStateException {
//FIXME use lambda to pass startImpl to superclass
final int delay = getStartDelayMs();
if (delay == 0) {
startImpl();
} else {
new Thread() {
public void run() {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
baseSetStartDelayMs(0);
try {
startImpl();
} catch (IllegalStateException e) {
// fail silently for a state exception when it is happening after
// a delayed start, as the player state could have changed between the
// call to start() and the execution of startImpl()
}
}
}.start();
}
}
If performance is of concern with Code A, then MediaPlayerHolder.loadMedia(String path) is where a separate Thread should be used.
So to answer your question. No, you do not need to run MediaPlayer in a Service. It depends on your requirements.
Regards

Stopping a Handler Thread

On one of our applications, we use a background service with a notification (basically a foreground service but you get the idea, activity is closeable while the service stays alive.)
On this service, we use 3 separate HandlerThreads with Handlers to manage various operations with some delay (for example, 250 milliseconds). Now, these actions need to be stopped if the screen goes off and be resumed if the screen goes back on, due to this situation we added a broadcast receiver to the service, and created-deleted threads. Everything works fine so far.
In order to stop the operations, we deleted the messages on handlers by calling Handler.removeCallbacksAndMessages(null) and it actually clears the message queue. However, the handler thread stays alive. And this is a problem.
In order to stop the thread we used HandlerThread.quit() which internally calls Looper.quit() that we thought, that it will finish the thread, but no sir, it does not delete the thread because we get some reports from Fabric that goes pthread_create failed (1040kb stack), try again or something. Under it, there were 940 separate threads that named the same, which caused a OOM (Out Of Memory) error. This was a huge mistake from us.
The question: How can we stop the handler threads? Is HandlerThread.interrupt() will be enough? Any help is appreciated, thanks. PS: I cannot share any source codes, and in this situation I don't think it is necessary since the question itself is self-explanatory.
Edit: Since you asked for some code, I'm showing an example of some logic I'm following.
public class ThreadHelper implements Runnable
{
private HandlerThread handlerThread = new HandlerThread("ThreadName");
private Handler handler;
private boolean shouldRun = true;
public ThreadHelper()
{
handlerThread.start();
startThread();
}
// Called if the screen state is turned on.
public void startThread()
{
if (handlerThread == null)
{
handlerThread = new HandlerThread("ThreadName");
handlerThread.start();
}
if (handler == null)
{
handler = new Handler(handlerThread.getLooper());
handler.post(this);
}
}
// Called if the screen state is turned off.
public void stopThread()
{
shouldRun = false;
handler.removeCallbacksAndMessages(null);
handlerThread.quit();
try
{
handlerThread.interrupt();
}
catch (Exception ignored)
{
// Skipped Thread.currentThread().interrupt() check here since this is
// called from a different thread that is not associated.
}
// remove variables.
handler = null;
handlerThread = null;
}
#Override
public void run()
{
if (shouldRun)
{
// rest of the code without having a long-running
// operation. Mostly ends in 1~2 millseconds.
// Continue looping.
handler.postDelayed(this, 250);
}
}
}

How to pause and resume a Thread?

I have a thread:
class SomeRunnable implements Runnable {
#Override
public void run() {
while (true) {
//some code...
try {
Thread.sleep(33);
} catch (InterruptedException e) {
return;
}
}
}
}
which I start using:
someThread = new Thread(new SomeRunnable());
someThread.setName("SomeThread");
someThread.start();
If I want to stop the thread I simply interrupt it:
someThreat.interrupt();
How can I later resume the thread?
Thank you!
You can use wait() and notify() method.
wait()
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).
notify()
Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods.

Should I create a worker thread when extend a Service?

I have a service for playing music that extends Service class. It has local MediaPlayer instance and performs music playback without creating working thread. It looks like UI thread is not blocked, I can freely navigate through my app while listening to music. I am a bit confused because on documentation guide it is said that such operation blocks main thread. Could someone explain what`s going on? Should I create working thread inside my service?
A service runs in the same process as the application in which it is
declared and in the main thread of that application, by default. So,
if your service performs intensive or blocking operations while the
user interacts with an activity from the same application, the service
will slow down activity performance. To avoid impacting application
performance, you should start a new thread inside the service.
The snippet looks like this
public class MusicPlayerService extends Service implements MediaPlayer.OnPreparedListener,
MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
...
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
playNext();
}
#Override
public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
mMediaPlayer.reset();
}
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.start();
}
#Override
public void onCreate() {
super.onCreate();
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.setOnErrorListener(this);
mMediaPlayer.setOnCompletionListener(this);
startForeground(1, mNotification);
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
#Override
public void onDestroy() {
mMediaPlayer.release();
}
public void onPlay() {
mMediaPlayer.start();
}
public void onPause() {
mMediaPlayer.pause();
}
}
https://developer.android.com/guide/components/services.html
As we can see in official document of Android:
Service This is the base class for all services. When you extend this
class, it's important that you create a new thread in which to do all
the service's work, because the service uses your application's main
thread, by default, which could slow the performance of any activity
your application is running.
IntentService This is a subclass of
Service that uses a worker thread to handle all start requests, one at
a time. This is the best option if you don't require that your service
handle multiple requests simultaneously. All you need to do is
implement onHandleIntent(), which receives the intent for each start
request so you can do the background work. The following sections
describe how you can implement your service using either one for these
classes.
If you are doing heavy work in service it's possible to block ui and you should create a thread or use intent service for non-blocking ui.

Java Android Native thread issues

I have one thread that does lot of time consuming tasks. The tasks are being done in the native part in c++. I would like to cancel the operation that is being done in the native, the code for that is place. I can reset everything.
mWorker = new WorkerThread("Worker thread");
mWorker.start();
//From Main thread:- Interrupting
mWorker.interrupt();
if(mWorker.isInterrupted()) {
Log.i(MOD_TAG, "Worker thread is interupptedd!!! ");
}
//Worker thread
public class WorkerThread extends Thread implements Runnable{
public void run() {
while(!Thread.currentThread().isInterrupted()) {
Looper.prepare();
mHandler = new WorkerHandler();
Looper.loop();
}
class WorkerHandler extends Handler {
#Override public void handleMessage(Message msg) {
try {
switch(msg.what) {
//do something native code
}
}
catch (Exception e) {}
}
}
}
}
Even if the workerthread is interrupted I cannot send any message to the worker thread while the worker thread is doing processing. Can I do something to post a message to workerthread or do something else that could let me call a native method within the same thread.
In your example, I don't understand what that Handler is doing inside the Thread. Once you call loop() within the run, that call will block until the looper is stopped via quit() or quitSafely(). The call is basically just a loop which reaps a queue for messages. Your check for interrupt will never happen.
I would recommend something like this. If you want your code to be managed by a handler you would do something like:
HandlerThread handlerThread = new HandlerThread("NativeHandler");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()) {
public void handleMessage(Message msg) {
someObject.callNativeLongRunningFuction();
}
};
However interrupt still won't do anything because the looper only handles one message at a time. So if it is stuck handling callNativeLongRunningFunction(), that is not going to help you really either. If you want to have interrupt stop the ongoing execution of the jni call, the I don't think this approach will work at all with the given information. Interrupt in java only sets a flag and when there is a call to wait(), it will throw an exception when that flag is checked and also set. But for a jni call there isn't a call to wait(), the java stack is sort of blocked but it is not in the middle of a wait() either. So unless you check interrupted within the native runtime, that function will continue to run. Overall I am guessing this probably would not be what you really want.
If that is so, I would recommend something like this instead.
public class NativeThreadTask {
public native void start();
public native boolean isRunning();
public native boolean cancel();
}
Inside the native implementation of that class, you would then use a pThread to call your native long running function. Start and Cancel would manipulate that pThread which would run the expensive function in a separate thread. Using pthread_cancel you can interrupt that pthread instance too. This moves the long operation off your thread and out of your runtime, while still allowing you to control when the pthread interrupt mechanism is invoked but over the jni bridge. If you don't even want to interrupt and if the long running native call is iterating over a large amount of data, then it might be worthwhile to have cancel() simply change bool that is evaluated within each iteration of the native function's loop.
So with the given example you could probably do something like this.
NativeThread nativeThread = new NativeThread();
Handler handler = new Handler() {
public void handleMessage(Message message) {
NativeThread nativeThread = (NativeThread)message.obj;
switch(message.what) {
case 0:
if (!nativeThread.isRunning()) {
nativeThread.start();
}
break;
case 1:
if (nativeThread.isRunning()) {
nativeThread.cancel();
}
break;
default:
}
}
};

Categories

Resources