I'm building a temporary demo app and I need a service that polls a server every minutes. (I know there are better mechanisms for this). Now I have a class I call APIHandler that both my MainActivity and service should use. I have run into issues providing the service with the instance of my APIHandler class. So what I basically want here is my service to be able to use my APIHandler instance. The APIHandler class cannot be made static as it needs a Volley.newRequestQueue object which needs a context instance.
This is how I start my service from my MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setup()
apiHandler = APIHandler(this) //I neeed this instance...
PollingService.enqueueWork(this, Intent(this, PollingService::class.java))
}
This is my service
class PollingService(private val apiHandler: APIHandler) : JobIntentService() {
private val timer = Timer()
private val tag = "PollingService"
//To be present here!
companion object {
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(context, PollingService::class.java, 1, work)
}
}
override fun onHandleWork(intent: Intent) {
Log.d(tag, "Starting")
timer.scheduleAtFixedRate(timerTask {
run {
Log.d(tag, "Polling...")
apiHandler.getLEDState(1)
apiHandler.getLEDState(2)
}
}, 0, 5000)
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
this.timer.cancel()
super.onDestroy()
}
}
If all you need is the context, you can get it in the onHandleWork function, see below (sorry I'm using Java instead of Kotlin):
#Override
protected void onHandleWork(#NonNull Intent intent) {
Context context = getApplicationContext();
// Instantiate your APIHandler with the context here
}
Try copy-pasting this into your project to have AS automatically convert it to Kotlin for you.
Related
I have a started service app. It intent to activity from another app, but still running in foreground. After a button click in that activity, I want to send data (for example a string "potato") to service without startService() in order to continue, not restart. That's how service keeps running till get the data, while(requiredData != "potato"){}.start. How can I send it, or return response ? I think to use Messenger or Broadcast, but I'm not sure it fits well and how to do.
Note: Service App connected to an activity from another app.
Service App
class RegistryService : Service() {
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val i = packageManager.getLaunchIntentForPackage("com.myexample.potatoactivity")
if (i!=null) {
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(i)
} else {
Toast.makeText(this,"Fail",Toast.LENGTH_SHORT).show()
}
while (true) { // requiredData != "potato"
//Log.d("MyService", "Wait for potato")
}
return START_STICKY
}
}
Potato Activity
class PotatoActivity : AppCompatActivity() {
private lateinit var binding: ActivityPotatoBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPotatoBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.buttonSendData.setOnClickListener {
//it.putExtra("REQUIRED_DATA", "potato")
}
}
}
I am developing a application to initiate the Bluetooth device application.
I have a service called MyService.
I have stub MyServiceImpl to handle the AIDL interface which will be called from outside.
I have broadcastreceiver to get the "ACTION_BOND_STATE_CHANGED".
My requirement is whenever device is connected(i.e device?.bondState == BluetoothDevice.BOND_BONDED, I need to send deviceinfo from Broadcast receiver to MyServiceImpl stub.
The Reason - AIDL interface is already handled there, once the aidl is called, new class will be created from there. For that new class MyServiceImpl is needed. In addition, response to that interface call is sent through callbacks is also handled there.
The code is below. Can someone help me to send the device information from Broadcastreceiver to the Service stub Implementation?
class MyService : Service() {
override fun onBind(intent: Intent): IBinder {
return binder
}
override fun onUnbind(intent: Intent?): Boolean {
return super.onUnbind(intent)
}
override fun onCreate() {
binder = MyServiceImpl()
filterNewDevices()
}
private fun filterNewDevices() {
val filter = IntentFilter()
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
registerReceiver(mBroadcastReceiverBond, filter)
}
private val mBroadcastReceiverBond: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
if (device?.bondState == BluetoothDevice.BOND_BONDED) {
//HERE I NEED TO SEND THE device TO
}
if (device?.bondState == BluetoothDevice.BOND_BONDING) {
}
if (device?.bondState == BluetoothDevice.BOND_NONE) {
}
}
}
}
class MyServiceImpl : IBluetoothConnection.Stub(), IBinder {
private lateinit var btConnection: BTConnection
override fun registerUuid(name : String?, uuid : String?, connectionId : Int, btDevice : BluetoothDevice?) {
btConnection = BTConnection(this)
btConnection.initialize(name!!, UUID.fromString(uuid), connectionId, btDevice)
}
}
}
Create a method in MyServiceImpl that takes the device as a parameter. In your BroadcastReceiver call the method on binder to pass the device to the service implementation.
I am trying to make a service performs and action at intervals, with the help of this article I was able to set up a service and set and interval of 1000miliseconds to log to my console, but I noticed that the service only runs once. Here is a snippet of my code:
class MessageService : Service() {
private var serviceLooper: Looper? = null
private var serviceHandler: ServiceHandler? = null
override fun onCreate() {
val context:Context = this
HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
serviceLooper = looper
serviceHandler = ServiceHandler(context, looper)
}
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
serviceHandler?.obtainMessage()?.also { msg ->
msg.arg1 = startId
serviceHandler?.sendMessage(msg)
}
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? { return null}
override fun onDestroy() {}
private inner class ServiceHandler(context: Context, looper: Looper) : Handler(looper) {
val baseContext = context
override fun handleMessage(msg: Message) {
val runnable = Runnable {
Log.i("thread", "service has been called")
}
this.postDelayed(runnable, 1000)
}
}
}
please what am I doing wrong?
The main problem is that you're creating a NEW Handler using val handler = Handler() after the first correct "handleMessage()" call, so each postDelayed() will post the runnable in an "unmanaged" Handler (I call "unmanaged" because that just-created-one hasn't overridden the "handleMessage(..)" method).
You need to postDelayed() to serviceHandler and not to a new Handler. To fix you can use this.postDelayed(...) because you're inside ServiceHandler and you want to post the Runnable in the same Handler.
I have a service, where I manage music playing. Also I have activity sending intents with user's music. When I open activity, I want to get current status of playing.
I have specific player, what have only two events: playing started and playing ends. So if I use broadcast, I will get only next event.
I save events in variable lastAction when getting it. I can create new command ACTION_SEND_CURRENT_STATE. but it looks not good.
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
null -> {
player?.cancel()
}
ACTION_PLAY -> {
player?.cancel()
player = createPlayer(intent)
player?.start()
}
ACTION_STOP -> {
player?.cancel()
}
}
return START_STICKY
}
override fun onPlayingBegin(p0: player?) {
lastAction = BRODCAST_PLAYING_BEGIN
sendBroadcast(Intent(BRODCAST_PLAYING_BEGIN)
.putExtra(EXTRA_SONG, currentSong)
)
}
How to get current state from service correctly? as state I mean last action.
Use this method
public static boolean isServiceRunning(Context context, Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
Hope help you.
As pskink mentioned you need to bind your service to your activity to be able to get information from your service. If your service is working in a remote process then you need to use AIDL to communicate with your service, but I believe that if you do so you're able to find how to do that by yourself.
In your particular case the service communication might look like this(please note that the code may be not completely correct, I wrote it right from my head):
class LocalBinder(val service: MusicService) : Binder
class MusicService : Service() {
var lastAction: String? = null
private set
private val binder: IBinder = LocalBinder(this)
override fun onBind(intent: Intent) = binder
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
lastAction = intent?.action
when (intent?.action) {
null -> {
player?.cancel()
}
ACTION_PLAY -> {
player?.cancel()
player = createPlayer(intent)
player?.start()
}
ACTION_STOP -> {
player?.cancel()
}
}
return START_STICKY
}
}
class MusicPlayerActivity : Activity() {
private var musicService: MusicService? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = (LocalBinder) service
musicService = binder.service
}
override fun onServiceDisconnected(className: ComponentName) {
musicService = null
}
}
override fun protected onStart() {
super.onStart()
val intent = Intent(this, MusicService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
override fun protected onStop() {
super.onStop()
if (musicService != null) {
unbind(connection)
musicService = null
}
}
fun onClick() {
if (musicService != null) {
musicService.lastAction // do something
}
}
}
No need to worry about the service just use foreground service as used by all music playing applications.
Here i am running a service for music play back.
This code snippet is in my onStart() method of my Activity
if(musicServiceStartIntent == null) {
musicServiceStartIntent = new Intent(this, MusicService.class);
startService(musicServiceStartIntent);
bindService(musicServiceStartIntent, musicConnection, Context.BIND_AUTO_CREATE);
}
First i'm starting my service then binding it. And i am calling unbindservice() in onDestroy() method. My Activity got destroyed and service stopped.
unbindService(musicConnection);
Manifest file declaration
<service android:name=".Services.MusicService"/>
How can i keep my service running in background even after activity destroyed. I refer few threads of StackOverflow but they are not helpful.
You just need to start the service, don't bind it to activity lifecycle
Intent intent = new Intent(context, SomeService.class);
startService(intent);
And your service can use START_STICKY / START_REDELIVER_INTENT to make sure that your service will be re-created when the android system kill your service
#Override
public int onStartCommand(final Intent intent, int flags, int startId) {
//other code
return START_STICKY;
}
If needed you can use Service.startForeground(notificationId, notification) to make sure that your service will not be killed by the system
Use your service in startForeground, using Notification you can keep your service alive..
Refer to https://developer.android.com/guide/components/services.html#Foreground.
A music player that plays music from a service should be set to run in the foreground, because the user is explicitly aware of its operation. The notification in the status bar might indicate the current song and allow the user to launch an activity to interact with the music player.
To request that your service run in the foreground, call startForeground().
return service.START_STICKY or service.START_REDELIVER_INTENT in onStartCommand
There is three important tricks:
Call startForegroundService which creates a long running service not limited to the binded context and make a promise to call startForeground later.
Return START_STICKY in onStartComand
Call startForeground with a notification as promised in (1).
For example, if you want to run a TimerService, in your TimerActivity you will do:
private var timerService: TimerService? = null
private val timerServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as TimerService.Binder
timerService = binder.getService()
}
override fun onServiceDisconnected(arg0: ComponentName) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
...
startButton.setOnClickListener {
timerService?.startTimer(60L, 0L)
}
}
override fun onStart() {
super.onStart()
Intent(this, TimerService::class.java).also {
ContextCompat.startForegroundService(this, it) // that's the first trick
bindService(it, timerServiceConnection, Context.BIND_AUTO_CREATE)
}
}
Your TimerService will be something like that:
class TimerService : Service() {
private val binder = Binder()
private var serviceLooper: Looper? = null
private var serviceHandler: ServiceHandler? = null
private var timer: CountDownTimer? = null
private val notificationUtil by lazy {
NotificationUtil(this)
}
override fun onCreate() {
HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
serviceLooper = looper
serviceHandler = ServiceHandler(looper)
}
}
override fun onBind(intent: Intent?): IBinder? = binder
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val timerRemaining = intent?.getLongExtra(EXTRA_REMAINING, 0) ?: 0L
if (timerRemaining != 0L) {
serviceHandler?.obtainMessage()?.also { msg ->
msg.arg1 = startId
msg.data.putLong(EXTRA_REMAINING, timerRemaining)
serviceHandler?.sendMessage(msg)
}
}
return START_STICKY // that's the second trick
}
override fun onDestroy() {
super.onDestroy()
timer?.cancel()
}
fun startTimer(secondsRemaining: Long, id: Long) {
Intent(this, TimerService::class.java).apply {
putExtra(EXTRA_REMAINING, secondsRemaining)
}.also {
onStartCommand(it, 0, id.toInt())
}
}
fun stopTimer() {
timer?.cancel()
}
fun updateNotification(secondsRemaining: Long){
val notification = NotificationCompat.Builder(this, NotificationUtil.CHANNEL_ID_TIMER)
.setSmallIcon(R.drawable.ic_timer)
.setAutoCancel(true)
.setDefaults(0)
.setContentTitle(secondsRemaining.formatSeconds())
.setContentText("Timer")
.setContentIntent(notificationUtil.getPendingIntentWithStack(this, TimerActivity::class.java))
.setOngoing(true)
.build()
startForeground(NotificationUtil.NOTIFICATION_ID, notification) // that's the last trick
}
private fun sendMessage(remaining: Long) {
Intent(TimerService::class.java.simpleName).apply {
putExtra(EXTRA_REMAINING, remaining)
}.also {
LocalBroadcastManager.getInstance(this).sendBroadcast(it)
}
}
private inner class ServiceHandler(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
val secondsRemaining = msg.data.getLong(EXTRA_REMAINING)
notificationUtil.showTimerStarted(secondsRemaining)
timer = object : CountDownTimer(secondsRemaining * 1000, 1000) {
override fun onTick(millisUntilFinished: Long) {
Log.i(this::class.java.simpleName, "tick ${(millisUntilFinished / 1000L).formatSeconds()}")
updateNotification(millisUntilFinished / 1000)
sendMessage(millisUntilFinished / 1000)
}
override fun onFinish() {
Log.i(this::class.java.simpleName, "finish")
notificationUtil.showTimerEnded()
sendMessage(0)
stopSelf()
}
}.start()
}
}
inner class Binder : android.os.Binder() {
// Return this instance of LocalService so clients can call public methods
fun getService(): TimerService = this#TimerService
}
companion object {
const val EXTRA_REMAINING = "EXTRA_REMAINING"
const val NOTIFICATION_ID = 1 // cannot be 0
fun Long.formatSeconds(): String {
val s = this % 60
val m = this / 60 % 60
val h = this / (60 * 60) % 24
return if (h > 0) String.format("%d:%02d:%02d", h, m, s)
else String.format("%02d:%02d", m, s)
}
}
}