58. Implementing an Android Started Service – A Worked Example

The previous chapter covered a considerable amount of information relating to Android services and, at this point, the concept of services may seem somewhat overwhelming. In order to reinforce the information in the previous chapter, this chapter will work through an Android Studio tutorial intended to gradually introduce the concepts of started service implementation.

Within this chapter, a sample application will be created and used as the basis for implementing an Android service. In the first instance, the service will be created using the IntentService class. This example will subsequently be extended to demonstrate the use of the Service class. Finally, the steps involved in performing tasks within a separate thread when using the Service class will be implemented. Having covered started services in this chapter, the next chapter, entitled “Android Local Bound Services – A Worked Example”, will focus on the implementation of bound services and client-service communication.

58.1 Creating the Example Project

Select the Create New Project quick start option from the welcome screen and, within the resulting new project dialog, choose the Empty Activity template before clicking on the Next button.

Enter ServiceExample into the Name field and specify com.ebookfrenzy.serviceexample as the package name. Before clicking on the Finish button, change the Minimum API level setting to API 26: Android 8.0 (Oreo) and the Language menu to Java. Modify the project to support view binding as outlined in section 11.8 Migrating a Project to View Binding.

58.2 Designing the User Interface

Locate and load the activity_main.xml file in the Project tool window (app -> res -> layout -> activity_main.xml). Right-click on the “Hello World!” TextView and select the Convert view... option from the resulting menu. In the conversion dialog, select the Button view before clicking on Apply. Select the new button, change the text to read “Start Service” and extract the string to a resource named start_service. With the new Button still selected, locate the onClick property in the Attributes panel and assign to it a method named buttonClick.

58.3 Creating the Service Class

Before writing any code, the first step is to add a new class to the project to contain the service. The first type of service to be demonstrated in this tutorial is to be based on the JobIntentService class. As outlined in the preceding chapter (“An Overview of Android Services”), the purpose of the JobIntentService class is to provide the developer with a convenient mechanism for creating services that perform tasks asynchronously within a separate thread from the calling application.

Add a new class to the project by right-clicking on the com.ebookfrenzy.serviceexample package name located under app -> java in the Project tool window and selecting the New -> Java Class menu option. Within the resulting dialog, name the new class MyJobIntentService. Finally, click on the OK button to create the new class.

Review the new MyJobIntentService.java file in the Android Studio editor where it should read as follows:

package com.ebookfrenzy.serviceexample;

 

public class MyJobIntentService {

}

The class needs to be modified so that it subclasses the JobIntentService class. When subclassing JobIntentService, the class must override the onHandleWork() method. Modify the code in the MyJobIntentService.java file, therefore, so that it reads as follows:

package com.ebookfrenzy.serviceexample;

 

import android.content.Intent;

import androidx.core.app.JobIntentService;

import androidx.annotation.NonNull;

 

public class MyJobIntentService extends JobIntentService {

 

    @Override

    protected void onHandleWork(@NonNull Intent intent) {

 

    }

}

All that remains at this point is to implement some code within the onHandleWork() method so that the service actually does something when invoked. Ordinarily this would involve performing a task that takes some time to complete such as downloading a large file or playing audio. For the purposes of this example, however, the handler will simply sleep for 30 seconds, outputting a message to the Android Studio Logcat panel every 10 seconds:

package com.ebookfrenzy.serviceexample;

 

.

.

import android.util.Log;

 

public class MyJobIntentService extends JobIntentService {

 

    private static final String TAG = "ServiceExample";

 

    @Override

    protected void onHandleWork(@NonNull Intent intent) {

       Log.i(TAG, "Job Service started");

 

        int i = 0;

        while (i <= 3) {

 

            try {

                Thread.sleep(10000);

                i++;

            } catch (Exception e) {

            }

            Log.i(TAG, "Service running");

        }

    }

}

58.4 Adding the Service to the Manifest File

