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
Related
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.
I have an Android app from which I receive BLE data (every 62ms via notifications). The app can save data via a BufferedWriter to a file. Upon each onCharacteristicChanged() callback, I call either an AsyncTask, Thread or an IntentService to do a file write if the user enabled file save.
The AsyncTask seems to work fine. But the docs say execute must be invoked on the UI thread, and I'm calling it from the BLE callback. Is that a problem? And how should I fix it?
Using Thread causes this error: GKI_exception out of buffers https://code.google.com/p/android/issues/detail?id=65455 (except my code is not scanning but receiving notifications) and if the file save is long, I need to power cycle the Nexus 7 (the app and BLE become totally unresponsive). Why does the Thread not work and how can I fix it?
The IntentService never goes to the onHandleIntent(). What are the issues here?
Here is some code:
...
_context = this.getApplicationContext();
...
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
...
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
...
int mode = 1;
if (mode==0) // Asynctask
new doFileWriteTask().execute(strBuild.toString());
else if (mode==1) // Thread
{
final String str = strBuild.toString();
new Thread(new Runnable() {
public void run() {
try {
_writer.write(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
else if (mode==2) // intentService
{
Intent mServiceIntent = new Intent(_context, writeFileService.class);
mServiceIntent.putExtra("foo", strBuild.toString());
startService(mServiceIntent);
}
}
...
};
private class doFileWriteTask extends AsyncTask<String, Void, Void> {
#Override
protected Void doInBackground(String... strings) {
try {
_writer.write(strings[0]);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private class writeFileService extends IntentService {
public writeFileService() {
super("writeFileService");
}
#Override
protected void onHandleIntent(Intent workIntent) {
String dataString = workIntent.getStringExtra("foo");
try {
_writer.write(dataString);
} catch (Exception e) {
e.printStackTrace();
}
}
}
...
But the docs say execute must be invoked on the UI thread, and I'm calling it from the BLE callback. Is that a problem? And how should I fix it?
The framework triggers the AsyncTask callback methods on the same thread it was called from (presumed to be the main thread). It doesn't really affect the background work, but you could see problems if you started trying to use onPostExecute() and the like. AsyncTask probably isn't the best choice to be called from a thread that you don't have control over.
Why does the Thread not work and how can I fix it?
I can't say exactly why you are still seeing errors, through spawning a series of private unsynchronized threads will probably lead to other headaches. If you want to use a single worker thread, a better choice would be to use a single HandlerThread that you can post to from your event callbacks using a Handler, something like:
…
_workerThread = new HandlerThread("Worker");
_workerThread.start();
_handler = new Handler(_workerThread.getLooper(), new Handler.Callback() {
#Override
public boolean handleMessage(Message msg) {
String str = (String) msg.obj;
_writer.write(str);
return true;
}
});
…
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
…
Message msg = Message.obtain(_handler, 0, strBuild.toString());
_handler.sendMessage(msg);
…
}
That solution is quite a bit more code, but given the frequency of writes this is probably the most efficient choice.
The IntentService never goes to the onHandleIntent(). What are the issues here?
You should pretty much never implement a top level Android component (activity, service, content provider, receiver) as an inner class, because they have to be declared in your manifest as well (and the XML syntax for inner classes is ugly). If your service does not have a matching entry in the manifest, then you will never see it start. You might want to have a look at the docs on using services.
At a minimum, a Service written as an inner class must be public static to work. Otherwise the framework cannot see it and cannot instantiate it using a default constructor (non-static inner classes mess with the constructor). Unless you are calling startService() inside of a try/catch right now, I'm surprised it isn't crashing when you attempt this.
IntentService is probably the simplest of your three choices because it is the most decoupled and the framework will handle queueing up work and tearing down the threads when all the incoming work is done.
At the moment I have got a singleton class that extends service like this:
public class ServiceSingleton extends Service {
private static ServiceSingleton instance;
private static boolean serviceSt;
private static PrefValues preferences;
private static Context context;
#Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
public static ServiceSingleton getInstance(Context cont) {
if (instance == null) {
context = cont;
// Some code
}
return instance;
}
So basically I run some methods in this class about every 30 minutes by using something like this:
private static void oneTasks() {
//task itself
}
private static void oneService() {
if (!serviceSt) {
serviceRunning = false;
return;
}
serviceRunning = true;
oneTasks();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
oneService();
}
}, (INTERVAL));
}
I also heard AlarmManager can do the same thing.
Anyway, my question is, If I am running periodical methods, which way to invoke methods is the best way(especially with the consideration of battery usage)?
At the moment I have got a singleton class that extends service like this
Yuck. Do not make a service be held indefinitely in a static data member.
So basically I run some methods in this class about every 30 minutes
You have not stated how you are doing that.
Anyway, my question is, If I am running periodical methods, which way to invoke methods is the best way(especially with the consideration of battery usage)?
If your objective is to only do this work when your process happens to be running for other reasons, you are welcome to use pretty much anything you want. I'd use ScheduledExecutorService.
If your objective is to do this work, even if your app is not running, AlarmManager covers that scenario. Team it with an IntentService, so that your process only needs to be in system RAM when it is actually doing work.
If your objective is to do this work, even if your app is not running, and even if the device falls asleep, you will need to use AlarmManager with a _WAKEUP alarm, coupled with either WakefulBroadcastReceiver, my WakefulIntentService, or the equivalent.
By using the Alarm Manager you can register a repeated alarm that will fire automatically every specific time, even if your application is closed. so it's a very efficient in term of battery usage.
Then inside the alarm's broadcast receiver you have to implement what you need. and you should consider creating a new thread or using IntentService class if your method will take more than a few seconds.
I know it's surely not the most elegant and best solution, but you could simply have a Thread with an infinite loop that had a SystemClock.sleep(1800000) at the end, so basically something like this:
final Thread buf_liberator = new Thread(
new Runnable() {
public void run() {
while (true) {
/* Your stuff */
SystemClock.sleep(1800000);
}
}
}
);
buf_liberator.setPriority(7);
buf_liberator.start();
Also you would need to have a stop condition inside the Thread as you can't stop it with the stop() method anymore.
You can also do it by CountDownTimer
CountDownTimer countDownTimer;
public void usingCountDownTimer() {
countDownTimer = new CountDownTimer(Long.MAX_VALUE, 10000) {
// This is called after every 10 sec interval.
public void onTick(long millisUntilFinished) {
setUi("Using count down timer");
}
public void onFinish() {
start();
}
}.start();
}
and onPause() method add
#Override
protected void onPause() {
super.onPause();
try {
countDownTimer.cancel();
} catch (Exception e) {
e.printStackTrace();
}
}
There are a few threads running in a service.
The threads need to post messages to UI / Activity
How would I pass over the Handler reference to the threads ? so that they can post their state changes to Activity ?
Or Better yet is there a way to globally expose handler ref like this ?
Handler getUIHandler();
Thank you in advance ;)
Create a Handler object in your UI thread. You can just create it at instantiation time if you like. Any thread launched from your activity can post messages or runnables to that handler. Threads launched from other activities, services, or whatnot will not work because there's no guarantee that your Activity is even running. (Actually, it might be a fun experiment to see if it works when the Activity is running, but you could never base a real app on this technique.)
In fact, you don't even need to create a Handler. Every View object contains its own Handler, so you can simply post your runnables to a view.
Or you could just call runOnUiThread()
From my notes on Handlers:
Usage patterns:
Pattern 1, handler plus runnables:
// Main thread
private Handler handler = new Handler()
...
// Some other thread
handler.post(new Runnable() {
public void run() {
Log.d(TAG, "this is being run in the main thread");
}
});
Pattern 2, handler plus messages:
// Main thread
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
Log.d(TAG, "dealing with message: " + msg.what);
}
};
...
// Some other thread
Message msg = handler.obtainMessage(what);
handler.sendMessage(msg);
Pattern 3, call runOnUiThread():
// Some other thread
runOnUiThread(new Runnable() { // Only available in Activity
public void run() {
// perform action in ui thread
}
});
Pattern 4, use the built-in handler of a View:
// Some other thread
myView.post(new Runnable() {
public void run() {
// perform action in ui thread, presumably involving this view
}
});
I've answered a similar question on how to report back to activity an error in the service.
Check Best practice for error handling in an Android Service, that will give you the aproach as well as a code example that you can use.
Regards.
OK, maybe we should get back to the base issue. Are you trying to make UI updates in your activity from the service? I see two approaches to this.
First, your service could send special Intents back up to the activity. Declare the activity with a launch mode of "singleTask" and implement onNewIntent() to receive intents from the service. Then, pack any relevant information into the intent and send it to the activity to be handled.
The better way, but somewhat more complicated, would be to bind the service from the activity, and then they can easily communicate with each other over the binder. If the service and activity are both part of the same application, and both running in the same process, this becomes much simpler.
Again, from my notes:
Declare an inner class named e.g. "LocalBinder" which extends Binder and contains a method named e.g. getService() which returns the instance of the service:
public class MyService extends Service
{
public class LocalBinder extends Binder {
MyService getService() {
return MyService.this;
}
}
private final IBinder binder = new LocalBinder();
public IBinder onBind(Intent intent) {
return binder;
}
}
Your activity contains code that looks like:
// Subclass of ServiceConnection used to manage connect/disconnect
class MyConnection extends ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder svc) {
myService = ((MyService.LocalBinder)svc).getService();
// we are now connected
}
public void onServiceDisconnected(ComponentName name) {
// we are now disconnected
myService = null;
}
}
private MyService myService;
private MyConnection connection = new MyConnection();
/**
* Bind to the service
*/
void doBind() {
bindService(new Intent(MyClient.this, MyService.class),
connection, Context.BIND_AUTO_CREATE);
}
/**
* Unbind from the service
*/
void doUnbind() {
if (connection != null) {
unbindService(connection);
}
}
My app does the following:
Activity1 starts Activity2.
Acitivity2 starts a Service.
The Service uses a AsyncTask to download a file.
In the AsyncTask I have a piece of code like this:
while ((status == 0)) {
byte buffer[];
if (size - downloaded > MAX_BUFFER_SIZE) {
buffer = new byte[MAX_BUFFER_SIZE];
} else {
buffer = new byte[size - downloaded];
}
int read = stream.read(buffer);
if (read == -1)
break;
file.write(buffer, 0, read);
downloaded += read;
}
Everything works like expected. With the status variable I can start and stop my download depending on its value.
BUT, when I close Activity2 and start it again (the service keeps running), I cannot stop the download, which means the variable status is not read correctly. I checked the variable, the value is OK but the Asynctask does not recognize it.
How can I get back control over my AsyncTask?
I made some more tests but this time with a thread, to make sure its not a failure in how I handle the AsyncTask. I did it this way:
Activity2 starts the Service (I did not change any code here).
The Service creates an Download Object what downloads the file using a Thread.
The structure looks like this:
in the Service
private Download dl = new Download();
private final DMInterface.Stub mBinder = new DMInterface.Stub() {
public void downloadFile() throws DeadObjectException {
try {
dl.start(url) // This starts a thread and the download
} catch (IndexOutOfBoundsException e) {
Log.e(getString(R.string.app_name), e.getMessage());
}
}
public void stop() throws DeadObjectException {
dl.cancel(); //This stops the download
}
};
And again, everything works until I disconnect from the service. Why am I only able to control the thread when I don't disconnect from the service?
Here is the code where I start/bind the service to Activity2 (only they important parts):
public class Activity2 extends ListActivity {
private DMInterface dmInterface;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.bindService(new Intent(Activity2.this, DMService.class), mConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
dmInterface = DMInterface.Stub.asInterface(service);
//do some stuff
}
public void onServiceDisconnected(ComponentName className) {
dmInterface = null;
}
};
}
There are two scenarios. In the first one I get an error in the second one not (but nothing else happens).
When an error is raised depends on, where i initialize the Thread e.g. the Object that starts the Thread.
Scenario 1:
When I do it like described above, I get no error but nothing happens.
Scenario 2:
In the Service:
private Download dl;
private final DMInterface.Stub mBinder = new DMInterface.Stub() {
public void downloadFile() throws DeadObjectException {
try {
dl = new Download();
dl.start(url) // This starts a thread and the download
} catch (IndexOutOfBoundsException e) {
Log.e(getString(R.string.app_name), e.getMessage());
}
}
public void stop() throws DeadObjectException {
dl.cancel(); //This stops the download
}
};
When I try to reach other parts of the service (setting a variable or something like that) everything works OK.
A Service lives in its own world, being a Service it is remote from the rest of your program.
A Binder can be used to communicate with your service. Defining a aidl interface with a setStatus method allows you to communicate the status to the service.