Android四大組件之Service

 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);    // 後面非原語類型需要一個方向指示符。

      }


 

發佈了44 篇原創文章 · 獲贊 12 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章