Before a service can be invoked, it must first be added to the manifest file of the application to which it belongs. At a minimum, this involves adding a <service> element together with the class name of the service and the BIND_JOB_SERVICE permission request.

Double-click on the AndroidManifest.xml file (app -> manifests) for the current project to load it into the editor and modify the XML to add the service element as shown in the following listing:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.example.serviceexample" >

    <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/Theme.ServiceExample" >

        <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=".MyJobIntentService"

            android:permission="android.permission.BIND_JOB_SERVICE" />

    </application>

</manifest>

58.5 Starting the Service

Now that the service has been implemented and declared in the manifest file, the next step is to add code to start the service when the button is clicked. Locate and load the MainActivity.java file into the editor and implement the buttonClick() method to add the code to start the service:

package com.ebookfrenzy.serviceexample;

.

.

import android.content.Intent;

 

import static androidx.core.app.JobIntentService.enqueueWork;

 

public class MainActivity extends AppCompatActivity {

 

    static final int SERVICE_ID = 1001;

.

.

   public void buttonClick(View view) {

        Intent intent = new Intent();

        enqueueWork(this, MyJobIntentService.class, SERVICE_ID, intent);

    }

}

The code declares a service id (this can be any integer value) which is used to identify the service. Next, the buttonClick() method is implemented to call enqueueWork() method of the JobIntentService class to submit the service for execution.

58.6 Testing the IntentService Example

The example IntentService based service is now complete and ready to be tested. Since the messages displayed by the service will appear in the Logcat panel, it is important that this is configured in the Android Studio environment.

Begin by displaying the Logcat tool window before clicking on the menu in the upper right-hand corner of the panel (which will probably currently read Show only selected application). From this menu, select the Edit Filter Configuration menu option.

In the Create New Logcat Filter dialog name the filter ServiceExample and, in the Log Tag field, enter the TAG value declared in MyIntentService.java (in the above code example this was ServiceExample).

When the changes are complete, click on the OK button to create the filter and dismiss the dialog. The newly created filter should now be selected in the Android tool window.

With the filter configured, run the application on a physical device or AVD emulator session click on the Start Service button. Note that the “Job Service Started” message appears in the Logcat panel. Note that it may be necessary to change the filter menu setting back to ServiceExample after the application has launched:

2021-01-14 14:20:05.630 7193-7265/com.example.serviceexample I/ServiceExample: Job Service started

Over the next 30 seconds, the Service Running message will appear at 10 second intervals. It is important to note that this execution is taking place on a separate thread from the main thread, leaving the app free to respond to the user and perform other tasks.

58.7 Using the Service Class

While the JobIntentService class allows a service to be implemented with minimal coding, there are situations where the flexibility and synchronous nature of the Service class will be required. As will become evident in this chapter, this involves some additional programming work to implement.

In order to avoid introducing too many concepts at once, and as a demonstration of the risks inherent in performing time-consuming service tasks in the same thread as the calling application, the example service created here will not run the service task within a new thread, instead relying on the main thread of the application. Creation and management of a new thread within a service will be covered in the next phase of the tutorial.

58.8 Creating the New Service

For the purposes of this example, a new class will be added to the project that will subclass from the Service class. Right-click, therefore, on the package name listed under app -> java in the Project tool window and select the New -> Service -> Service menu option. Create a new class named MyService with both the Exported and Enabled options selected.

The minimal requirement in order to create an operational service is to implement the onStartCommand() callback method which will be called when the service is starting up. In addition, the onBind() method must return a null value to indicate to the Android system that this is not a bound service. For the purposes of this example, the onStartCommand() method will loop 3 times sleeping for 10 seconds on each loop iteration. For the sake of completeness, stub versions of the onCreate() and onDestroy() methods will also be implemented in the new MyService.java file as follows:

package com.ebookfrenzy.serviceexample;

 

import android.app.Service;

import android.content.Intent;

import android.os.IBinder;

import android.util.Log;

 

public class MyService extends Service {

 

