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.
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.
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.