Optimize background Workers, remove redundant JobServices

1. Adapt the location callback in WeatherWorker to coroutines
2. Merge EventListenerJob into UpdateCalendarWorker
3. Remove BatteryListenerJob, as it's impossible to use Jobs to detect battery status
This commit is contained in:
azuo 2021-10-07 16:03:07 +08:00
parent 388653f62b
commit d8e204c5d9
13 changed files with 123 additions and 168 deletions

View File

@ -108,10 +108,6 @@
</intent-filter>
</receiver>
<service android:name=".services.EventListenerJob" android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name=".services.BatteryListenerJob" android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name=".receivers.NotificationListener" android:exported="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>

View File

@ -4,7 +4,6 @@ import android.Manifest
import android.content.Context
import android.content.Intent
import android.provider.CalendarContract
import com.tommasoberlose.anotherwidget.services.EventListenerJob
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.services.UpdateCalendarWorker
@ -50,11 +49,11 @@ object CalendarHelper {
}
fun setEventUpdatesAndroidN(context: Context) {
EventListenerJob.schedule(context)
UpdateCalendarWorker.enqueueTrigger(context)
}
fun removeEventUpdatesAndroidN(context: Context) {
EventListenerJob.remove(context)
UpdateCalendarWorker.cancelTrigger(context)
}
fun List<Event>.applyFilters() : List<Event> {

View File

@ -324,7 +324,9 @@ object WeatherHelper {
}
}
fun getWeatherGovIcon(iconString: String, isDaytime: Boolean): String = when (iconString.substringBefore('?').substringAfterLast('/')) {
fun getWeatherGovIcon(iconString: String, isDaytime: Boolean): String = when (
iconString.substringBefore('?').substringAfterLast('/').substringBefore(',')
) {
"skc" -> "01"
"few" -> "02"
"sct" -> "02"

View File

@ -42,12 +42,12 @@ class WeatherReceiver : BroadcastReceiver() {
5 -> 60L * 24
else -> 60
}
WeatherWorker.enqueue(context, interval, TimeUnit.MINUTES)
WeatherWorker.enqueuePeriodic(context, interval, TimeUnit.MINUTES)
}
}
fun removeUpdates(context: Context) {
WeatherWorker.cancel(context)
WeatherWorker.cancelPeriodic(context)
}
}
}

View File

@ -1,61 +0,0 @@
package com.tommasoberlose.anotherwidget.services
import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.Context
import android.os.Build
import android.provider.CalendarContract
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
class BatteryListenerJob : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
MainWidget.updateWidget(this)
schedule(
this
)
return false
}
@Synchronized
override fun onStopJob(params: JobParameters): Boolean {
return false
}
companion object {
private const val chargingJobId = 1006
private const val notChargingJobId = 1007
fun schedule(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
remove(context)
val componentName = ComponentName(
context,
EventListenerJob::class.java
)
with(context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) {
schedule(
JobInfo.Builder(chargingJobId, componentName)
.setRequiresCharging(true)
.setPersisted(true)
.build()
)
schedule(
JobInfo.Builder(notChargingJobId, componentName)
.setRequiresCharging(false)
.setPersisted(true)
.build()
)
}
}
}
private fun remove(context: Context) {
val js = context.getSystemService(JobScheduler::class.java)
js?.cancel(chargingJobId)
js?.cancel(notChargingJobId)
}
}
}

View File

@ -1,55 +0,0 @@
package com.tommasoberlose.anotherwidget.services
import android.app.job.JobInfo
import android.app.job.JobInfo.TriggerContentUri
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.Context
import android.os.Build
import android.provider.CalendarContract
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
class EventListenerJob : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
CalendarHelper.updateEventList(this)
schedule(
this
)
return false
}
@Synchronized
override fun onStopJob(params: JobParameters): Boolean {
return false
}
companion object {
private const val jobId = 1005
fun schedule(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val componentName = ComponentName(
context,
EventListenerJob::class.java
)
with(context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) {
schedule(
JobInfo.Builder(jobId, componentName)
.addTriggerContentUri(TriggerContentUri(
CalendarContract.CONTENT_URI,
TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
))
.build()
)
}
}
}
fun remove(context: Context) {
val js = context.getSystemService(JobScheduler::class.java)
js?.cancel(jobId)
}
}
}

