banner



What Apps Cause The Net Scheduler Wake Lock?

I've been struggling these days trying to find a way to run an endless service in Android. This is just a guide for all of you who pursue the same goal. Hope it helps! :grin:

The problem

Due to Android battery optimizations introduced in Android 8.0 (API level 26), background services have now some important limitations. Essentially, they are killed once the app is in background for a while making them worthless for our purpose of running an always-running service.

According to Android recommendations, we should be using JobScheduler, which seems to work pretty well and will handle wakelocks for us, keeping the phone awake while the jobs are running.

Unfortunately, this won't work either. JobScheduler will run jobs at Android's discretion and, on top of that, once the phone enters in Doze Mode, the frequency of these jobs being run will constantly decrease. And even worst, if you ever want to access the network -say you need to send data to your server- you won't be able to. Check out the list of restrictions Doze Mode imposes.

JobScheduler works well if you don't mind about not having access to the network and you don't care about not controlling the periodicity either. In our case, we want our service to run at a very specific frequency and never be stopped, so we'll need something else.

It's all about Foreground Services

If you've been looking over the internet for a solution to this problem it's very likely that you've eventually arrived to this page from the Android's documentation.

There, we are introduced to the different types of services that Android provides. Take a look at the Foreground Service description:

A foreground service performs some operation that is noticeable to the user. For example, an audio app would use a foreground service to play an audio track. Foreground services must display a Notification. Foreground services continue running even when the user isn't interacting with the app.

It seems to be precisely what we're looking for… an it is, indeed! :wink:

Show me the code

Creating a foreground service is really a straight-forward process so I will visit and explain all the steps needed to build a foreground service that never stops.

As usual, I've created a repository with all the code in case you want to take a look at it and skip the rest of the post.

Adding some dependencies

I'm using Kotlin for this example, so we will be leveraging coroutines and the Fuel library for HTTP requests.

In order to add these dependencies we must add them to our build.gradle file:

                          dependencies              {              implementation              fileTree              (              dir:              'libs'              ,              include:              [              '*.jar'              ])              implementation              "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"              implementation              'com.android.support:appcompat-v7:28.0.0'              implementation              'com.android.support.constraint:constraint-layout:1.1.3'              implementation              'com.jaredrummler:android-device-names:1.1.8'              implementation              'com.github.kittinunf.fuel:fuel:2.1.0'              implementation              'com.github.kittinunf.fuel:fuel-android:2.1.0'              implementation              'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M1'              testImplementation              'junit:junit:4.12'              androidTestImplementation              'com.android.support.test:runner:1.0.2'              androidTestImplementation              'com.android.support.test.espresso:espresso-core:3.0.2'              }                      

Writing our service

Foreground Services need a notification to be shown so the user is aware that the app is still running. That makes sense if you think about it.

Note that we will have to override some of the Service callback methods that handle key aspects of the service lifecycle.

It's also very important that we use a partial wakelock so our service never gets affected by Doze Mode. Bear in mind that this will have an impact in the battery life of our phone so we must evaluate whether our use case can be handled by any of the other alternatives Android offers in order to run processes in the background.

