Building an Endless Running Android Service

Building an Endless Running Android Service

Hacker Techniques
Click on the right to follow and learn about the world of hackers!
Building an Endless Running Android Service

Building an Endless Running Android Service

JavaDevelopment Advancement
Click on the right to master the path of advancement!
Building an Endless Running Android Service

Building an Endless Running Android Service

Python Development
Click on the right to discuss technical topics!
Building an Endless Running Android Service
Author丨Happy Developer World

Source丨Android Bus Android Developer Portal

Link:

http://www.apkbus.com/blog-989692-81330.html
Building an Endless Running Android Service
These days I have been trying to find a way to run a service in Android that never stops.This is just a guide to pursue the same goal.I hope it helps you!

Problem

Due to Android 8.0 (API level 26) introducing Android battery optimization, there are now some significant restrictions on background services.Basically, once an application runs in the background for a while, it gets killed, making it worthless for running a service that never stops.
According to Android’s advice, we should use JobScheduler to wake locks to keep the phone awake while the job runs; it seems to work well and will handle the wake locks for us.
Unfortunately, this does not work.Most importantly, the JobScheduler’s doze mode (you need to send data to your server) has a list of restrictions that will allow Android to decide when to run jobs, and once the phone enters doze mode, the frequency of running these jobs will keep increasing.Even worse, if you want to access the network (you need to send data to your server), you won’t be able to do so.Check the list of restrictions imposed by doze mode.
If you don’t mind not being able to access the network and you don’t care about not controlling periodicity, JobScheduler can work fine.In our case, we want our service to run at a very specific frequency and never stop, so we need something else.

About Foreground Services

If you have been looking for a solution to this problem on the internet, you likely ended up on this page from Android’s documentation.
There, we introduce the different types of services that Android provides.Take a look atForeground Service Description:
Foreground services perform operations that are noticeable to the user. For example, audio applications will use foreground services to play audio tracks. Foreground services must display a notification. Even if the user does not interact with the application, the foreground service continues to run.
This seems to be exactly what we are looking for… and indeed it is!

My Code

Creating aforeground service is really a simple process, so I will go through and explain all the steps required to build a never-stopping foreground service.
As usual, I have created a repository containing all the code in case you want to take a look and skip the rest of the post.

Add Some Dependencies

I am using Kotlin coroutines Fuel in this example, so we will leverage coroutines and the Fuel library to handle HTTP requests.
To add these dependencies, we have to add them to ourbuild.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'
}

Our Service

Foreground Services need to display notifications so that users know the application is still running.It makes sense if you think about it.
Note that we need to override some service lifecycle handling callback methods.
It is also very important that we use partial wake locks to ensure that our service is never affected by doze mode.Keep in mind that this will affect the battery life of our phone, so we must evaluate whether our use case can handle any other alternatives provided by Android to run processes in the background.
There are some utility function calls in the code (log, setServiceState) and some custom enums (ServiceState.STARTED), but don’t worry too much.If you want to know where they come from, check 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 Handle the Android Manifest

We need some extra permissionsFOREGROUND_SERVICE, INTERNET and WAKE_LOCK.Make sure you don’t forget to include them, as it won’t work.
Once we have them in place, we will need to declare the 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>

Starting the Service

Depending on the Android version, we must use a specific method to start the service.
If the Android version is below API 26, we must use startServicestartForegroundService.In any other case, it is we who use startForegroundService.
Here you can see ourMainActivity, which has only one screen with two buttons to start and stop the service.This is all you need to start our never stopping service.
Keep in mind that you can check 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 &amp;&amp; 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)
        }
    }
}

Effect: Start Service on Android Boot

Okay, now we have a never stopping service that initiates network requests every minute, and then the user restarts the phone… our service won’t restart…:(Disappointed)
Don’t worry, we can find a solution for this too.We will create a BroadcastReceiver namedStartReceiver.
class StartReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == Intent.ACTION_BOOT_COMPLETED &amp;&amp; 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 will modify ourAndroid Manifest again to 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>

Note that the service will not be restarted unless it was running.This is how we programmed it, and it doesn’t have to be this way.
If you want to test this, just start an emulator with Google services and make sure to run 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!

Enjoy!

Recommended↓↓↓

Building an Endless Running Android Service

Long

Press

Close

Note

👉16 Technical Public Accounts】 are all here!

Covers: Programmer Celebrities, Source Code Reading, Programmer Reading, Data Structures and Algorithms, Hacker Techniques and Network Security, Big Data Technology, Programming Frontend, Java, Python, Web Programming Development, Android, iOS Development, Linux, Database Development, Humorous Programmers, etc.

Building an Endless Running Android Service
Endless affection, could you please click “Looking”?

Leave a Comment

Your email address will not be published. Required fields are marked *