View File

@ -2,6 +2,9 @@ package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.content.Context
import android.os.Build
import android.provider.CalendarContract
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
@ -17,12 +20,12 @@ import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
import java.util.*
import kotlin.collections.ArrayList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class UpdateCalendarWorker(private val context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
@ -36,7 +39,6 @@ class UpdateCalendarWorker(private val context: Context, params: WorkerParameter
if (!context.checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
Preferences.showEvents = false
} else {
// fetch all events from now to next ACTION_CALENDAR_UPDATE + limit
val now = Calendar.getInstance()
@ -139,6 +141,7 @@ class UpdateCalendarWorker(private val context: Context, params: WorkerParameter
} catch (ignored: java.lang.Exception) {
}
}
enqueueTrigger(context)
} else {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
@ -156,11 +159,34 @@ class UpdateCalendarWorker(private val context: Context, params: WorkerParameter
companion object {
fun enqueue(context: Context) {
WorkManager.getInstance(context.applicationContext).enqueueUniqueWork(
"UpdateCalendarWorker",
ExistingWorkPolicy.REPLACE,
WorkManager.getInstance(context).enqueueUniqueWork(
"updateEventList",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<UpdateCalendarWorker>().build()
)
}
fun enqueueTrigger(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
WorkManager.getInstance(context).enqueueUniqueWork(
"updateEventListByTrigger",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<UpdateCalendarWorker>().setConstraints(
Constraints.Builder().addContentUriTrigger(
CalendarContract.CONTENT_URI,
true
).build()
).build()
)
}
}
fun cancelTrigger(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
WorkManager.getInstance(context).cancelUniqueWork(
"updateEventListByTrigger"
)
}
}
}
}

View File

@ -14,19 +14,18 @@ import androidx.work.WorkerParameters
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.location.LocationServices
import com.google.android.gms.tasks.Tasks
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
class WeatherWorker(private val context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
@ -42,7 +41,11 @@ class WeatherWorker(private val context: Context, params: WorkerParameters) :
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
== ConnectionResult.SUCCESS
) {
LocationServices.getFusedLocationProviderClient(context).lastLocation
suspendCancellableCoroutine { continuation ->
LocationServices.getFusedLocationProviderClient(context).lastLocation.addOnCompleteListener {
continuation.resume(if (it.isSuccessful) it.result else null)
}
}
} else {
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
var location: Location? = null
@ -54,21 +57,15 @@ class WeatherWorker(private val context: Context, params: WorkerParameters) :
location = it
}
}
Tasks.forResult(location)
}.addOnCompleteListener { task ->
val networkApi = WeatherNetworkApi(context)
if (task.isSuccessful) {
val location = task.result
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
location
}.let { location ->
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.IO) {
WeatherNetworkApi(context).updateWeather()
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
else -> {
@ -83,24 +80,24 @@ class WeatherWorker(private val context: Context, params: WorkerParameters) :
companion object {
fun enqueue(context: Context) {
WorkManager.getInstance(context.applicationContext).enqueueUniqueWork(
"OneTimeWeatherWorker",
WorkManager.getInstance(context).enqueueUniqueWork(
"updateWeather",
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<WeatherWorker>().build()
)
}
fun enqueue(context: Context, interval: Long, unit: TimeUnit) {
WorkManager.getInstance(context.applicationContext).enqueueUniquePeriodicWork(
"WeatherWorker",
fun enqueuePeriodic(context: Context, interval: Long, unit: TimeUnit) {
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"updateWeatherPeriodically",
ExistingPeriodicWorkPolicy.REPLACE,
PeriodicWorkRequestBuilder<WeatherWorker>(interval, unit).build()
)
}
fun cancel(context: Context) {
WorkManager.getInstance(context.applicationContext).cancelUniqueWork(
"WeatherWorker"
fun cancelPeriodic(context: Context) {
WorkManager.getInstance(context).cancelUniqueWork(
"updateWeatherPeriodically"
)
}
}

View File

@ -141,7 +141,6 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
if (Preferences.showEvents && !checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
Preferences.showEvents = false
com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver.removeUpdates(this)
}
}

View File

@ -88,6 +88,7 @@ class PreferencesFragment : Fragment() {
CalendarHelper.setEventUpdatesAndroidN(requireContext())
} else {
CalendarHelper.removeEventUpdatesAndroidN(requireContext())
UpdatesReceiver.removeUpdates(requireContext())
}
}
}
@ -124,7 +125,6 @@ class PreferencesFragment : Fragment() {
requireCalendarPermission()
} else {
Preferences.showEvents = enabled
UpdatesReceiver.removeUpdates(requireContext())
}
}
@ -165,6 +165,7 @@ class PreferencesFragment : Fragment() {
.withPermissions(
Manifest.permission.READ_CALENDAR
).withListener(object: MultiplePermissionsListener {
private var shouldShowRationale = false
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
val granted = report.areAllPermissionsGranted()
@ -172,12 +173,33 @@ class PreferencesFragment : Fragment() {
if (granted) {
CalendarHelper.updateEventList(requireContext())
}
else if (!shouldShowRationale && report.isAnyPermissionPermanentlyDenied) {
MaterialBottomSheetDialog(
requireContext(),
getString(R.string.title_permission_calendar),
getString(R.string.description_permission_calendar)
).setNegativeButton(getString(R.string.action_ignore))
.setPositiveButton(getString(R.string.action_grant_permission)) {
startActivity(
android.content.Intent(
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
).apply {
data = android.net.Uri.fromParts(
"package",
requireContext().packageName,
null
)
}
)
}.show()
}
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) {
shouldShowRationale = true
// Remember to invoke this method when the custom rationale is closed
// or just by default if you don't want to use any custom rationale.
token?.continuePermissionRequest()

View File

@ -221,18 +221,40 @@ class WeatherFragment : Fragment() {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
).withListener(object: MultiplePermissionsListener {
private var shouldShowRationale = false
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if (report.grantedPermissionResponses.isNotEmpty()) {
checkLocationPermission()
WeatherHelper.updateWeather(requireContext())
}
else if (!shouldShowRationale && report.isAnyPermissionPermanentlyDenied) {
MaterialBottomSheetDialog(
requireContext(),
getString(R.string.title_permission_location),
getString(R.string.description_permission_location)
).setNegativeButton(getString(R.string.action_ignore))
.setPositiveButton(getString(R.string.action_grant_permission)) {
startActivity(
Intent(
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
).apply {
data = android.net.Uri.fromParts(
"package",
requireContext().packageName,
null
)
}
)
}.show()
}
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) {
shouldShowRationale = true
// Remember to invoke this method when the custom rationale is closed
// or just by default if you don't want to use any custom rationale.
token?.continuePermissionRequest()
@ -241,6 +263,12 @@ class WeatherFragment : Fragment() {
.check()
}
override fun onResume() {
super.onResume()
checkWeatherProviderConfig()
checkLocationPermission()
}
private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false
callback.invoke()

View File

@ -155,6 +155,7 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::secondRowInformation)) { value = true }
addSource(Preferences.asLiveData(Preferences::showWeather)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherProvider)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherTempUnit)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherIconPack)) { value = true }
addSource(Preferences.asLiveData(Preferences::customLocationLat)) { value = true }

View File

@ -34,25 +34,26 @@ class MainWidget : AppWidgetProvider() {
CalendarHelper.updateEventList(context)
WeatherReceiver.setUpdates(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
if (Preferences.showEvents) {
CalendarHelper.setEventUpdatesAndroidN(context)
} else {
CalendarHelper.removeEventUpdatesAndroidN(context)
}
}
override fun onDisabled(context: Context) {
if (getWidgetCount(context) == 0) {
CalendarHelper.removeEventUpdatesAndroidN(context)
UpdatesReceiver.removeUpdates(context)
WeatherReceiver.removeUpdates(context)
}
}
companion object {
private val handler by lazy { android.os.Handler(android.os.Looper.getMainLooper()) }
fun updateWidget(context: Context) {
context.sendBroadcast(IntentHelper.getWidgetUpdateIntent(context))
handler.run {
removeCallbacksAndMessages(null)
postDelayed ({
context.sendBroadcast(IntentHelper.getWidgetUpdateIntent(context))
}, 100)
}
}
fun getWidgetCount(context: Context): Int {