Android之Service与activity互相交流
转自一个外文不错的博客,非常值得一看。http://www.vogella.com/articles/AndroidServices/article.html
Android Service Tutorial
Version 2.6
Copyright © 2011, 2012, 2013 Lars Vogel
22.05.2013
Revision History | |||
---|---|---|---|
Revision 0.1 | 07.03.2011 | Lars Vogel |
created |
Revision 0.2 - 2.6 | 08.03.2011 - 22.05.2013 | Lars Vogel |
bug fixed and enhancements |
Table of Contents
A service is a component which runs in the background, without direct interaction with the user. The Android platform provides and runs predefined system services and every Android application can use them, given the right permissions.
An Android application can, in addition to consuming the existing Android platform services, define and use new services.
The Android platform provides pre-defined services, usually exposed via a specific Manager class. Access to them can be gained via the getSystemService()
method.
Every Android application can define and start new services
If you use asynchronous processing in activities or fragments the corresponding threads are still connected to the life-cycle of the corresponding activity. The Android system may decide to terminate them at any point in time.
Services run with a higher priority than inactive or invisible activities and therefore it is less likely that the Android system terminates them.
Defining your own services allows you to design very responsive applications. You can fetch the application via a service and once the application is started by the user, it can present fresh data to the user.
A service needs to be declared in the AndroidManifest.xml
and the implementing class must extend the Service
class or one of its subclasses. The following code shows an example for a servicedeclaration and its implementation.
<service
android:name="MyService"
android:icon="@drawable/icon"
android:label="@string/service_name"
>
</service>
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//TODO do something useful
return Service.START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
//TODO for communication return IBinder implementation
return null;
}
}
A service runs by default in the same process as the application. in its own thread.
Therefore you need to use asynchronous processing in the service to perform resource intensive tasks in the background.
Services
which run in the process of the application are sometimes called local services
.
You can also specify that your Service
runs in a separate process via theandroid:process=":process_description"
attribute.
<service
android:name="WordService"
android:process=":my_process"
android:icon="@drawable/icon"
android:label="@string/service_name"
>
</service>
The colon prefix before the name tells Android that the Service
is private to its declaring application. If the colon is not used the Service
would be a global process and can be used by other Android applications.
Running a service in its own process will not block the application in case the service performs long running operations in its main thread. But as the services runs in its own process you need to use some interprocess communication (IPC) to communicate to your service from other parts.
Even if the service runs in its own process you need to use asynchronous processing to perform network access because Android does not allow network access in the main thread of a process.
Running a service in its own process gives it its own memory address space and a garbage collector of the virtual machine in this process does not affect the application process.
Application rarely need to run a service in its own process. Running a services in its own process make the communication of the other Android components and the service harder to implement.
If you want to make a service to other Android application available, they must run in their own process.
You can also extend the IntentService
class for your service implementation.
The IntentService
is used to perform a certain task in the background. Once done, the instance ofIntentService
terminate itself automatically. Examples for its usage would be to download a certain resources from the Internet.
The IntentService
class offers the onHandleIntent()
method which will be asynchronously called by the Android system.
For an introduction into BroadcastReceiver
please see Android Broadcast Receiver tutorial .
An Android component (service, receiver, activity) can start and trigger a service via thestartService(intent)
method. This method call starts the service if it is not running.
Intent service = new Intent(context, MyService.class);
context.startService(service);
If the service started the onCreate()
method is called.
Once the service is started the method call to start the service triggers startService(intent)
method in the service. It passes in the Intent
for the startService(intent)
call.
If startService(intent)
is called while the service is running, its onStartCommand()
is also called. Therefore your service needs to be prepared that onStartCommand()
can be called several times. This method is called in the main user interface thread therefore it cannot be called simultaneously from two different threads.
Alternatively to startService(intent)
you can also start a service via the bindService()
method call. This allows you to communicate directly with the service.
You stop a service via the stopService()
method. No matter how frequently you started the servicewith startService(intent)
a call to stopService()
stops it.
A service can stop itself by calling the stopSelf()
method.
If the activity wants to interact with the service it can use the bindService()
method to start theservice.
This method requires as parameter a ServiceConnection
object which allows to connect to theservice. In the service the onBind()
method is called. This method returns a IBinder
object to theServiceConnection
.
This IBinder
object can be used by the activity to communicate with the service.
Afterwards the binding was done the onStartCommand()
method is called with the Intent
data provided by the activity.
startService()
also allows you to provide a flag which determines the restart behavior of the services. Service.START_STICKY
is used for services which are explicit started or stopped. If these services are terminated by the Android system, they are restarted if sufficient resource are available again.
Services started with Service.START_NOT_STICKY
are not automatically restarted if terminated by the Android system.
As with activities the Android system may terminate the process of a service at any time to save resources. For this reason you cannot simple use a TimerTask
in the service to ensure that it is executed on a regular basis.
For correct scheduling of the Service
use the AlarmManager
class. The following code demonstrates how to do this.
Calendar cal = Calendar.getInstance();
Intent intent = new Intent(this, MyService.class);
PendingIntent pintent = PendingIntent.getService(this, 0, intent, 0);
AlarmManager alarm = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
// Start every 30 seconds
alarm.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), 30*1000, pintent);
There are several way for an activity to communicate with an service and vice versa. This section dicusses the different ways and gives recommendation which to use.
If the Service
is started in the same process as the activity, the activity can directly bind to the service. This is a relatively simple and efficient way to communication.
You can also use dynamically registered receivers for the communication. For example your activity can dynamically register a receiver and the service sends outs corresponding events.
To bind to a service which runs in a different process you need to use Inter Process Communication (IPC) as the data needs to be send between different processes. For this you need to create a AIDL file which looks similar to an Java interface but ends with the .aidl
file extension and is only allowed to extend other AIDL files.
This approach is required if your service should be provided to other applications, otherwise you should prefer a local service.
The service receives data from the starting Android component and can use this data.
If the service should be communicating back to the activity it can receive an object of type Messenger
via the Intent
data it receives from the activity. If the Messenger
is bound to a Handler
in theactivity the service
can send objects of type Message
to the activity.
A Messenger
is parcelable, which means it can be passed to another process and you can use this object to send Messages
to the Handler
in the activity.
Messenger
provides also the method getBinder()
which allows to pass a Messenger
to theactivity. The activity can therefore send Messages
to the service.
The following example demonstrates how to use the IntentService
class to download a file from the Internet. Once done the IntentService
will use an instance of the Messenger
class to inform the activity which started the service about the location of the downloaded file.
Create a new project called de.vogella.android.intentservice.download with a activity calledMainActivity.
Create a service "DownloadService" by creating the following class and the entry inAndroidManifest.xml
. Also add the permission to write to external storage and to access the Internet to the file.
package de.vogella.android.intentservice.download;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import android.app.Activity;
import android.app.IntentService;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
public class DownloadService extends IntentService {
private int result = Activity.RESULT_CANCELED;
public DownloadService() {
super("DownloadService");
}
// Will be called asynchronously be Android
@Override
protected void onHandleIntent(Intent intent) {
Uri data = intent.getData();
String urlPath = intent.getStringExtra("urlpath");
String fileName = data.getLastPathSegment();
File output = new File(Environment.getExternalStorageDirectory(),
fileName);
if (output.exists()) {
output.delete();
}
InputStream stream = null;
FileOutputStream fos = null;
try {
URL url = new URL(urlPath);
stream = url.openConnection().getInputStream();
InputStreamReader reader = new InputStreamReader(stream);
fos = new FileOutputStream(output.getPath());
int next = -1;
while ((next = reader.read()) != -1) {
fos.write(next);
}
// Sucessful finished
result = Activity.RESULT_OK;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Bundle extras = intent.getExtras();
if (extras != null) {
Messenger messenger = (Messenger) extras.get("MESSENGER");
Message msg = Message.obtain();
msg.arg1 = result;
msg.obj = output.getAbsolutePath();
try {
messenger.send(msg);
} catch (android.os.RemoteException e1) {
Log.w(getClass().getName(), "Exception sending message", e1);
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.vogella.android.intentservice.download"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<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>
<service android:name="DownloadService" >
</service>
</application>
</manifest>
Change the main.xml
layout to the following.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Button" />
</LinearLayout>
Change MainActivity
to the following.
package de.vogella.android.intentservice.download;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
private Handler handler = new Handler() {
public void handleMessage(Message message) {
Object path = message.obj;
if (message.arg1 == RESULT_OK && path != null) {
Toast.makeText(MainActivity.this,
"Downloaded" + path.toString(), Toast.LENGTH_LONG)
.show();
} else {
Toast.makeText(MainActivity.this, "Download failed.",
Toast.LENGTH_LONG).show();
}
};
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void onClick(View view) {
Intent intent = new Intent(this, DownloadService.class);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(handler);
intent.putExtra("MESSENGER", messenger);
intent.setData(Uri.parse("http://www.vogella.com/index.html"));
intent.putExtra("urlpath", "http://www.vogella.com/index.html");
startService(intent);
}
}
If you run your example and press the button, the download should be performed by the Service
and once done the activity should show a Toast with the file name.
This exercise demonstrates the creation and the consumption of a local service from an activity.
The service is started after the Android device boots and periodically fetches data. The activity binds itself to the service to access its data.
Create a new project called de.vogella.android.ownservice.local with an activity called MainActivity.
Create the following LocalWordService
class.
package de.vogella.android.ownservice.local;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
public class LocalWordService extends Service {
private final IBinder mBinder = new MyBinder();
private ArrayList<String> list = new ArrayList<String>();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Random random = new Random();
if (random.nextBoolean()) {
list.add("Linux");
}
if (random.nextBoolean()) {
list.add("Android");
}
if (random.nextBoolean()) {
list.add("iPhone");
}
if (random.nextBoolean()) {
list.add("Windows7");
}
if (list.size() >= 20) {
list.remove(0);
}
return Service.START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
public class MyBinder extends Binder {
LocalWordService getService() {
return LocalWordService.this;
}
}
public List<String> getWordList() {
return list;
}
}
Create the following two classes, which will be registered as BroadcastReceivers
.
package de.vogella.android.ownservice.local;
import java.util.Calendar;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class MyScheduleReceiver extends BroadcastReceiver {
// Restart service every 30 seconds
private static final long REPEAT_TIME = 1000 * 30;
@Override
public void onReceive(Context context, Intent intent) {
AlarmManager service = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(context, MyStartServiceReceiver.class);
PendingIntent pending = PendingIntent.getBroadcast(context, 0, i,
PendingIntent.FLAG_CANCEL_CURRENT);
Calendar cal = Calendar.getInstance();
// Start 30 seconds after boot completed
cal.add(Calendar.SECOND, 30);
//
// Fetch every 30 seconds
// InexactRepeating allows Android to optimize the energy consumption
service.setInexactRepeating(AlarmManager.RTC_WAKEUP,
cal.getTimeInMillis(), REPEAT_TIME, pending);
// service.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),
// REPEAT_TIME, pending);
}
}
package de.vogella.android.ownservice.local;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class MyStartServiceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, LocalWordService.class);
context.startService(service);
}
}
Register all components in your AndroidManifest.xml
file.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.vogella.android.ownservice.local"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/icon"
android:label="@string/app_name" >
<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>
<service
android:name=".LocalWordService"
android:icon="@drawable/icon"
android:label="@string/service_name" >
</service>
<receiver android:name="MyScheduleReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name="MyStartServiceReceiver" >
</receiver>
</application>
</manifest>
Change the layout file of the activity similar to the following example.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="showServiceData"
android:text="Button" >
</Button>
<ListView
android:id="@id/android:list"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
Change your activity class to the following code.
package de.vogella.android.ownservice.local;
import java.util.ArrayList;
import java.util.List;
import android.R;
import android.app.ListActivity;
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.view.View;
import android.widget.ArrayAdapter;
import android.widget.Toast;
public class MainActivity extends ListActivity {
private LocalWordService s;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
wordList = new ArrayList<String>();
adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, android.R.id.text1,
wordList);
setListAdapter(adapter);
}
@Override
protected void onResume() {
super.onPause();
bindService(new Intent(this, LocalWordService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onPause() {
super.onPause();
unbindService(mConnection);
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
s = ((LocalWordService.MyBinder) binder).getService();
Toast.makeText(MainActivity.this, "Connected", Toast.LENGTH_SHORT)
.show();
}
public void onServiceDisconnected(ComponentName className) {
s = null;
}
};
private ArrayAdapter<String> adapter;
private List<String> wordList;
public void showServiceData(View view) {
if (s != null) {
Toast.makeText(this, "Number of elements" + s.getWordList().size(),
Toast.LENGTH_SHORT).show();
wordList.clear();
wordList.addAll(s.getWordList());
adapter.notifyDataSetChanged();
}
}
}