android編寫Service入門用法與教程

android SDK提供了Service,用於類似*nix守護進程或者windows的服務。

Service有兩種類型:

1. 本地服務(Local Service):用於應用程序內部

2. 遠程服務(Remote Sercie):用於android系統內部的應用程序之間

前者用於實現應用程序自己的一些耗時任務,比如查詢升級信息,並不佔用應用程序比如Activity所屬線程,而是單開線程後臺執行,這樣用戶體驗比較好。

後者可被其他應用程序複用,比如天氣預報服務,其他應用程序不需要再寫這樣的服務,調用已有的即可。

編寫不需和Activity交互的本地服務示例

本地服務編寫比較簡單。首先,要創建一個Service類,該類繼承android的Service類。這裏寫了一個計數服務的類,每秒鐘爲計數器加一。在服務類的內部,還創建了一個線程,用於實現後臺執行上述業務邏輯。

package com.easymorse;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class CountService extends Service {

private boolean threadDisable;

private int count;

@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {

@Override
public void run() {
while (!threadDisable) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
count++;
Log.v("CountService", "Count is " + count);
}
}
}).start();
}

@Override
public void onDestroy() {
super.onDestroy();
this.threadDisable = true;
Log.v("CountService", "on destroy");
}

public int getCount() {
return count;
}
}

需要將該服務註冊到配置文件AndroidManifest.xml中,否則無法找到:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.easymorse" android:versionCode="1" android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".LocalServiceDemoActivity"
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="CountService" />
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>

在Activity中啓動和關閉本地服務。
package com.easymorse;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public class LocalServiceDemoActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

this.startService(new Intent(this, CountService.class));
}

@Override
protected void onDestroy() {
super.onDestroy();
this.stopService(new Intent(this, CountService.class));
}
}

可通過日誌查看到後臺線程打印的計數內容。

編寫本地服務和Activity交互的示例

上面的示例是通過startService和stopService啓動關閉服務的。適用於服務和activity之間沒有調用交互的情況。如果之間需要傳遞參數或者方法調用。需要使用bind和unbind方法。

具體做法是,服務類需要增加接口,比如ICountService,另外,服務類需要有一個內部類,這樣可以方便訪問外部類的封裝數據,這個內部類需要繼承Binder類並實現ICountService接口。還有,就是要實現Service的onBind方法,不能只傳回一個null了。

這是新建立的接口代碼:

 
package com.easymorse;

public interface ICountService {

public abstract int getCount();
}

修改後的CountService代碼:
package com.easymorse;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class CountService extends Service implements ICountService {

private boolean threadDisable;

private int count;

private ServiceBinder serviceBinder=new ServiceBinder();

public class ServiceBinder extends Binder implements ICountService{
@Override
public int getCount() {
return count;
}
}

@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}

@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {

@Override
public void run() {
while (!threadDisable) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
count++;
Log.v("CountService", "Count is " + count);
}
}
}).start();
}

@Override
public void onDestroy() {
super.onDestroy();
this.threadDisable = true;
Log.v("CountService", "on destroy");
}

/* (non-Javadoc)
* @see com.easymorse.ICountService#getCount()
*/

public int getCount() {
return count;
}

}
服務的註冊也要做改動,AndroidManifest.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.easymorse" android:versionCode="1" android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".LocalServiceDemoActivity"
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="CountService">
<intent-filter>
<action android:name="com.easymorse.CountService"/>
</intent-filter>
</service>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>

Acitity代碼不再通過startSerivce和stopService啓動關閉服務,另外,需要通過ServiceConnection的內部類實現來連接Service和Activity。

package com.easymorse;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

public class LocalServiceDemoActivity extends Activity {

private ServiceConnection serviceConnection = new ServiceConnection() {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
countService = (ICountService) service;
Log.v("CountService", "on serivce connected, count is "
+ countService.getCount());
}

@Override
public void onServiceDisconnected(ComponentName name) {
countService = null;
}
};

