ZOFTINO.COM android and web dev tutorials

Android Services

Android service is a component like activity but runs in the background. Android service by default runs in the main thread. Android service is used to perform tasks in the background without user interaction.

There are two types of services called started service and bound service. Started service runs in the background until it is stopped and doesn’t return results to the caller. Bound service allows callers to interact with it using the provided client-server interface. Bound service stops when all clients are unbind. A service can be both started and bound.

Service can be used in the same way how activity component is used, by sending intent to the system. System finds the matching service component and creates it. Service has callback methods which are called by the system when service is created and destroyed. Like other components, service components need to be defined in mainifest.xml file.

As mentioned before, services run in the main thread. If you have background task that takes long time to complete, it is better to create a thread within the service to perform long running tasks so that user operations are not blocked.

How to Create Service

Service component can be created by extending Service class or other sub classes of Service class. Required call back methods need to be implemented to provide a particular behavior or perform certain task. Most useful callback methods are onStartCommand(), onBind(), onCreate() and onDestroy().

To start a service, startService() or bindService() need to be called. Starting a service with startService() creates started service meaning it runs until stopSelf() or stopService() method is called. Starting a service with bindService() creates bound service and it returns client-server interface for clients to interact with the service. Callback methods onCreate() and onDestroy() are called for both types of services.

Callback method onStartCommand() is called before onCreate() for started services. Callback method onBind() is applicable for bound services and it should return IBinder which allows clients to interact with service.

Started Service

Started service is created by calling startService() passing intent from an activity or other components. System finds matching service using the intent information and first calls onCreate() if service is not instantiated before and then calls onStartCommand() method. After this, the service is in running state until stopSelf() or stopService() is called. System calls onDestroy() callback method after stopSelf() or stopService() method is invoked.

Method onStartCommand() returns integer to tell system how it needs to handle in case service is killed. Returning Service.START_STICKY tells the system to recreate the service and call onStartCommand() method. Returning Service.START_NOT_STICKY indicates the system not to recreate the service. Returning START_REDELIVER_INTENT indicate that system should recreate the service and call onStartCommand() with last intent delivered to the service.

System doesn’t instantiate new instance of the service for each start service request. If service is already instantiated, it just calls onStartCommand(). Method onStartCommand() is called for each start service request. Started service implemented by extending Service class handles multiple requests concurrently.

If you don’t want your service to handle requests concurrently, you need to create service by extending IntentService class. IntentService creates worker thread, request queue and handles one request at a time.

Bound Service

A service is called bound service when it is started by calling bindService() method. It is easy to implement bound service that can be used within the same process as shown in the example. If you need to build a service that should work from clients in different process than service, you need to use AIDL.

Method bindService() takes ServiceConnection object as a parameter. So you need to implement ServiceConnection interface in your client app. ServiceConnection has two callback methods onServiceConnected() and onServiceDisconnected(). After client calls bindService() from client app, system calls onServiceConnected() method of service connection object once connection to the service is established. IBinder object can be obtained in onServiceConnected() method and used for interactions with service.

Foreground Service

You can run services in the foreground. To run a service in foreground, service needs to be started as usual via intent. In service’s onCreate() method or in the service behavior code, startForeground() method needs to be called to make a service foreground service. Method startForeground() takes notification as parameter which will be displayed as notification on status bar. The notification can’t be removed or dismissed. Foreground service notification gets removed by force stopping, stopping the service or making it background service by calling stopForeground() method.

Complete Code

Started Service

package com.zoftino.content;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.widget.Toast;

public class LatestCouponService extends Service {

    private int notificationId = 22;
    private int serviceRegNum = 0;
    private Object obj = new Object();

    @Override
    public void onCreate() {
        Toast.makeText(this, "service request "+serviceRegNum, Toast.LENGTH_SHORT).show();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        synchronized (obj){
            serviceRegNum++;
        }
        Toast.makeText(this, "service request "+serviceRegNum+" "+startId, Toast.LENGTH_SHORT).show();

        boolean fg = intent.getExtras().getBoolean("foreground");
        if(fg){
            makeServiceForeground();
        }
        return START_NOT_STICKY;
    }

    private void makeServiceForeground(){
        Intent pi = new Intent(this, ServicesActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, pi, 0);

        Notification notification = new Notification.Builder(this)
                .setContentTitle("Coupons Service")
                .setContentText("Trying to get latest coupons")
                .setContentIntent(pendingIntent)
                .setTicker("Latest coupons")
                .build();
        startForeground(notificationId, notification);
        Toast.makeText(this, "foreground started ", Toast.LENGTH_SHORT).show();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onDestroy() {
        Toast.makeText(this, "service destroyed "+serviceRegNum, Toast.LENGTH_SHORT).show();
    }
}

Bound service

package com.zoftino.content;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;

public class CouponsBoundService extends Service {

    private final IBinder mBinder = new CouponServiceBinder();

    public class CouponServiceBinder extends Binder {
        CouponsBoundService getService() {
            return CouponsBoundService.this;
        }
    }

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

    public String getCoupon(){
        return "get 10% off";
    }
}

Activity

package com.zoftino.content;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

public class ServicesActivity  extends AppCompatActivity {

    private CouponsBoundService boundService = null;

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

        Intent i =  new Intent();
        i.setClass(this, CouponsBoundService.class);
        bindService(i, sconnection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onStop() {
        super.onStop();
        if (boundService != null) {
            unbindService(sconnection);
            boundService = null;
        }
    }
    public void createStartedService(View view){
        Intent i =  new Intent();
        i.setClass(this, LatestCouponService.class);
        startService(i);
    }

    public void stopService(View view){
        Intent i =  new Intent();
        i.setClass(this, LatestCouponService.class);
        stopService(i);
    }
    public void startForegroundService(View view){
        Intent i =  new Intent();
        i.setClass(this, LatestCouponService.class);
        i.putExtra("foreground", true);
        startService(i);
    }

    public void useBoundService(View view){
        if(boundService != null) {
            String latestCoupon = boundService.getCoupon();
            Toast.makeText(this, latestCoupon, Toast.LENGTH_SHORT).show();
        }
    }
    private ServiceConnection sconnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            CouponsBoundService.CouponServiceBinder binder = (CouponsBoundService.CouponServiceBinder) service;
            boundService = binder.getService();
        }
        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            boundService = null;
        }
    };

}

Activity layout xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_service"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.zoftino.content.ServicesActivity">

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="createStartedService"
        android:text="Started Service"></Button>
    <Button
        android:id="@+id/button9"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="startForegroundService"
        android:text="Foreground Service"></Button>

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="stopService"
        android:text="Stop Service"></Button>

    <Button
        android:id="@+id/button3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="useBoundService"
        android:text="Bound Service"></Button>

</LinearLayout>