Services - Android Application Development: A Beginner's Tutorial (2015)

Android Application Development: A Beginner's Tutorial (2015)

Chapter 24. Services

So far in this book, everything you have learned is related to activities. It is now time to present another Android component, the service. A service has no user interface and runs in the background. It is suitable for long-running operations. This chapter explains how to create a service and provides an example.

Overview

As already mentioned, a service is a component that perform a long running operation in the background. A service will continue to run even after the application that started it has been stopped. A service runs on the same process as the application in which the service is declared and in the application’s main thread. As such, if a service takes a long time to complete, it should run on a separate thread. The good thing is, running a service on a separate thread is easy if you extend a certain class in the Service API.

A service can take one of two forms. It can be started or bound. A service is started if another component starts it. It can run in the background indefinitely even after the component that started it is no longer in service or destroyed. A service is bound if an application component binds to it. A bound service acts like a server in a client-server relationship, taking requests from other application components and returning results. A service can also be started and bound.

In terms of accessibility, a service can be made private or public. A public service can be invoked by any application. A private service, on the other hand, can only be invoked by a component in the same application in which the service is declared.

The Service API

To create a service you must write a class that extends android.app.Service or its subclass android.app.IntentService. Subclassing IntentService is easier because it requires you to override fewer methods. However, extending Service allows you more control.

If you decide to subclass Service, you may need to override the callback methods in it. These methods are listed in Table 24.1.

Method

Description

onStartCommand

This method is called when another application component calls the service’s startService to start it.

onBind

This method is invoked when another application component calles the service’s bindService to bind with it.

onCreate

This method is invoked when the service is first created.

onDestroy

This method is invoked when the service is being destroyed.

Table 24.1: The Service class’s callback methods

If you extend IntentService, you have to override its abstract method onHandleIntent. Here is the signature of this method.

protected abstract void onHandleIntent(

android.content.Intent intent)

The implementation of onHandleIntent should contain code that needs to be executed by the service. Also note that onHandleIntent is always run on a separate worker thread.

Declaring A Service

A service must be declared in the manifest using the service element under <application>. The attributes that may appear in the service element are shown in Table 24.2.

Attribute

Description

enabled

Indicates whether the service should be enabled. The value is either true (default) or false.

exported

Accepts a value of true or false to indicate whether or not the service can be started or invoked from other applications.

icon

An icon representing this service.

isolatedProcess

Accepts a value of true or false indicating whether the service should be run as a separate process.

label

A label for this service.

name

The fully-qualified name of the service class.

permission

The name of a permission that that an entity must have in order to launch the service or bind to it.

process

The name of the process where the service is to run.

Table 24.2: The attributes of the service element

For example, here is the declaration of a service element that can be invoked by other applications.

<application>

...

<service android:name="com.example.MyService"

android:exported="true" />

</application>

A Service Example

This example is an Android application that lets you download web pages and store them for offline viewing when you have no Internet access.

There are two activities and one service. In the main activity (shown in Figure 24.1), you can enter URLs of the web sites whose contents you want to store in your device. Just type a URL in each line and click the FETCH WEB PAGES button to start the URL Service.

image

Figure 24.1: The Main activity

Click VIEW PAGES on the action bar to view the stored contents. You will see the second activity like that shown in Figure 24.2.

image

Figure 24.2: The View activity

The View activity’s view area consists of a Spinner and a WebView. The spinner contains encoded URLs that have been fetched. Select a URL to display the content in the WebView.

Now that you have an idea of what the app does, let’s take a look at the code.

As usual, you start from the manifest. It describes the application and is listed in Listing 24.1.

Listing 24.1: The manifest

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

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

package="com.example.urlservice" >

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

<uses-permission

android:name="android.permission.ACCESS_NETWORK_STATE" />

<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<activity

android:name=".MainActivity"

android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category

android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity

android:name=".ViewActivity"

android:parentActivityName=".MainActivity"

android:label="@string/title_activity_view" >

</activity>

<service

android:name=".URLService"

android:exported="true" />

</application>

</manifest>

As you can see the application element contains two activity elements and a service element. There are also two uses-permission elements to give the application to access the Internet. They are android.permission. INTERNET and android.permission.ACCESS_NETWORK_STATE.

Listing 24.1: The main activity class

package com.example.urlservice;

import android.content.Intent;

import android.os.StrictMode;

import android.support.v7.app.ActionBarActivity;

import android.os.Bundle;

import android.util.Log;

import android.view.Menu;

import android.view.MenuItem;

import android.view.View;

import android.widget.EditText;

public class MainActivity extends ActionBarActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

StrictMode.ThreadPolicy policy = new

StrictMode.ThreadPolicy.Builder().permitAll().build();

StrictMode.setThreadPolicy(policy);

}

@Override

public boolean onCreateOptionsMenu(Menu menu) {

getMenuInflater().inflate(R.menu.menu_main, menu);

return true;

}

@Override