There are some utility function calls (log, setServiceState) and some custom enums (ServiceState.STARTED) in the code, but don't worry too much. If you want to see where they come from, just take a look at the example repository.

                          class              EndlessService              :              Service              ()              {              private              var              wakeLock              :              PowerManager              .              WakeLock              ?              =              null              private              var              isServiceStarted              =              false              override              fun              onBind              (              intent              :              Intent              ):              IBinder              ?              {              log              (              "Some component want to bind with the service"              )              // We don't provide binding, so return null                            return              null              }              override              fun              onStartCommand              (              intent              :              Intent              ?,              flags              :              Int              ,              startId              :              Int              ):              Int              {              log              (              "onStartCommand executed with startId: $startId"              )              if              (              intent              !=              null              )              {              val              action              =              intent              .              action              log              (              "using an intent with action $action"              )              when              (              action              )              {              Actions              .              START              .              name              ->              startService              ()              Actions              .              STOP              .              name              ->              stopService              ()              else              ->              log              (              "This should never happen. No action in the received intent"              )              }              }              else              {              log              (              "with a null intent. It has been probably restarted by the system."              )              }              // by returning this we make sure the service is restarted if the system kills the service                            return              START_STICKY              }              override              fun              onCreate              ()              {              super              .              onCreate              ()              log              (              "The service has been created"              .              toUpperCase              ())              var              notification              =              createNotification              ()              startForeground              (              1              ,              notification              )              }              override              fun              onDestroy              ()              {              super              .              onDestroy              ()              log              (              "The service has been destroyed"              .              toUpperCase              ())              Toast              .              makeText              (              this              ,              "Service destroyed"              ,              Toast              .              LENGTH_SHORT              ).              show              ()              }              private              fun              startService              ()              {              if              (              isServiceStarted              )              return              log              (              "Starting the foreground service task"              )              Toast              .              makeText              (              this              ,              "Service starting its task"              ,              Toast              .              LENGTH_SHORT              ).              show              ()              isServiceStarted              =              true              setServiceState              (              this              ,              ServiceState              .              STARTED              )              // we need this lock so our service gets not affected by Doze Mode                            wakeLock              =              (              getSystemService              (              Context              .              POWER_SERVICE              )              as              PowerManager              ).              run              {              newWakeLock              (              PowerManager              .              PARTIAL_WAKE_LOCK              ,              "EndlessService::lock"              ).              apply              {              acquire              ()              }              }              // we're starting a loop in a coroutine                            GlobalScope              .              launch              (              Dispatchers              .              IO              )              {              while              (              isServiceStarted              )              {              launch              (              Dispatchers              .              IO              )              {              pingFakeServer              ()              }              delay              (              1              *              60              *              1000              )              }              log              (              "End of the loop for the service"              )              }              }              private              fun              stopService              ()              {              log              (              "Stopping the foreground service"              )              Toast              .              makeText              (              this              ,              "Service stopping"              ,              Toast              .              LENGTH_SHORT              ).              show              ()              try              {              wakeLock              ?.              let              {              if              (              it              .              isHeld              )              {              it              .              release              ()              }              }              stopForeground              (              true              )              stopSelf              ()              }              catch              (              e              :              Exception              )              {              log              (              "Service stopped without being started: ${e.message}"              )              }              isServiceStarted              =              false              setServiceState              (              this              ,              ServiceState              .              STOPPED              )              }              private              fun              pingFakeServer              ()              {              val              df              =              SimpleDateFormat              (              "yyyy-MM-dd'T'HH:mm:ss.mmmZ"              )              val              gmtTime              =              df              .              format              (              Date              ())              val              deviceId              =              Settings              .              Secure              .              getString              (              applicationContext              .              contentResolver              ,              Settings              .              Secure              .              ANDROID_ID              )              val              json              =              """                 {                     "deviceId": "$deviceId",                     "createdAt": "$gmtTime"                 }             """              try              {              Fuel              .              post              (              "https://jsonplaceholder.typicode.com/posts"              )              .              jsonBody              (              json              )              .              response              {              _              ,              _              ,              result              ->              val              (              bytes              ,              error              )              =              result              if              (              bytes              !=              null              )              {              log              (              "[response bytes] ${String(bytes)}"              )              }              else              {              log              (              "[response error] ${error?.message}"              )              }              }              }              catch              (              e              :              Exception              )              {              log              (              "Error making the request: ${e.message}"              )              }              }              private              fun              createNotification              ():              Notification              {              val              notificationChannelId              =              "ENDLESS SERVICE CHANNEL"              // depending on the Android API that we're dealing with we will have                            // to use a specific method to create the notification                            if              (              Build              .              VERSION              .              SDK_INT              >=              Build              .              VERSION_CODES              .              O              )              {              val              notificationManager              =              getSystemService              (              Context              .              NOTIFICATION_SERVICE              )              as              NotificationManager              ;              val              channel              =              NotificationChannel              (              notificationChannelId              ,              "Endless Service notifications channel"              ,              NotificationManager              .              IMPORTANCE_HIGH              ).              let              {              it              .              description              =              "Endless Service channel"              it              .              enableLights              (              true              )              it              .              lightColor              =              Color              .              RED              it              .              enableVibration              (              true              )              it              .              vibrationPattern              =              longArrayOf              (              100              ,              200              ,              300              ,              400              ,              500              ,              400              ,              300              ,              200              ,              400              )              it              }              notificationManager              .              createNotificationChannel              (              channel              )              }              val              pendingIntent              :              PendingIntent              =              Intent              (              this              ,              MainActivity              ::              class              .              java              ).              let              {              notificationIntent              ->              PendingIntent              .              getActivity              (              this              ,              0              ,              notificationIntent              ,              0              )              }              val              builder              :              Notification              .              Builder              =              if              (              Build              .              VERSION              .              SDK_INT              >=              Build              .              VERSION_CODES              .              O              )              Notification              .              Builder              (              this              ,              notificationChannelId              )              else              Notification              .              Builder              (              this              )              return              builder              .              setContentTitle              (              "Endless Service"              )              .              setContentText              (              "This is your favorite endless service working"              )              .              setContentIntent              (              pendingIntent              )              .              setSmallIcon              (              R              .              mipmap              .              ic_launcher              )              .              setTicker              (              "Ticker text"              )              .              setPriority              (              Notification              .              PRIORITY_HIGH              )              // for under android 26 compatibility                            .              build              ()              }              }                      

Time to deal with the Android Manifest

We will need some extra permissions for FOREGROUND_SERVICE, INTERNET and WAKE_LOCK. Be sure you don't forget to include them because it won't work otherwise.

Once we put them in place we will need to declare our service.

                          <?xml version="1.0" encoding="utf-8"?>              <manifest              xmlns:android=              "http://schemas.android.com/apk/res/android"              package=              "com.robertohuertas.endless"              >              <uses-permission              android:name=              "android.permission.FOREGROUND_SERVICE"              ></uses-permission>              <uses-permission              android:name=              "android.permission.INTERNET"              ></uses-permission>              <uses-permission              android:name=              "android.permission.WAKE_LOCK"              />              <application              android:allowBackup=              "true"              android:icon=              "@mipmap/ic_launcher"              android:label=              "@string/app_name"              android:roundIcon=              "@mipmap/ic_launcher_round"              android:supportsRtl=              "true"              android:theme=              "@style/AppTheme"              >              <service              android:name=              ".EndlessService"              android:enabled=              "true"              android:exported=              "false"              >              </service>              <activity              android:name=              ".MainActivity"              >              <intent-filter>              <action              android:name=              "android.intent.action.MAIN"              />              <category              android:name=              "android.intent.category.LAUNCHER"              />              </intent-filter>              </activity>              </application>              </manifest>                      

Can you tell me how I start the service?

Yes, you're right. You see, depending on the Android version we must start the service with a particular method.

If the Android version is below API 26 we must use startService. In any other case, startForegroundService is what we must use instead.

Here you can see our MainActivity, just a screen with two buttons to start and stop the service. That's all you need to start our endless service.

Remember that you can check out the complete code in this GitHub repository.

                          class              MainActivity              :              AppCompatActivity              ()              {              override              fun              onCreate              (              savedInstanceState              :              Bundle              ?)              {              super              .              onCreate              (              savedInstanceState              )              setContentView              (              R              .              layout              .              activity_main              )              title              =              "Endless Service"              findViewById              <              Button              >(              R              .              id              .              btnStartService              ).              let              {              it              .              setOnClickListener              {              log              (              "START THE FOREGROUND SERVICE ON DEMAND"              )              actionOnService              (              Actions              .              START              )              }              }              findViewById              <              Button              >(              R              .              id              .              btnStopService              ).              let              {              it              .              setOnClickListener              {              log              (              "STOP THE FOREGROUND SERVICE ON DEMAND"              )              actionOnService              (              Actions              .              STOP              )              }              }              }              private              fun              actionOnService              (              action              :              Actions              )              {              if              (              getServiceState              (              this              )              ==              ServiceState              .              STOPPED              &&              action              ==              Actions              .              STOP              )              return              Intent              (              this              ,              EndlessService              ::              class              .              java              ).              also              {              it              .              action              =              action              .              name              if              (              Build              .              VERSION              .              SDK_INT              >=              Build              .              VERSION_CODES              .              O              )              {              log              (              "Starting the service in >=26 Mode"              )              startForegroundService              (              it              )              return              }              log              (              "Starting the service in < 26 Mode"              )              startService              (              it              )              }              }              }                      

Bonus: Start the service on Android boot

Ok, we now have our endless service making network requests every minute as we wanted but then the user restarts the phone… and our service doesn't start again… :disappointed:

Don't worry, we can find a solution for this, too. We will create a BroadCastReceiver called StartReceiver.

                          class              StartReceiver              :              BroadcastReceiver              ()              {              override              fun              onReceive              (              context              :              Context              ,              intent              :              Intent              )              {              if              (              intent              .              action              ==              Intent              .              ACTION_BOOT_COMPLETED              &&              getServiceState              (              context              )              ==              ServiceState              .              STARTED              )              {              Intent              (              context              ,              EndlessService              ::              class              .              java              ).              also              {              it              .              action              =              Actions              .              START              .              name              if              (              Build              .              VERSION              .              SDK_INT              >=              Build              .              VERSION_CODES              .              O              )              {              log              (              "Starting the service in >=26 Mode from a BroadcastReceiver"              )              context              .              startForegroundService              (              it              )              return              }              log              (              "Starting the service in < 26 Mode from a BroadcastReceiver"              )              context              .              startService              (              it              )              }              }              }              }                      

Then, we'll modify again our Android Manifest and add a new permission (RECEIVE_BOOT_COMPLETED) and our new BroadCastReceiver.

                          <?xml version="1.0" encoding="utf-8"?>              <manifest              xmlns:android=              "http://schemas.android.com/apk/res/android"              package=              "com.robertohuertas.endless"              >              <uses-permission              android:name=              "android.permission.FOREGROUND_SERVICE"              ></uses-permission>              <uses-permission              android:name=              "android.permission.INTERNET"              ></uses-permission>              <uses-permission              android:name=              "android.permission.WAKE_LOCK"              />              <uses-permission              android:name=              "android.permission.RECEIVE_BOOT_COMPLETED"              />              <application              android:allowBackup=              "true"              android:icon=              "@mipmap/ic_launcher"              android:label=              "@string/app_name"              android:roundIcon=              "@mipmap/ic_launcher_round"              android:supportsRtl=              "true"              android:theme=              "@style/AppTheme"              >              <service              android:name=              ".EndlessService"              android:enabled=              "true"              android:exported=              "false"              >              </service>              <activity              android:name=              ".MainActivity"              >              <intent-filter>              <action              android:name=              "android.intent.action.MAIN"              />              <category              android:name=              "android.intent.category.LAUNCHER"              />              </intent-filter>              </activity>              <receiver              android:enabled=              "true"              android:name=              ".StartReceiver"              >              <intent-filter>              <action              android:name=              "android.intent.action.BOOT_COMPLETED"              />              </intent-filter>              </receiver>              </application>              </manifest>                      

Take into account that the service won't be rebooted unless it was already running. That's how we programmed it, it's not that it has to be like that.

Anyway, if you want to test this, just spin up one emulator with Google Services in it and be sure to be running adb in root mode.

            adb root              # If you get an error then you're not running the proper emulator.              # Be sure to stop the service              # and force a system restart:              adb shell stop adb shell start              # wait for the service to be restarted!                      

Bonus 2: Restart the service when the task is removed

Michal Materowski wrote to me with this case and its solution, so kudos for him!

Theoretically, according to Android documentation, returning RETURN_STICKY from the service's onStartCommand method should be enough for Android to keep the foreground service running.

Michal was testing all this with a Xiaomi Note 5 with Android Pie and every time he swiped an app from recent apps, it worked flawlessly. Unfortunately, whenever he pressed the Clear all recent apps button (MIUI specific), the service was stopped and the notification was gone. The Clear all recent apps button was probably doing some sort of battery life optimization by killing all processes and their associated services.

He found out that Android documentation states that onTaskRemoved "is called if the service is currently running and the user has removed a task that comes from the service's application.", so the plan was to leverage this to restart the service. Bear in mind, though, that onTaskRemoved is not called if the application is killed in any other way (e.g. stopped from the phone settings).

Add these lines to your service:

                          override              fun              onTaskRemoved              (              rootIntent              :              Intent              )              {              val              restartServiceIntent              =              Intent              (              applicationContext              ,              EndlessService              ::              class              .              java              ).              also              {              it              .              setPackage              (              packageName              )              };              val              restartServicePendingIntent              :              PendingIntent              =              PendingIntent              .              getService              (              this              ,              1              ,              restartServiceIntent              ,              PendingIntent              .              FLAG_ONE_SHOT              );              applicationContext              .              getSystemService              (              Context              .              ALARM_SERVICE              );              val              alarmService              :              AlarmManager              =              applicationContext              .              getSystemService              (              Context              .              ALARM_SERVICE              )              as              AlarmManager              ;              alarmService              .              set              (              AlarmManager              .              ELAPSED_REALTIME              ,              SystemClock              .              elapsedRealtime              ()              +              1000              ,              restartServicePendingIntent              );              }                      

You can check out the original Michal Materowski's PR with the whole code.

IMPORTANT: Michal had to manually set Autostart permission, otherwise the service was not started on boot.

Autostart

According to Michal, some people mentioned that setting stopWithTask flag might help, but it didn't make a difference for him:

                          <service              android:name=              ".EndlessService"              "              android:stopWithTask=              "false"              />                      

Lots of kudos to Michal Materowski for his help on this case.

Enjoy!

What Apps Cause The Net Scheduler Wake Lock?

Source: https://robertohuertas.com/2019/06/29/android_foreground_services/

Posted by: schneiderfelist.blogspot.com

0 Response to "What Apps Cause The Net Scheduler Wake Lock?"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel