Android Services

Android service is a component that is used to perform operations on the background such as playing music, handle network transactions, interacting content providers etc. It doesn't has any UI (user interface) and it is not bound to the lifecycle of an activity.

The service runs in the background indefinitely even if application is destroyed.

Moreover, service can be bounded by a component to perform interactivity and inter process communication (IPC).

A service can essentially take two states

State Description
Started A service is started when an application component, such as an activity, starts it by calling startService(). Once started, a service can run in the background indefinitely, even if the component that started it is destroyed.
Bound A service is bound when an application component binds to it by calling bindService(). A bound service offers a client-server interface that allows components to interact with the service, send requests, get results, and even do so across processes with interprocess communication (IPC).

Started Service

A service is started when component (like activity) calls startService() method, now it runs in the background indefinitely. It is stopped by stopService() method. The service can stop itself by calling the stopSelf() method.


Bound Service

A service is bound when another component (e.g. client) calls bindService() method. The client can unbind the service by calling the unbindService() method.


Note: The service cannot be stopped until all clients unbind the service.


MyService.java
package com.theitbulls.androidservicedemo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

import java.util.Date;

public class MyService extends Service {
    private boolean started;

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if(started) {
            Log.d("Service", "Already started");
            Toast.makeText(getBaseContext(), "Already running", Toast.LENGTH_SHORT).show();
            return START_STICKY;
        }

        started = true;

        Log.d("Service", "Started at: " + new Date());
        Toast.makeText(getBaseContext(), "Service started at: " + new Date(), Toast.LENGTH_SHORT).show();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        started = false;
        Log.d("Service", "Stoped at: " + new Date());
        Toast.makeText(getBaseContext(), "Service stoped at: " + new Date(), Toast.LENGTH_SHORT).show();
    }
}


MainActivity.java
package com.theitbulls.androidservicedemo;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);
    }

    public void startService(View view) {
        Intent serviceIntent = new Intent(this, MyService.class);
        startService(serviceIntent);
    }

    public void stopService(View view) {
        Intent serviceIntent = new Intent(this, MyService.class);
        stopService(serviceIntent);
    }
}


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.theitbulls.androidservicedemo">

    <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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"/>
    </application>

</manifest>

Service restart behavior

In its onStartCommand() method call, the service returns an int which defines its restart behavior in case the service gets terminated by the Android platform. You can use the constants, the most common options are described by the following table

Option Description
Service.START_STICKY Service is restarted if it gets terminated. Intent data passed to the onStartCommand method is null. Used for services which manages their own state and do not depend on the Intent data.
Service.START_NOT_STICKY Service is not restarted. Used for services which are periodically triggered anyway. The service is only restarted if the runtime has pending startService() calls since the service termination.
Service.START_REDELIVER_INTENT Similar to Service.START_STICKY but the original Intent is re-delivered to the onStartCommand method.

IntentServices

IntentService is used to perform a certain task in the background. Once done, the instance of IntentService terminates itself automatically. An example for its usage would be downloading certain resources from the internet.
The IntentService class offers the onHandleIntent() method which will be asynchronously called by the Android system.
An example of IntentService:

MyIntentService.java
package com.theitbulls.androidservicedemo;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.Nullable;

public class MyIntentService extends IntentService {
    public static final String ACTION_FOO = "com.theitbulls.androidservicedemo.action.FOO";
    public static final String ACTION_BAZ = "com.theitbulls.androidservicedemo.action.BAZ";

    public static final String EXTRA_PARAM1 = "com.theitbulls.androidservicedemo.extra.PARAM1";
    public static final String EXTRA_PARAM2 = "com.theitbulls.androidservicedemo.extra.PARAM2";

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();

            if (ACTION_FOO.equals(action)) {
                final String param1 = intent.getStringExtra(EXTRA_PARAM1);
                final String param2 = intent.getStringExtra(EXTRA_PARAM2);
                handleActionFoo(param1, param2);
            } else if (ACTION_BAZ.equals(action)) {
                final String param1 = intent.getStringExtra(EXTRA_PARAM1);
                final String param2 = intent.getStringExtra(EXTRA_PARAM2);
                handleActionBaz(param1, param2);
            }
        }
    }

    private void handleActionFoo(String param1, String param2) {
        Log.d("Service", "Foo Intent Service value for param1: " + param1 + ", param2: " + param2);
        Toast.makeText(getBaseContext(), "Foo Intent Service value for param1: " + param1 + ", param2: " + param2, Toast.LENGTH_SHORT).show();
    }

    private void handleActionBaz(String param1, String param2) {
        Log.d("Service", "Baz Intent Service value for param1: " + param1 + ", param2: " + param2);
        Toast.makeText(getBaseContext(), "Baz Intent Service value for param1: " + param1 + ", param2: " + param2, Toast.LENGTH_SHORT).show();
    }
}


