I'm writing an app that plays an audio file and I want it to continue doing so while minimized. I've done this, but I want the audio playback to be on a separate thread, because according to the Android developer website, CPU-heavy services work better on a separate thread.
First I tried using IntentService (this was the perfect solution). However, for some stupid reason, the service destroys itself once the code executed - which is immediately after it starts playing the file. I couldn't prevent this.
Then I created a Thread that runs the Service. However, I don't know how to make the Thread stop the service when needed - the best thing I could do is this:
serviceThread = new Thread() {
public void run() {
while (true) {
if (playAudio) {
startService(new Intent(getApplicationContext(),
MusicService.class));
playAudio = false;
}
if (stopAudio) {
stopService(new Intent(getApplicationContext(),
MusicService.class));
stopAudio = false;
}
}
}
};
Evidently, after that I set startService to true to start the service and I set stopService to true to stop it. However, I believe the Thread has to keep doing the check all the time, thus doing a lot of useless work all the time. I'm not even sure why is the app working, isn't it doing like million checks every second?
How can I properly do this?
Assuming you can't block the thread that the service runs in, you can add a java.lang.Thread.Sleep at the end of the loop if you're worried about doing too much work.
You might also want to read up thread scheduling and time slicing to understand why your app is still working (but probably using a lot more CPU than necessary).
Related
In one of my android applications, I need to run a task for every minute. It should run even if the app closes and when device is in Idle state also.
I have tried handler, it is working fine when device is active, but not working when device is in idle state.
I have tried workmanager(one time and repeated ) as well. Document says this works even when the device is in Idle mode, but this is stops working after 3/4 repeats.Workmanager is inconsitent, its working sometimes and not working most of the cases till i reboot device.
Can anyone suggest better way to handle the situation?
Thanks
bhuvana
Work manager can only work within 15 minutes of interval, if you do not define a longer time. To run something every minute, you need a Foreground Service with a sticky notification in it. There is no other way to run something every minute.
To start a foreground service, create a service as usual, and in its onStartCommand, call startForeground and from the method, return START_STICKY. These should achieve what you need.
Edit: Sample code for handler thread (this is Java btw, should be similar on Xamarin):
private HandlerThread handlerThread;
private Handler backgroundHandler;
#Override
public int onStartCommand (params){
// Start the foreground service immediately.
startForeground((int) System.currentTimeMillis(), getNotification());
handlerThread = new HandlerThread("MyLocationThread");
handlerThread.setDaemon(true);
handlerThread.start();
handler = new Handler(handlerThread.getLooper())
// Every other call is up to you. You can update the location,
// do whatever you want after this part.
// Sample code (which should call handler.postDelayed()
// in the function as well to create the repetitive task.)
handler.postDelayed(() => myFuncToUpdateLocation(), 60000);
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
handlerThread.quit();
}
First of all, this is a long question but targeting only one topic, and I'm sorry I cannot share any code as it is a project of our company and is classified. We are using a foreground service that executes a task within 100 milliseconds only. Until I asked this question we used multiple approaches on executing a code in a short amount of time, follows:
A thread with "Thread.sleep" in it (not the best approach but it was our first try, went inconsistent),
A thread with "Object.wait" (same result as above),
Timer (also is inconsistent and stops executing after some time),
ScheduledThreadPoolExecutor which also has the problem of all above.
On my phone (HUAWEI P20 Lite), after trying these approaches, we finally decided to use a Handler with a HandlerThread as the work does not require to interact with UI thread. (not always but we are using a seperate handler for it.) In between every approach, this seems to be the most consistent one, however we don't know if it depends on the phone, manufacturer or anything, my phone stops executing the looping handler with postDelayed until I interact with the app UI in some way, for example: touching the notification, while the screen is on. Yes, I'm not talking about clicking the notification itself, but even touching, or like expanding the detail text starts the handler again. Which means, I think my phone is trying to save power with pausing the background execution.
Now, we only want to run this method while the screen is on, as we are already pausing the handler itself via "a screen on-off broadcast receiver" that registered in the foreground service by removing the callbacks of the runnable, but after screen goes on, broadcast is received but even if "Handler.post()" executes, it does not run the runnable inside, hence it never loops.
To give you a bit of context, this is what kind of logic we are following:
We open a foreground service with something similar to the below code:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
getApplicationContext().startForegroundService(
new Intent(getApplicationContext(),
MyService.class));
}
else
{
getApplicationContext().startService(
new Intent(getApplicationContext(), MyService.class));
}
And then, inside the service we do something like the following:
public int onStartCommand()
{
// notification stuff
HandlerThread thread = new HandlerThread("myThread");
thread.start();
Handler handler = new Handler(thread.getLooper());
handler.post(new Runnable()
{
// do stuff
// this runnable stops executing after some time. This is where the problem lies.
handler.postDelayed(this, 100);
})
return START_STICKY;
}
The handler inside onStartCommand is unstable, sometimes it does not respect the delay, or sometimes it does not execute at all. Now, not respecting the delay is not the problem as we don't have to be that consistent as our calculations do not depend on the elapsed time or something, but if it stops executing (which in this case, it does), that's where the problem starts.
To handle this, we are going to use a brand new class that Android Jetpack offers, WorkManager, which is perfect for checking the status of handlers. To understand if the threads are active or not, we registered the last execution time of the code inside the handler and are going to access it via the worker class. Now, if the handlers are down, we would like to wake these handlers up. How can this be done? Because remember, this happens while the screen is on.
Edit: Here are some logs about how the thread execution goes.
//2018-12-19 11:35:15.048 17222-17952/com.example.something D/MyService: threadName: MainThread, postDelayedValue: true
//2018-12-19 11:35:15.154 17222-17952/com.example.something D/MyService: threadName: MainThread, postDelayedValue: true
2018-12-19 11:35:15.262 17222-17952/com.example.something D/MyService: threadName: MainThread, postDelayedValue: true
2018-12-19 11:35:45.262 17222-17952/com.example.something D/MyService: threadName: MainThread, postDelayedValue: true
//2018-12-19 11:35:45.365 17222-17952/com.example.something D/MyService: threadName: MainThread, postDelayedValue: true
//2018-12-19 11:35:45.468 17222-17952/com.example.something D/MyService: threadName: MainThread, postDelayedValue: true
Any suggestions? Any workarounds for this? Every help and comment is appreciated, thank you so much.
Found where the problem was, it is running smoothly everywhere else except on my device (and probably other devices which has EMUI on their system), which is apparently pretty strict about power management.
Here are the logs:
2018-12-19 16:45:52.943 12619-12822/com.example.something D/ThreadHelper: MainThread looping.
2018-12-19 16:45:52.986 1699-2028/? I/ash: com.example.something skips not important notification
2018-12-19 16:45:52.996 1699-2028/? I/ash: com.example.something { doze duration=40061 UptimeDuration=40061 } transition to: hibernation reason:
2018-12-19 16:45:52.996 1699-2028/? I/ash: perform hibernation actions: com.example.something
My device, by on it's own, decides that the process with a notification that is not important can be silently stopped, so I increased the importance of the notification with NotificationManager.IMPORTANCE_MAX. Now, it does not stop working at all.
If you experience something like this and want to assure that raising the notification won't be enough for you, then on HUAWEI devices there is a setting that can be opened with Settings --> Battery --> Launch. Disable your automatic app power management and make it manual, so the OS won't interfere with the process ever (hopefully).
Does it happen on pre-Oreo devices?
If you start a service with startForegroundService, you should immediately call setForeground on onStartCommand. Otherwise service is killed within a short amount of time.
Also, your handler reference is local to onStartCommand, that may be why it does not persist.
I am currently having a really weird issue, and I don't know what could be causing it. Every time I close the app by swiping it away, and then start it again, the app seems to be duplicating its output to logcat, or in other words, it appears to be running multiple times.
This doesn't happen when I am connected to Android Studio, but without a cable connection, it does it without fail.
In my main activity I start my service like this:
Intent intent = new Intent(this, BluetoothService.class);
startService(intent);
bindService(intent, bluetoothServiceConnection, Context.BIND_AUTO_CREATE);
Stopping the service:
unbindService(bluetoothServiceConnection);
Intent intent = new Intent(MainActivity.this, BluetoothService.class);
stopService(intent);
EDIT I have made some changes in the way I stop and start my service, which seems to have solved my problem on Android 5.1.1, but not on Android 4.4.4, sadly.
I was thinking that maybe my logging process could be the problem, but destroying that process in my activity's onDestroy()-method didn't solve the issue either.
Starting the logging process:
logger = Runtime.getRuntime().exec(new String[]{"logcat", "-v", "time", "-f", logcatFile.getAbsolutePath(), " *:E"});
Stopping it:
logger.destroy();
If you start a service, depends how you define your service in the AndroidManifest.xml, if you have android:process=":yourProcess", if the process name start with ":" it will create a new process, that probably your log output multiple times.
Your code doesn't contains any logging information, nor did you provide a stacktrace with it. Your splash screen doesn't seem to be the issue, but you should replace the new thread with a delayed post to a handler to the looper thread (main thread) you're running in:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
Intent i = new Intent(getApplicationContext(), MainActivity.class);
startActivity(i);
finish();
}
}, 1500);
Instead of getApplicationContext() you could also use SplashActivity.this directly.
In your MainActivity you then need to make sure to unbind the service. Maybe also stop it, depending on what you need it for. Refer to Bound Services for more documentation and examples.
For making the Service terminate the documentation states
A service can be both started and have connections bound to it. In such a case, the system will keep the service running as long as either it is started or there are one or more connections to it with the Context.BIND_AUTO_CREATE flag. Once neither of these situations hold, the service's onDestroy() method is called and the service is effectively terminated.
That's why it works when the service isn't started at all. You try to stop the service in onDestroy() which is only called if the service is stopped. Your code looks like the service doesn't need to be started at all. But to do it correctly, just stop the service in the acivity's onDestroy() or utilize
onUnbind() or any other useful lifecycle event of the activity or service.
I have created an app. It starts a service(onStartCommand type).
It creates a thread that keeps on monitor the clipboard:
private class MonitorTask extends Thread {
#Override
public void run() {
mKeepRunning = true;
while (true) {
doTask(); //-this function uses the toast to show the string in clipboard
try {
Thread.sleep(300);
} catch (InterruptedException ignored) {
}
if (!mKeepRunning) {
break;
}
}
}
I found that the after some time passed, the service was still there (according to running service manager), but the thread disappeared. How can I keep the thread running forever until user closes the app.
I guess that may be InterruptedException, how can I use that catch to restart the Thread?
Some old threads mentioned using AlarmManager to "startup the service and do something, close the service" at regular interval, but i dont think it is a good idea?
Please let me know if there is a typical way to do so or any good idea? thanks in advance.
Update
in fact, i know there is ClipboardManager, but i know this one is not compatible to android 2.3.4. Besides, I would like to know if I want to create a thread in service, how can i reset it if it was killed? thanks
The best way to accomplish what you are trying to do is by using the ClipboardManager class, always try to avoid doing you own "Monitoring" functionality specially when it already exist in the OS and make sure if the OS itself already provides a BroadcastReceiver triggering an action with whatever you are expecting, take a look at the documentation for this class:
http://developer.android.com/reference/android/content/ClipboardManager.html
Instead of having a thread running doing nothing, draining battery and performance, why dont you create a BroadcastReceiver waitting for Clipboard actions, make use of the method:
ClipboardManager.addPrimaryClipChangedListener(ClipboardManager.OnPrimaryClipChangedListener what)
to be notified of any changes...
Regards!
inside this : http://developer.android.com/guide/components/services.html , Extending the Service class will help you !
I want to start a Service from an Activity.
First I tried it with a LocalBinder. This works, but the service was bound to the activity. I don't want to stop the service when the activity is gone. I found no solution with the LocalBinder so I removed it and tried this:
use a singleton instance in the service
call the startService methode in a new thread and waits until the instance is available:
final Intent recordService = new Intent(RecordActivity.this, RecordService.class);
Runnable r = new Runnable() {
#Override
public void run() {
startService(recordService);
}
};
new Thread(r).start();
Log.i(MMLF.T, "service instance: "+serviceInstance);
final ProgressDialog mProgressDialog = ProgressDialog.show(
RecordActivity.this, "Waiting", "wait until record service is loaded",
true);
while (serviceInstance == null) {
serviceInstance = RecordService.get();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.e(MMLF.T, "could not sleep", e);
}
}
mProgressDialog.dismiss();
But this doesn't work, too. It stucked in the waiting loop. If I remove this waiting stuff and the new new Thread(r).start() line is the last, the activity and service start fine.
How to start a service independent from an activity? I also let them to communicate with each other. The activity should call two methods (start and stop recording) and the service should send messages. For the second I can use LocalBroadcast.
Your question is a little confusing, because Services already live independently of Activities. Note, however, that Services run in the main thread by default. If you want to run the Service in a different thread (and in this case it looks like you do), you will have to set up a Messenger object and send messages between your worker thread and your UI thread. You can also look into using AIDL (on top of which Messenger is really built anyway). Your communication, if you don't use a Messenger, could use intents. If this is the case, you should look into IntentService. However, this only works when you are sending messages to the Service, not back and forth. If you want back and forth communication, you will have to use some sort of Messenger or similar pattern.
By the way, using an IntentService for things like 'stop' and 'start' is pretty common. Typically there is also a background thread, which communicates with the Service using a Messenger or something similar, and then sends / receives messages to instruct the worker thread as to what should be done.
You might also look into AsyncTask, as it makes this kind of thing much simpler.