public boolean onOptionsItemSelected(MenuItem item) {

int id = item.getItemId();

if (id == R.id.action_view) {

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

startActivity(intent);

return true;

}

return super.onOptionsItemSelected(item);

}

public void fetchWebPages(View view) {

EditText editText = (EditText) findViewById(R.id.urlsEditText);

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

intent.putExtra("urls", editText.getText().toString());

startService(intent);

}

}

Listing 24.2: The view activity class

package com.example.urlservice;

import android.os.Bundle;

import android.support.v7.app.ActionBarActivity;

import android.view.Menu;

import android.view.MenuItem;

import android.view.View;

import android.webkit.WebView;

import android.widget.AdapterView;

import android.widget.ArrayAdapter;

import android.widget.Spinner;

import java.io.BufferedReader;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileReader;

import java.io.IOException;

public class ViewActivity extends ActionBarActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_view);

Spinner spinner = (Spinner) findViewById(R.id.spinner);

File saveDir = getFilesDir();

if (saveDir.exists()) {

File dir = new File(saveDir, "URLService");

dir = saveDir;

if (dir.exists()) {

String[] files = dir.list();

ArrayAdapter<String> dataAdapter =

new ArrayAdapter<String>(this,

android.R.layout.simple_spinner_item, files);

dataAdapter.setDropDownViewResource(

android.R.layout.simple_spinner_dropdown_item);

spinner.setAdapter(dataAdapter);

spinner.setOnItemSelectedListener(

new AdapterView.OnItemSelectedListener() {

@Override

public void onItemSelected(AdapterView<?>

adapterView, View view, int pos,

long id) {

//open file

Object itemAtPosition = adapterView

.getItemAtPosition(pos);

File file = new File(getFilesDir(),

itemAtPosition.toString());

FileReader fileReader = null;

BufferedReader bufferedReader = null;

try {

fileReader = new FileReader(file);

bufferedReader =

new BufferedReader(fileReader);

StringBuilder sb = new StringBuilder();

String line = bufferedReader.readLine();

while (line != null) {

sb.append(line);

line = bufferedReader.readLine();

}

WebView webView = (WebView)

findViewById(R.id.webview);

webView.loadData(sb.toString(),

"text/html", "utf-8");

} catch (FileNotFoundException e) {

} catch (IOException e) {

}

}

@Override

public void onNothingSelected(AdapterView<?>

adapterView) {

}

});

}

}

}

@Override

public boolean onCreateOptionsMenu(Menu menu) {

getMenuInflater().inflate(R.menu.menu_view, menu);

return true;

}

@Override

public boolean onOptionsItemSelected(MenuItem item) {

int id = item.getItemId();

return super.onOptionsItemSelected(item);

}

}

The most important piece of the application, the service class, is shown in Listing 24.3. It extends IntentService and implements its onHandleIntent method.

Listing 24.3: The service class

package com.example.urlservice;

import android.app.IntentService;

import android.content.Intent;

import java.io.BufferedReader;

import java.io.File;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.MalformedURLException;

import java.net.URL;

import java.util.StringTokenizer;

public class URLService extends IntentService {

public URLService() {

super("URLService");

}

@Override

protected void onHandleIntent(Intent intent) {

String urls = intent.getStringExtra("urls");

if (urls == null) {

return;

}

StringTokenizer tokenizer = new StringTokenizer(urls);

int tokenCount = tokenizer.countTokens();

int index = 0;

String[] targets = new String[tokenCount];

while (tokenizer.hasMoreTokens()) {

targets[index++] = tokenizer.nextToken();

}

File saveDir = getFilesDir();

fetchPagesAndSave(saveDir, targets);

}

private void fetchPagesAndSave(File saveDir, String[] targets) {

for (String target : targets) {

URL url = null;

try {

url = new URL(target);

} catch (MalformedURLException e) {

e.printStackTrace();

}

String fileName = target.replaceAll("/", "-")

.replaceAll(":", "-");

File file = new File(saveDir, fileName);

PrintWriter writer = null;

BufferedReader reader = null;

try {

writer = new PrintWriter(file);

reader = new BufferedReader(

new InputStreamReader(url.openStream()));

String line;

while ((line = reader.readLine()) != null) {

writer.write(line);

}

} catch (Exception e) {

} finally {

if (writer != null) {

try {

writer.close();

} catch (Exception e) {

}

}

if (reader != null) {

try {

reader.close();

} catch (Exception e) {

}

}

}

}

}

}

The onHandleIntent method receives an array of URLs and uses a StringTokenizer to extract each URL from the array. Each URL is used to populate a string array named targets, which is then passed to the fetchPagesAndSave method. This method employs a java.net.URL to send an HTTP request for each target and saves its content in internal storage.

Summary

A service is an application component that runs in the background. Despite the fact that it runs in the background, a service is not a process and does not run on a separate thread. Instead, a service runs on the main thread of the application that invoked the service.

You can write a service by extending android.app.Service or android.app.IntentService.