Android四大組件之Service
Android支持服務的概念,服務是在後臺運行的組件,沒有用戶界面,Android服務可用有與活動獨立的生命週期。Android支持兩種類型的服務:
本地服務:
本地服務只能由承載該服務的應用程序訪問,無法供在設備上運行的其他應用程序訪問。客戶端調用Context.startService()啓動該服務。
遠程服務:
遠程服務除了可從承載服務的應用程序訪問,還可以從其他應用程序訪問。遠程服務使用AIDL向客戶端定義。服務支持onBind()方法,客戶端通過Context.bindService()進行調用。
1)本地服務
1.1、startService
本地服務可由Context.startService()啓動,啓動後這些服務將持續運行,直到客戶端調用Context.stopService()或服務自己調用stopSelf()。
注意:如果調用Context.startService()時還未創建服務,系統將實例化服務並調用服務的onStartCommand()方法。如果在調用Context.startService()時服務已經啓動,那麼不會再創建一個實例,而是重新調用正在運行的服務的onStartCommand()方法。
Demo:我們在MainActivity中新建兩個兩個Button,一個用於啓動服務,另外一個用於停止服務。建立一個MyService類繼承於Service,當收到服務的時候在通知欄彈出通知,一直到我們的服務退出才清除通知,同時收到啓動服務的消息時我們建立一個線程sleep 10秒。當我們退出MainActivity的時候停止服務,同時清除通知欄的通知。
MainActivity.xml:就兩個Button用於啓動和停止服務。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="startService" >
</Button>
<Button
android:id="@+id/btnStop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="stopService"
android:layout_below="@id/btnStart">
</Button>
</RelativeLayout>
然後是MainActivity,用於響應Button的單擊事件:
public class MainActivity extends Activity implements OnClickListener{
private static final String TAG = "MainActivity";
private int counter = 1;
private Button btnStart, btnStop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnStart = (Button)this.findViewById(R.id.btnStart);
btnStop = (Button)this.findViewById(R.id.btnStop);
btnStart.setOnClickListener(this);
btnStop.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.v(TAG, "id:"+v.getId() + "btn:"+R.id.btnStart);
switch (v.getId()) {
case R.id.btnStart:
Log.v(TAG, "Starting Service...counter=" + counter);
Intent intent = new Intent(MainActivity.this, MyService.class);
intent.putExtra("counter", counter);
startService(intent);
break;
case R.id.btnStop:
Log.v(TAG, "Stopping Service...");
if( stopService(new Intent(MainActivity.this, MyService.class)) ) {
Log.v(TAG, "stopService successful");
} else {
Log.v(TAG, "stopService failed");
}
break;
default:
break;
}
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
stopService(new Intent(MainActivity.this, MyService.class));
super.onDestroy();
}
}
最後是我們的MyService,當收到服務的時候,在通知欄彈出通知,並啓動一個sleep 10秒的線程。
public class MyService extends Service {
private static final String TAG = "MyService";
private NotificationManager notificationMgr;
private ThreadGroup threadGroup = new ThreadGroup("ServiceWorkder");
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
Log.v(TAG, "in onCreate");
notificationMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
Notification notification = new Notification(R.drawable.ic_launcher, "Service is running", System.currentTimeMillis());
notification.flags = Notification.FLAG_NO_CLEAR;
PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
notification.setLatestEventInfo(this, TAG, "Service is running", intent);
notificationMgr.notify(0, notification);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
super.onStartCommand(intent, flags, startId);
int counter = intent.getExtras().getInt("counter");
Log.v(TAG, "in onStartCommand, counter = "+counter+",startId = "+startId);
new Thread(threadGroup, new ServiceWorker(counter)).start();
return START_STICKY;
}
class ServiceWorker implements Runnable {
private int counter = -1;
public ServiceWorker(int counter) {
this.counter = counter;
}
public void run() {
final String TAG = "ServiceWorker" + Thread.currentThread().getId();
try {
Log.v(TAG, "Sleeping for 10 seconds.counter="+counter);
Thread.sleep(10000);
Log.v(TAG, "...waking up");
} catch (Exception e) {
// TODO: handle exception
Log.v(TAG, "...sleep interrupt");
}
}
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
Log.v(TAG, "in onDestroy. Interrupt threads and canceling notifications");
threadGroup.interrupt();
notificationMgr.cancelAll();
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}
最後不要忘了在AndroidManifest.xml中聲明我們的Service:
<service
android:name=".MyService">
</service>
最後通知欄運行效果如下:
1.2、bindService
本地服務也可由Context.bindService()啓動,這樣調用者和服務綁在一起,調用者一旦退出,服務也就終止了。客戶端建立一個與Service連接,使用此連接與Service通信,通過Context.bindService()綁定服務,使用Context.unbindService()關閉服務。多個客戶端可以綁定同一個服務,如果Service未啓動,bindService()可以啓動服務。
注意:上面的startService()和bindService()是完全獨立的兩種模式,你可以綁定一個已經通過startService()啓動的服務。例如:一個後臺播放音樂的服務可以通過startService()啓動播放,然後activity可以通過調用bindService()方法建立於Service的聯繫,執行切換歌曲等操作。這種情況下:stopService()不會停止服務,直到最後一個unbindService()調用。
當創建一個能夠提供綁定功能的服務時,我們必須提供一個IBinder對象,客戶端能夠使用這個對象與服務通信,Android中有三種方式:
(1)擴展Binder類
一般用於服務和Activity屬於同一個進程的情況。類似上面的startService()我們在MainActivity中建立兩個Button,一個用於bindService,另外一個unbindService()。在獲得Service的IBinder接口之後就可以調用Service的內部方法了。
public class MainActivity extends Activity implements OnClickListener{
private static final String TAG = "MainActivity";
private boolean isBindFlag = false;
private Button btnBind, btnUnbind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnBind = (Button)this.findViewById(R.id.btnBind);
btnUnbind = (Button)this.findViewById(R.id.btnUnbind);
btnBind.setOnClickListener(this);
btnUnbind.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.btnBind:
Intent intent2 = new Intent(MainActivity.this, MyBindService.class);
bindService(intent2, serviceConnection, Context.BIND_AUTO_CREATE);
break;
case R.id.btnUnbind:
unbindService(serviceConnection);
break;
default:
break;
}
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
isBindFlag = false;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
MyBindService.MyBinder binder = (MyBinder)service;
MyBindService bndService = binder.getService();
bndService.myMethod();
isBindFlag = true;
}
};
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
if(isBindFlag == true) {
unbindService(serviceConnection);
}
super.onDestroy();
}
}
這裏當綁定到MyBindService之後,就可以通過bndService實例調用其方法myMethod()了。下面MyBindService比較簡單就是繼承於Service,並實現其onBind()接口,返回一個MyBinder實例,客戶端拿到這個MyBinder之後可以通過它獲取到MyBindService實例,然後調用其提供的myMethod()方法了。
public class MyBindService extends Service {
private static final String TAG = "MyBindService";
public void myMethod() {
Log.i(TAG, "myBindService->myMethod()");
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return myBinder;
}
public class MyBinder extends Binder {
public MyBindService getService() {
return MyBindService.this;
}
}
private MyBinder myBinder = new MyBinder();
}
同理最後我們也需要在AndroidManifest.xml中聲明我們的服務。
(2)使用Messenger
(3)Remote Service 也就是我們下面要說的AIDL服務了。
2)AIDL服務
2.1)構建遠程服務
上面介紹的各種服務只能由承載它的應用程序使用,如果想構建可由其他進程通過RPC使用的服務,需要使用IDL來定義向客戶端公開的接口,在Android中這個IDL就稱爲AIDL。構建遠程服務的一般步驟爲:
1、編寫一個AIDL文件用來向客戶端定義接口。AIDL文件使用Java語法擴展名爲.aidl,其內部使用的包名和Android項目使用的包名相同。
首先在項目的src目錄下新建一個IStudentInfo.aidl文件,在AIDL文件中定義服務接口。提供了double getScore(String name)接口,根據給定的String類型的學生姓名,返回一個double類型的分數。
package com.myAndroid.aidlService;
interface IStudentInfoService {
double getScore(String name);
}
2、將AIDL文件添加到Eclipse項目的src目錄下,Android Eclipse插件將調用AIDL編譯器從AIDL文件生成Java接口。
生成的java接口文件位於gen/com.myAndroid.aidlService下,名爲IStudengInfoService.java:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: C:\\Documents and Settings\\Administrator\\workspace\\Android使用AIDL創建Service\\src\\com\\myAndroid\\aidlService\\IStudentInfoService.aidl
*/
package com.myAndroid.aidlService;
public interface IStudentInfoService extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.myAndroid.aidlService.IStudentInfoService
{
private static final java.lang.String DESCRIPTOR = "com.myAndroid.aidlService.IStudentInfoService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.myAndroid.aidlService.IStudentInfoService interface,
* generating a proxy if needed.
*/
public static com.myAndroid.aidlService.IStudentInfoService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.myAndroid.aidlService.IStudentInfoService))) {
return ((com.myAndroid.aidlService.IStudentInfoService)iin);
}
return new com.myAndroid.aidlService.IStudentInfoService.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getScore:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
double _result = this.getScore(_arg0);
reply.writeNoException();
reply.writeDouble(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.myAndroid.aidlService.IStudentInfoService
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public double getScore(java.lang.String name) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
double _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
mRemote.transact(Stub.TRANSACTION_getScore, _data, _reply, 0);
_reply.readException();
_result = _reply.readDouble();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getScore = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public double getScore(java.lang.String name) throws android.os.RemoteException;
}
對於所生成的類,注意幾點:在IStudentInfoService中有一個名爲IStudentInfoService的接口,實現了IInterface接口。
內部有一個名爲Stub的static final 抽象類擴展了android.os.Binder並實現了IStudentInfoService接口。
內部還有一個名爲Proxy的static類,實現了IStudentInfoService接口,它是Stub類的代理。
3、實現一個服務並從onBind()方法返回所生成的接口。
要實現服務的接口,需要編寫一個類來擴展android.app.Service並實現IStudentInfoService接口,這個類需要提供onBind()方法將服務向客戶端公開。
package com.myAndroid.aidlService;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class StudentInfoService extends Service {
private static final String TAG = "StudentInfoService";
public class StudentInfoServiceImpl extends IStudentInfoService.Stub {
@Override
public double getScore(String name) throws RemoteException {
// TODO Auto-generated method stub
Log.v(TAG, "getScore() called for "+ name);
return 85.0;
}
}
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
Log.v(TAG, "onBind called");
return new StudentInfoServiceImpl();
}
}
從AIDL文件生成的Stub類是抽象類,且實現了IStudentInfoService接口。在我們的服務實現中內部類StudentInfoServiceIml擴展了Stub類,實現了getScore()方法,充當着遠程服務具體實現,當客戶端bind到服務時,返回一個此類的實例。
4、最後將服務配置添加到AndroidManifest.xml文件中
這次我們需要用一個Intent過濾器來公開服務。
<service android:name="StudentInfoService">
<intent-filter >
<action android:name="com.myAndroid.aidlService.IStudentInfoService"/>
</intent-filter>
</service>
2.2)調用遠程服務
當客戶端與服務通信時,它們之間需要一個協議或契約,在Android中這個協議就是AIDL文件。所以客戶端調用服務的第一步就是獲取服務的AIDL文件並將其複製到客戶端項目中,同理AIDL編譯器會創建一個接口定義公開文件,這個文件與服務器中的文件一樣。
我們創建一個新的Android項目名爲 StudentInfoClient,包名爲com.myAndroid.studentInfoClient。然後在這個項目下新建一個Java包名爲
com.myAndroid.aidlService,並將IStudentInfoService.aidl文件拷貝到當前包下面。
最後我們在MainActivity中通過bindService()獲取服務的引用,然後調用其getScore()方法,即可跟服務端通信。下面給出客戶端源碼:
package com.myAndroid.studentInfoClient;
import com.myAndroid.aidlService.IStudengInfoService;
import android.os.Bundle;
import android.os.IBinder;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.support.v4.widget.SimpleCursorAdapter.ViewBinder;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
import android.widget.ToggleButton;
public class MainActivity extends Activity implements View.OnClickListener {
private ToggleButton toggleButton;
private Button callButton;
private IStudengInfoService myService = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toggleButton = (ToggleButton)this.findViewById(R.id.bindBtn);
callButton = (Button)this.findViewById(R.id.callBtn);
toggleButton.setOnClickListener(this);
callButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.bindBtn:
if(((ToggleButton)v).isChecked()) {
bindService(new Intent(IStudengInfoService.class.getName()), conn, Context.BIND_AUTO_CREATE);
} else {
unbindService(conn);
callButton.setEnabled(false);
}
break;
case R.id.callBtn:
callService();
break;
default:
break;
}
}
private void callService() {
try {
double val = myService.getScore("Lucy");
Toast.makeText(MainActivity.this, "Value from service is "+ val, Toast.LENGTH_LONG).show();
} catch (Exception e) {
// TODO: handle exception
}
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
myService = null;
toggleButton.setChecked(false);
callButton.setEnabled(false);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
myService = IStudengInfoService.Stub.asInterface(service);
toggleButton.setChecked(true);
callButton.setEnabled(true);
}
};
protected void onDestroy() {
if(callButton.isEnabled()) {
unbindService(conn);
}
super.onDestroy();
}
}
代碼中對於AIDL服務我們需要提供ServiceConnection接口的實現,此接口定義兩個方法:一個供系統建立服務連接時調用,另一個在銷燬服務連接時調用。當建立服務時回調onServiceConnected()方法,我們根據參數service調用IStudengInfoService.Stub.asInterface()獲得服務端的代理,然後調用其相應方法getScore()。
注意:bindService()是異步調用,因爲進程或服務可能沒有運行,但是我們不能在主線程上等待服務啓動。當從服務解除綁定時我們不會調用onServiceDisConnected(),只有在服務崩潰時纔會調用它。如果調用了它,我們可能需要重寫調用bindService()。
2.3)向服務傳遞複雜類型
注意:AIDL對非原語的支持:
1、AIDL支持String和CharSequence。
2、AIDL支持傳遞其他AIDL接口,但你引用的每個AIDL接口都需要一個import語句。
3、AIDL支持傳遞實現android.os.Parcelable接口的複雜類型。需要在AIDL文件中包含針對這些類型的Import語句。
4、AIDL支持java.util.List和java.util.Map,但是具有一些限制,集合中的項允許數據類型包括Java原語、String、CharSequence和android.os.Parcelable。無需爲List和Map提供import語句,但是需要爲Parcelable提供。
5、除字符串外。非原語類型需要一個方向指示符。方向指示符包括in、out和inout。in表示由客戶端設置,out表示值由服務設置,inout表示客戶端和服務都設置了該值。
Parcelable接口告訴Android運行時在封送marshalling和解unmarshalling過程中如何序列化和反序列化對象。
public class Person implements Parcelable {
private int age;
private String name;
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
public Person createFromParcel(Parcel in) {
return new Person(in);
}
public Person[] newArray(int size) {
return new Person[size];
}
};
public Person() {
}
private Person(Parcel in) {
readFromParcel(in);
}
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
// TODO Auto-generated method stub
dest.writeInt(age);
dest.writeString(name);
}
public void readFromParcel(Parcel in) {
age = in.readInt();
name = in.readString();
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Parcelable接口定義在封送/解封送過程中混合和分解對象的契約,Parcelable接口的底層是Parcel容器對象,Parcel類是一種最快的序列號和反序列化機制,專爲Android中的進程間通信而設計。
要實現Parcelable接口,需要實現writeToParecl()和readFromParcel()方法。寫入對象到包裹和從包裹中讀取對象,注意:寫入屬性的順序和讀取屬性的順序必須相同。
向Person類添加一個名爲CREATOR的static final屬性,該屬性需要實現android.os.Parcelable.Creator<T>接口。
爲Parcelable提供一個構造函數,知道如何從Parcel創建對象。
在.aidl文件中我們需要導入該類:import com.myAndroid.aidlService.Person。
interface IStudentInfoServie{
String getScore(in String name, in Person requester); // 後面非原語類型需要一個方向指示符。
}