Android Tutorial: 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 article explains how to create a service and provides an example.

Overview

As already mentioned, a service is a component that performs 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. Previous article: Android Tutorial: Asynchronous Tasks

An android 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. Abound service acts as 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. 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 1.1.

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

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

Table 1.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 1.1), you can enter URLs of the websites whose contents you want to store on your device. Just type a URL in each line and click the FETCH WEB PAGES button to start the URL Service.

Figure 1.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 1.2.

Figure 1.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 1.1.

Listing 1.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 1.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 1.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 1.3. It extends IntentService and implements its onHandleIntent method.

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

Leave a Reply

Your email address will not be published. Required fields are marked *