I am developing a game that has a certain number of moves that replenish every 10 seconds, only 250 moves. You may have seen this in many modern mobile games as energy units.
The method that counts down 10 seconds and adds moves is implemented in the "Service". But the fact is that after the death of the application, the service does not work for a long time. Or it is restarted by first calling "OnStartCommand", then immediately "onDestroy" and somehow works. In the event of such a restart, if you open an application where the "Service" is launched in "OnCreate", another "Service" is launched and works in parallel with another (this breaks the logic of the recovery of moves, because you can create a lot of "Service" in this way) ... And even in this case, the "Service" do not live long and die.
class MyService : Service() {
lateinit var pref: SharedPreferences
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i("MYTAG", "Сервис работает")
return START_STICKY
}
fun check() {
if (pref.getInt("servicestep", 250) < 250) {
reload()
} else stopSelf()
}
private fun reload() {
var progress = 0
val b = "com.example.driverclicker"
val intent = Intent(b)
object : CountDownTimer(10100, 1000) {
override fun onTick(millisUntilFinished: Long) {
Log.i("MYTAG", Thread.currentThread().name)
Log.i("MYTAG", "Таймер $millisUntilFinished")
progress += 1
intent.putExtra("step", progress)
sendBroadcast(intent)
if (progress == 10) {
var step = pref.getInt("servicestep", 249)
step += 1
pref.edit().putInt("servicestep", step).commit()
Log.i("MYTAG", "Таймер сохранился $step")
Log.i("MYTAG", "Progress if= $progress")
}
Log.i("MYTAG", "Progress после if= $progress")
}
override fun onFinish() {
intent.putExtra("step", 0)
sendBroadcast(intent)
check()
Log.i("MYTAG", "Таймер закончился")
}
}.start()
}
override fun onCreate() {
super.onCreate()
pref = getSharedPreferences("save", Context.MODE_PRIVATE)
check()
Log.i("MYTAG", "Сервис запущен")
}
override fun onDestroy() {
super.onDestroy()
Log.i("MYTAG", "Сервис отключен")
}
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
}
BroadcastReceiver in Activity
br = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val serviceSteps = intent?.getIntExtra("step", 0)
if (serviceSteps != null) {
showProgress(serviceSteps, R.id.moves_bar)
if (serviceSteps == 10) {
val steps = presenter.loadMoveValue()
Log.i(TAGNAME, "Ресивер получил $steps")
presenter.showText("$steps", R.id.text_movesValue)
Log.i(TAGNAME, "Ресивер отобразил $steps")
}
}
}
}
registerReceiver(br, IntentFilter(BROAD))
I have a question. How do, for example, farm games work on Android? Resources are generated there in the background, and you just come and collect. How to implement this in the background in Android?
There are different methods to handle this, the easiest is to implement an offline calculation. Get a timestamp, when the service stopped working and get a timestamp when it is recreated and working again, with the time between these two timestamps you can calculate how often your service would have triggered.
This is as you already noticed not very safe and can easily be manipulated.
The 2nd approach is have a server handle the current moves and sync with the device. This needs a server, serverlogic and a database, but this can't be manipulated easy.
For multiplayer a serversynced solution is the better choice, if your game is single player only you can do it locally with services and offline calculation.
Which method you chose is your free choice.
Related
I am making a stopwatch app and would like my stopwatch to continue after 1 minute of the app being closed or the phone being turned off. I was wondering if there was any way for me to make my timer service continue working after the the app is paused. In the background service I am currently using the timer function stop exactly 1 minute after the app is closed. I have been searching the internet for solutions which have pointed me towards foreground services but I am very inexperienced and cannot figure out how to make them work.
Here is my timer service (I got this from Code With Cal on youtube)
class TimerService : Service() {
override fun onBind(p0: Intent?): IBinder? = null
private val timer = Timer()
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val time = intent.getDoubleExtra(TIME_EXTRA, 0.0)
timer.scheduleAtFixedRate(TimeTask(time), 0, 1000)
return START_NOT_STICKY
}
override fun onDestroy() {
timer.cancel()
super.onDestroy()
}
private inner class TimeTask(private var time: Double) : TimerTask() {
override fun run() {
val intent = Intent(TIMER_UPDATED)
time++
intent.putExtra(TIME_EXTRA, time)
sendBroadcast(intent)
}
}
companion object {
const val TIMER_UPDATED = "timerUpdated"
const val TIME_EXTRA = "timeExtra"
}
Can I make this into a service that continues on when the app is closed? Thank you so much (Note: This is my first app and I am very inexperienced. If there is any more code you need, please tell me, thanks!)
As Mike M. Answered in the comments:
"Nope, you can't do that, but you don't really need/want to, anyway. All you really need to know whenever your app is setting up the stopwatch is the starting time of the current timing session, and the time it is right now. If you save that starting time somewhere persistent – e.g., in SharedPreferences – then you just need to retrieve that value and do some simple arithmetic to get the stopwatch re-set correctly."
-Thanks so much (-Casper)
I want to send an text to firebase database when the user has finished typing (I use AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED). I don't want it to send text every time the user types a letter because that would result in A LOT of text data, however I don't want them to have to hit the enter button either.
Is there a way so I can detect when the user has finished typing and then send the text data?
Using Kotlin here! Thanks
I don't want to use button
#Inject lateinit var interactor: InteractorAccessibilityData
override fun onAccessibilityEvent(event: AccessibilityEvent) {
when (event.eventType) {
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> {
val data = event.text.toString()
if (data != "[]") {
interactor.setDataKey("${getDateTime()} |(TEXT)| $data")
Log.i(TAG, "${getDateTime()} |(TEXT)| $data")
}
}
}
}
In InteractorAccessibilityData.kt->
override fun setDataKey(data: String) {
if (firebase.getUser()!=null) firebase.getDatabaseReference(KEYS).child(DATA).push().child(TEXT).setValue(data)
}
Using a Timer would be the best way.
Another way would be checking the length of the text.
say after an initial length of 10 sends data to firebase and after that every +2 or +3 length.
Introduce a variable outside this function which counts the timer.
// declare this outside onAccessibilityEvent function
private val timer = object : CountDownTimer(50000, 1000) {
override fun onTick(millisUntilFinished: Long) {
//use it if you want to show the timer
}
override fun onFinish() {
//send data to firebase, like
//if (firebase.getUser()!=null) firebase.getDatabaseReference(KEYS).child(DATA).push().child(TEXT).setValue(data)
}
}
//function to start or cancel the timer
private fun x(p: Int = 0) {
when (p) {
0 -> timer.start()
1 -> timer.cancel()
}
}
This method doesn't look efficient but this is how I would do it if I have no other options.
The Best you can do is send text after every word (by using spaces)
Like This
#Inject lateinit var interactor: InteractorAccessibilityData
override fun onAccessibilityEvent(event: AccessibilityEvent) {
when (event.eventType) {
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> {
val data = event.text.toString()
if (data.contain( )) {
interactor.setDataKey("${getDateTime()} |(TEXT)| $data")
Log.i(TAG, "${getDateTime()} |(TEXT)| $data")
}
}
}
}
I want to make a feature to notify user when there's data changes in my firebase database. The problem is when the app is destroyed, then the service automatically destroyed. After that when the data in database change, my app won't notify the user.
Here's some snippet code for my apps.
Service :
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "DogNotificationService created")
val phone = intent?.extras?.getString("PHONE")
if(isFirstTime) {
val firebaseDatabase = FirebaseDatabase.getInstance()
notificationReference = firebaseDatabase.getReference("walker/$phone/notification")
val coroutineScope = CoroutineScope(Job() + Dispatchers.IO)
coroutineScope.launch {
launchListener()
}
isFirstTime = false
}
return super.onStartCommand(intent, flags, startId)
}
fun launchListener() {
valueEventListener =
notificationReference.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
Log.w(TAG, "Read failed: " + p0.message)
}
override fun onDataChange(p0: DataSnapshot) {
val notificationData = p0.getValue(String::class.java)
if(count > 0) {
Log.d(TAG, notificationData)
sendNotification(notificationData!!)
}
count++
}
})
}
When I start the service :
private fun startNotificationService() {
val intent = Intent(context, DogNotificationService::class.java)
intent.putExtra("PHONE", "081293312313")
Log.d(TAG, "Start notification service")
activity?.startService(intent)
}
If any idea to do this approach, please help.
I was working in an app to keep track of the users to allow them record their tracks and found out that services can be killed at any moment if the android system requires free memory. Even if your service has wakelocks or is running in foreground.
The solution I found was to use alarms with a foreground service, if you schedule alarms this alarms will be fired whether your app is still executing or not. That way my app could get the device position even though the system had killed the app due to lack of resources. It's the only solution I found that works in this scenario. An alarm that wakes up the service.
The idea came to me in some google i/o when they said that if you really need your app to continue no matter what you should use alarms instead of services.
Besides that, if you need the app to be awaked constantly then use exact alarms as the inexact ones in some devices they might be fired 5 minutes later if the time that they should be fired is too near to the current time.
In Android Oreo, I want to create a service that periodically updates data from the network.
class NetworkJobService : JobService() {
override fun onStopJob(p0: JobParameters?): Boolean {
jobFinished(p0,true)
return true
}
override fun onStartJob(p0: JobParameters?): Boolean {
//Average working time 3 to 5 minutes
NetworkConnect.connect()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
jobFinished(p0,true)
}
.subscribe({result->
// Writes the parameters to the cache with the current time.
Cache.write("result : $result")
},{e->
// Writes the parameters to the cache with the current time.
Cache.write(e)
})
return true
}
}
This service is registered in the schedule when you run MainActivity.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
jobSchedule()
button.setOnClickListener { readLog() }
}
val interval = 1000 * 60 * 15L
private fun jobSchedule(){
val jobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val jobInfo = JobInfo.Builder(3,
ComponentName(this, NetworkJobService::class.java))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPeriodic(interval)
.build()
jobScheduler.schedule(jobInfo)
}
private fun readLog(){
//The log file is read line by line.
Cache.read()
.reversed()
.toObservable()
.subscribe({ text->
Log.i("Service Log",text)
},{
})
}
}
However, when I read the log file and checked the results, the service was running only when the MainActivity was running. In other words, it was not being rescheduled.
1) Run the activity and just turn off the device's screen
2) Run the activity and press the Home button to return to the launcher.
3) When the service is terminated and the app is deleted in the multitasking window
The most I wanted was to work in case 3), but in any of the above, the services I wanted were not rescheduled.
What have I missed?
While working with background threads in Oreo when the app is in killed you need to start the services as foreground service. Here is the details of the same. Essentially, show a notification to make the user aware that your app is trying to doing something in the background.
Hope it helps you.
TL;DR: I have successfully created and coupled (via a subscription) an activity to a media browser service. This media browser service can continue running and play music in the background. I'd like to be able to refresh the content at some stage, either when the app comes to the foreground again or during a SwipeRefreshLayout event.
I have the following functionality I'd like to implement:
Start a MediaBrowserServiceCompat service.
From an activity, connect to and subscribe to the media browser service.
Allow the service to continue running and playing music while the app is closed.
At a later stage, or on a SwipeRefreshLayout event, reconnect and subscribe to the service to get fresh content.
The issue I am receiving is that within a MediaBrowserService (after a subscription has been created) you can only call sendResult() once from the onLoadChildren() method, so the next time you try to subscribe to the media browser service using the same root, you get the following exception when sendResult() is called for the second time:
E/UncaughtException: java.lang.IllegalStateException: sendResult() called when either sendResult() or sendError() had already been called for: MEDIA_ID_ROOT
at android.support.v4.media.MediaBrowserServiceCompat$Result.sendResult(MediaBrowserServiceCompat.java:602)
at com.roostermornings.android.service.MediaService.loadChildrenImpl(MediaService.kt:422)
at com.roostermornings.android.service.MediaService.access$loadChildrenImpl(MediaService.kt:50)
at com.roostermornings.android.service.MediaService$onLoadChildren$1$onSyncFinished$playerEventListener$1.onPlayerStateChanged(MediaService.kt:376)
at com.google.android.exoplayer2.ExoPlayerImpl.handleEvent(ExoPlayerImpl.java:422)
at com.google.android.exoplayer2.ExoPlayerImpl$1.handleMessage(ExoPlayerImpl.java:103)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:5665)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:822)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:712)
I call the following methods to connect to and disconnect from the media browser (again, everything runs smoothly on first connection, but on the second connection I'm not sure how to refresh the content via a subscription):
override fun onStart() {
super.onStart()
mMediaBrowser = MediaBrowserCompat(this, ComponentName(this, MediaService::class.java), connectionCallback, null)
if (!mMediaBrowser.isConnected)
mMediaBrowser.connect()
}
override fun onPause() {
super.onPause()
//Unsubscribe and unregister MediaControllerCompat callbacks
MediaControllerCompat.getMediaController(this#DiscoverFragmentActivity)?.unregisterCallback(mediaControllerCallback)
if (mMediaBrowser.isConnected) {
mMediaBrowser.unsubscribe(mMediaBrowser.root, subscriptionCallback)
mMediaBrowser.disconnect()
}
}
I unsubscribe and disconnect in onPause() instead of onDestroy() so that the subscription is recreated even if the activity is kept on the back-stack.
Actual method used for swipe refresh, in activity and service respectively:
Activity
if (mMediaBrowser.isConnected)
mMediaController?.sendCommand(MediaService.Companion.CustomCommand.REFRESH.toString(), null, null)
Service
inner class MediaPlaybackPreparer : MediaSessionConnector.PlaybackPreparer {
...
override fun onCommand(command: String?, extras: Bundle?, cb: ResultReceiver?) {
when(command) {
// Refresh media browser content and send result to subscribers
CustomCommand.REFRESH.toString() -> {
notifyChildrenChanged(MEDIA_ID_ROOT)
}
}
}}
Other research:
I have referred to the Google Samples code on Github, as well as...
https://github.com/googlesamples/android-MediaBrowserService
https://github.com/moondroid/UniversalMusicPlayer
Neither of the above repos seem to handle the issue of refreshing content after the media browser service has been created and the activity has subscribed at least once - I'd like to avoid restarting the service so that the music can continue playing in the background.
Possible related issues:
MediaBrowser.subscribe doesn't work after I get back to activity 1 from activity 2 (6.0.1 Android) --no effect on current issue
Calling you music service implementations notifyChildrenChanged(String parentId) will trigger the onLoadChildren and inside there, you can send a different result with result.sendResult().
What I did was that I added a BroadcastReceiver to my music service and inside it, I just called the notifyChildrenChanged(String parentId). And inside my Activity, I sent a broadcast when I changed the music list.
Optional (not Recommended) Quick fix
MusicService ->
companion object {
var musicServiceInstance:MusicService?=null
}
override fun onCreate() {
super.onCreate()
musicServiceInstance=this
}
//api call
fun fetchSongs(params:Int){
serviceScope.launch {
firebaseMusicSource.fetchMediaData(params)
//Edit Data or Change Data
notifyChildrenChanged(MEDIA_ROOT_ID)
}
}
ViewModel ->
fun fetchSongs(){
MusicService.musicServiceInstance?.let{
it.fetchSongs(params)
}
}
Optional (Recommended)
MusicPlaybackPreparer
class MusicPlaybackPreparer (
private val firebaseMusicSource: FirebaseMusicSource,
private val serviceScope: CoroutineScope,
private val exoPlayer: SimpleExoPlayer,
private val playerPrepared: (MediaMetadataCompat?) -> Unit
) : MediaSessionConnector.PlaybackPreparer {
override fun onCommand(player: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?
): Boolean {
when(command){
//edit data or fetch more data from api
"Add Songs"->{
serviceScope.launch {
firebaseMusicSource.fetchMediaData()
}
}
}
return false
}
override fun getSupportedPrepareActions(): Long {
return PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID or
PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
}
override fun onPrepare(playWhenReady: Boolean) = Unit
override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) {
firebaseMusicSource.whenReady {
val itemToPlay = firebaseMusicSource.songs.find { mediaId == it.description.mediaId }
playerPrepared(itemToPlay)
}
}
override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) = Unit
override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) = Unit
}
MusicServiceConnection
fun sendCommand(command: String, parameters: Bundle?) =
sendCommand(command, parameters) { _, _ -> }
private fun sendCommand(
command: String,
parameters: Bundle?,
resultCallback: ((Int, Bundle?) -> Unit)
) = if (mediaBrowser.isConnected) {
mediaController.sendCommand(command, parameters, object : ResultReceiver(Handler()) {
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
resultCallback(resultCode, resultData)
}
})
true
} else {
false
}
ViewModel
fun fetchSongs(){
val args = Bundle()
args.putInt("nRecNo", 2)
musicServiceConnection.sendCommand("Add Songs", args )
}
MusicService ->
override fun onLoadChildren(
parentId: String,
result: Result<MutableList<MediaBrowserCompat.MediaItem>>
) {
when(parentId) {
MEDIA_ROOT_ID -> {
val resultsSent = firebaseMusicSource.whenReady { isInitialized ->
if(isInitialized) {
try {
result.sendResult(firebaseMusicSource.asMediaItems())
if(!isPlayerInitialized && firebaseMusicSource.songs.isNotEmpty()) {
preparePlayer(firebaseMusicSource.songs, firebaseMusicSource.songs[0], true)
isPlayerInitialized = true
}
}
catch (exception: Exception){
// not recommend to notify here , instead notify when you
// change existing list in MusicPlaybackPreparer onCommand()
notifyChildrenChanged(MEDIA_ROOT_ID)
}
} else {
result.sendResult(null)
}
}
if(!resultsSent) {
result.detach()
}
}
}
}
My issue was unrelated to the MediaBrowserServiceCompat class. The issue was coming about because I was calling result.detach() in order to implement some asynchronous data fetching, and the listener I was using had both the parentId and result variables from the onLoadChildren method passed in and assigned final val rather than var.
I still don't fully understand why this occurs, whether it's an underlying result of using a Player.EventListener within another asynchronous network call listener, but the solution was to create and assign a variable (and perhaps someone else can explain this phenomenon):
// Create variable
var currentResult: Result<List<MediaBrowserCompat.MediaItem>>? = null
override fun onLoadChildren(parentId: String, result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>) {
// Use result.detach to allow calling result.sendResult from another thread
result.detach()
// Assign returned result to temporary variable
currentResult = result
currentParentId = parentId
// Create listener for network call
ChannelManager.onFlagChannelManagerDataListener = object : ChannelManager.Companion.OnFlagChannelManagerDataListener {
override fun onSyncFinished() {
// Create a listener to determine when player is prepared
val playerEventListener = object : Player.EventListener {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
when(playbackState) {
Player.STATE_READY -> {
if(mPlayerPreparing) {
// Prepare content to send to subscribed content
loadChildrenImpl(currentParentId, currentResult as MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>)
mPlayerPreparing = false
}
}
...
}
}
}
}