Download: AndroidServiceDemo.zip

Create following MainActivity.java
package com.theitbulls.downloadfile;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatTextView;

public class MainActivity extends AppCompatActivity {
    private AppCompatTextView tvFileUrl;

    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Bundle bundle = intent.getExtras();
            if (bundle != null) {
                String fileUrl = bundle.getString(DownloadFileIntentService.URL);
                int resultCode = bundle.getInt(DownloadFileIntentService.RESULT);
                String msg = bundle.getString(DownloadFileIntentService.RESULT_MSG);
                if (resultCode == RESULT_OK) {
                    Log.d("FileLocation", fileUrl);
                    tvFileUrl.setText("File download completed\r\nFile Location:" + fileUrl);
                } else {
                    Log.d("FileError", msg);
                    Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);

        tvFileUrl = findViewById(R.id.tvFileUrl);
    }

    @Override
    protected void onResume() {
        super.onResume();
        registerReceiver(receiver, new IntentFilter(DownloadFileIntentService.DOWNLOAD_INTENT));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);
    }

    public void download(View view) {
        Intent downloadIntent = new Intent(this, DownloadFileIntentService.class);
        downloadIntent.putExtra(DownloadFileIntentService.URL, "https://storage.googleapis.com/itbulls_tarun/6_1570049668145.jpg");
        downloadIntent.putExtra(DownloadFileIntentService.FILE_NAME, "6_1570049668145.jpg");
        downloadIntent.setAction(DownloadFileIntentService.DOWNLOAD_INTENT);

        startService(downloadIntent);

        tvFileUrl.setText("File download start, please wait...");
    }
}


Create DownloadFileIntentService.java where we download a file
package com.theitbulls.downloadfile;

import android.app.Activity;
import android.app.IntentService;
import android.content.Intent;
import android.os.Environment;
import android.util.Log;

import androidx.annotation.Nullable;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class DownloadFileIntentService extends IntentService {
    public static final String DOWNLOAD_INTENT = "com.theitbulls.downloadfile";

    public static final String URL = "fileUrl";
    public static final String FILE_NAME = "filename";

    public static final String RESULT = "result";
    public static final String RESULT_MSG = "msg";

    public DownloadFileIntentService() {
        super("File Downloading Intent");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        if (intent == null) {
            return;
        }

        if (DOWNLOAD_INTENT.equals(intent.getAction())) {
            String fileUrl = intent.getStringExtra(URL);
            String fileName = intent.getStringExtra(FILE_NAME);

            File output = new File(Environment.getExternalStorageDirectory(), fileName);
            output.deleteOnExit();

            InputStream io = null;
            FileOutputStream fos = null;

            try {
                URL url = new URL(fileUrl);
                io = url.openConnection().getInputStream();

                byte[] fileData = readFileData(io);
                fos = new FileOutputStream(output.getPath());
                fos.write(fileData);
                fos.flush();

                sendResult(output.getPath(), "Success", Activity.RESULT_OK);
            } catch (Exception e) {
                Log.e("DownloadError: ", e.toString());
                sendResult(output.getPath(), e.toString(), Activity.RESULT_CANCELED);
            } finally {
                close(io, fos);
            }
        }
    }

    private void sendResult(String url, String msg, int resultCode) {
        Intent resultIntent = new Intent(DOWNLOAD_INTENT);
        resultIntent.putExtra(URL, url);
        resultIntent.putExtra(RESULT, resultCode);
        resultIntent.putExtra(RESULT_MSG, msg);
        sendBroadcast(resultIntent);
    }

    private byte[] readFileData(InputStream io) throws IOException {
        ByteArrayOutputStream ous = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int read = 0;
        while ((read = io.read(buffer)) != -1) {
            ous.write(buffer, 0, read);
        }
        return ous.toByteArray();
    }

    private void close(InputStream io, FileOutputStream fos) {
        try {
            io.close();
        } catch (Exception e) {
        }
        try {
            fos.close();
        } catch (Exception e) {
        }

    }
}


Make sure following permission define in AndroidManifest.xml

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Download complete working source code example below

Download: IntentServiceToDownloadFile.zip