private ICountService countService;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.bindService(new Intent("com.easymorse.CountService"),
this.serviceConnection, BIND_AUTO_CREATE);
}

@Override
protected void onDestroy() {
super.onDestroy();
this.unbindService(serviceConnection);
}
}
編寫傳遞基本型數據的遠程服務

上面的示例,可以擴展爲,讓其他應用程序複用該服務。這樣的服務叫遠程(remote)服務,實際上是進程間通信(RPC)。

這時需要使用android接口描述語言(AIDL)來定義遠程服務的接口,而不是上述那樣簡單的java接口。擴展名爲aidl而不是java。可用上面的ICountService改動而成ICountSerivde.aidl,eclipse會自動生成相關的java文件。

package com.easymorse;

interface ICountService {
int getCount();
}

編寫服務(Service)類,稍有差別,主要在binder是通過遠程獲得的,需要通過樁(Stub)來獲取。樁對象是遠程對象的本地代理。

package com.easymorse;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class CountService extends Service {

private boolean threadDisable;

private int count;

private ICountService.Stub serviceBinder = new ICountService.Stub() {

@Override
public int getCount() throws RemoteException {
return count;
}
};

@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}

@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {

@Override
public void run() {
while (!threadDisable) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
count++;
Log.v("CountService", "Count is " + count);
}
}
}).start();
}

@Override
public void onDestroy() {
super.onDestroy();
this.threadDisable = true;
Log.v("CountService", "on destroy");
}
}

配置文件AndroidManifest.xml和上面的類似,沒有區別。

在Activity中使用服務的差別不大,只需要對ServiceConnection中的調用遠程服務的方法時,要捕獲異常。

private ServiceConnection serviceConnection = new ServiceConnection() {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
countService = (ICountService) service;
try {
Log.v("CountService", "on serivce connected, count is "
+ countService.getCount());
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
countService = null;
}

};
這樣就可以在同一個應用程序中使用遠程服務的方式和自己定義的服務交互了。

如果是另外的應用程序使用遠程服務,需要做的是複製上面的aidl文件和相應的包構到應用程序中,其他調用等都一樣。

編寫傳遞複雜數據類型的遠程服務

遠程服務往往不只是傳遞java基本數據類型。這時需要注意android的一些限制和規定:

  1. android支持String和CharSequence
  2. 如果需要在aidl中使用其他aidl接口類型,需要import,即使是在相同包結構下;
  3. android允許傳遞實現Parcelable接口的類,需要import;
  4. android支持集合接口類型List和Map,但是有一些限制,元素必須是基本型或者上述三種情況,不需要import集合接口類,但是需要對元素涉及到的類型import;
  5. 非基本數據類型,也不是String和CharSequence類型的,需要有方向指示,包括in、out和inout,in表示由客戶端設置,out表示由服務端設置,inout是兩者均可設置。

這裏將前面的例子中返回的int數據改爲複雜數據類型:

package com.easymorse;

import android.os.Parcel;
import android.os.Parcelable;

public class CountBean implements Parcelable {

public static final Parcelable.Creator<CountBean> CREATOR = new Creator<CountBean>() {

@Override
public CountBean createFromParcel(Parcel source) {
CountBean bean = new CountBean();
bean.count = source.readInt();
return bean;
}

@Override
public CountBean[] newArray(int size) {
return new CountBean[size];
}

};

public int count;

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.count);
}

@Override
public int describeContents() {
return 0;
}

}
然後,需要在相同包下建一個同名的aidl文件,用於android生成相應的輔助文件:
package com.easymorse;

parcelable CountBean;

這一步是android 1.5後的變化,無法通過adt生成aidl,也不能用一個比如全局的project.aidl文件,具體見:http://www.anddev.org/viewtopic.php?p=20991

然後,需要在服務的aidl文件中修改如下:

package com.easymorse;

import com.easymorse.CountBean;

interface ICountService {
CountBean getCount();
}
其他的改動很小,只需將CountService和調用CountService的部分修改爲使用CountBean即可。

原文出自:http://marshal.easymorse.com/archives/1564

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章