    public MyService() {

    }

 

    private static final String TAG =

            "ServiceExample";

 

    @Override

    public void onCreate() {

        Log.i(TAG, "Service onCreate");

    }

 

    @Override

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

 

        Log.i(TAG, "Service onStartCommand " + startId);

 

        int i = 0;

        while (i <= 3) {

 

            try {

                Thread.sleep(10000);

                i++;

            } catch (Exception e) {

            }

            Log.i(TAG, "Service running");

        }

        return Service.START_STICKY;

    }

 

    @Override

    public IBinder onBind(Intent arg0) {

        Log.i(TAG, "Service onBind");

        return null;

    }

 

    @Override

    public void onDestroy() {

        Log.i(TAG, "Service onDestroy");

    }

}

With the service implemented, load the AndroidManifest.xml file into the editor and verify that Android Studio has added an appropriate entry for the new service which should read as follows:

<service

 android:name=".MyService"

            android:enabled="true"

            android:exported="true" >

</service>

58.9 Launching the Service

Edit the MainActivity.java file and modify the buttonClick() method to launch the MyService service:

public void buttonClick(View view)

{

    Intent intent = new Intent(this, MyService.class);

    startService(intent);

}

All that the buttonClick() method does is create an intent object for the new service and then start it running.

58.10 Running the Application

Run the application and, once loaded, touch the Start Service button. Within the Logcat tool window (using the ServiceExample filter created previously) the log messages will appear indicating that the buttonClick() method was called and that the loop in the onStartCommand() method is executing.

Before the final loop message appears, attempt to touch the Start Service button a second time. Note that the button is unresponsive. After approximately 20 seconds, the system may display a warning dialog containing the message “ServiceExample isn’t responding”. The reason for this is that the main thread of the application is currently being held up by the service while it performs the looping task. Not only does this prevent the application from responding to the user, but also to the system, which eventually assumes that the application has locked up in some way.

Clearly, the code for the service needs to be modified to perform tasks in a separate thread from the main thread.

58.11 Adding Threading to the Service

As outlined in “A Basic Overview of Java Threads, Handlers and Executors”, when an Android application is first started, the runtime system creates a single thread in which all application components will run by default. This thread is generally referred to as the main thread. The primary role of the main thread is to handle the user interface in terms of event handling and interaction with views in the user interface. Any additional components that are started within the application will, by default, also run on the main thread.

As demonstrated in the previous section, any component that undertakes a time consuming operation on the main thread will cause the application to become unresponsive until that task is complete. It is not surprising, therefore, that Android provides an API that allows applications to create and use additional threads. Any tasks performed in a separate thread from the main thread are essentially performed in the background. Such threads are typically referred to as background or worker threads.

Modify the onStartCommand() method in the MyService.java file to execute the task in a separate thread as follows:

@Override

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

 

    Log.i(TAG, "Service onStartCommand " + startId);

 

    Runnable runnable = () -> {

        int i = 0;

        while (i <= 3) {

 

            try {

                Thread.sleep(10000);

                i++;

            } catch (Exception e) {

            }

            Log.i(TAG, "Service running " + startId);

        }

    };

    Thread serviceThread = new Thread(runnable);

    serviceThread.start();

    return Service.START_STICKY;

}

When the application is now run, it should be possible to touch the Start Service button multiple times. When doing so, the Logcat output should indicate more than one task running simultaneously:

I/ServiceExample: Service onStartCommand 1

I/ServiceExample: Service onStartCommand 2

I/ServiceExample: Service onStartCommand 3

I/ServiceExample: Service running 1

I/ServiceExample: Service running 2

I/ServiceExample: Service running 3

With the service now handling requests outside of the main thread, the application remains responsive to both the user and the Android system.

58.12 Summary

This chapter has worked through an example implementation of an Android started service using the IntentService and Service classes. The example also demonstrated the use of asynchronous tasks within a service to avoid making the main thread of the application unresponsive.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.109.30