1.題記
Android中的服務和windows中的服務是類似的東西,服務一般沒有用戶操作界面,它運行於系統中不容易被用戶發覺,可以使用它開發如監控之類的程序。
廣播接收者(BroadcastReceiver)用於接收廣播Intent,廣播Intent的發送是通過調用Context.sendBroadcast()、Context.sendOrderedBroadcast()來實現的。通常一個廣播Intent可以被訂閱了此Intent的多個廣播接收者所接收,這個特性跟JMS中的Topic消息接收者類似。
2.Service開發詳解
服務的開發比較簡單,如下:
第一步:繼承Service類
public class SMSService extends Service { }
第二步:在AndroidManifest.xml文件中的<application>節點裏對服務進行配置:
<service android:name=".SMSService" />
服務不能自己運行,需要通過調用Context.startService()或Context.bindService()方法啓動服務。這兩個方法都可以啓動Service,但是它們的使用場合有所不同。使用startService()方法啓用服務,訪問者與服務之間沒有關連,即使訪問者退出了,服務仍然運行。使用bindService()方法啓用服務,訪問者與服務綁定在了一起,訪問者一旦退出,服務也就終止,大有“不求同時生,必須同時死”的特點。
採用Context.startService()方法啓動服務,只能調用Context.stopService()方法結束服務,服務結束時會調用onDestroy()方法。
startService 與 bindService區別如下:
- 生命週期:startService方式啓動,Service是通過接受Intent並且會經歷onCreate和onStart。當用戶在發出意圖使之銷燬時會經歷onDestory而bindService方式啓動,與Activity綁定的時候,會經歷onCreate和onBind,而當Activity被銷燬的時候,Service會先調用onUnbind然後是onDestory.
- 控制方式:牽着的控制方式需要使用固定的方法,對Service進行單一的操作。而後者由於與Activity綁定,不用考慮其生命週期問題,並且從發送Intent的被動操作,變爲可以主動對Service對象進行操作,我們深圳可以建立一個Handler類,對Service進行相關的操作。大大加強了Service的靈活性、可操作性。
- 總結:對於簡單的應用startService啓動方式能帶來更少的代碼,簡單的操作,對於複雜的應用bindService方式,雖然帶來的更多的編碼,但同時也帶來了更好的可操作性,使其實用起來更像Activity。
3.BroadcastReceiver開發詳解
3.1BroadcastReceiver廣播接收者
要實現一個廣播接收者方法如下:
第一步:繼承BroadcastReceiver,並重寫onReceive()方法。
public class IncomingSMSReceiver extends BroadcastReceiver {
@Override public void onReceive(Context context, Intent intent) {
}
}
第二步:訂閱感興趣的廣播Intent,訂閱方法有兩種:
第一種:使用代碼進行訂閱
- IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
- IncomingSMSReceiver receiver = new IncomingSMSReceiver();
- registerReceiver(receiver, filter);
第二種:在AndroidManifest.xml文件中的<application>節點裏進行訂閱:
- <receiver android:name=".IncomingSMSReceiver">
- <intent-filter>
- <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
- </intent-filter>
- </receiver>
廣播被分爲兩種不同的類型:“普通廣播(Normal broadcasts)”和“有序廣播(Ordered broadcasts)”。普通廣播是完全異步的,可以在同一時刻(邏輯上)被所有接收者接收到,消息傳遞的效率比較高,但缺點是:接收者不能將處理結果傳遞給下一個接收者,並且無法終止廣播Intent的傳播;然而有序廣播是按照接收者聲明的優先級別,被接收者依次接收廣播。如:A的級別高於B,B的級別高於C,那麼,廣播先傳給A,再傳給B,最後傳給C 。優先級別聲明在intent-filter元素的android:priority屬性中,數越大優先級別越高,取值範圍:-1000到1000,優先級別也可以調用IntentFilter對象的setPriority()進行設置 。有序廣播的接收者可以終止廣播Intent的傳播,廣播Intent的傳播一旦終止,後面的接收者就無法接收到廣播。另外,有序廣播的接收者可以將數據傳遞給下一個接收者,如:A得到廣播後,可以往它的結果對象中存入數據,當廣播傳給B時,B可以從A的結果對象中得到A存入的數據。
Context.sendBroadcast()
發送的是普通廣播,所有訂閱者都有機會獲得並進行處理。
Context.sendOrderedBroadcast()
發送的是有序廣播,系統會根據接收者聲明的優先級別按順序逐個執行接收者,前面的接收者有權終止廣播(BroadcastReceiver.abortBroadcast()),如果廣播被前面的接收者終止,後面的接收者就再也無法獲取到廣播。對於有序廣播,前面的接收者可以將數據通過setResultExtras(Bundle)方法存放進結果對象,然後傳給下一個接收者,下一個接收者通過代碼:Bundle bundle = getResultExtras(true))可以獲取上一個接收者存入在結果對象中的數據。
系統收到短信,發出的廣播屬於有序廣播。如果想阻止用戶收到短信,可以通過設置優先級,讓你們自定義的接收者先獲取到廣播,然後終止廣播,這樣用戶就接收不到短信了。
3.2廣播接收者的響應
在Android中,每次廣播消息到來時都會創建BroadcastReceiver實例並執行onReceive() 方法, onReceive() 方法執行完後,BroadcastReceiver 的實例就會被銷燬。當onReceive() 方法在10秒內沒有執行完畢,Android會認爲該程序無響應。所以在BroadcastReceiver裏不能做一些比較耗時的操作,否側會彈出ANR(Application No Response)的對話框。如果需要完成一項比較耗時的工作,應該通過發送Intent給Service,由Service來完成。這裏不能使用子線程來解決,因爲BroadcastReceiver的生命週期很短,子線程可能還沒有結束BroadcastReceiver就先結束了。BroadcastReceiver一旦結束,此時BroadcastReceiver的所在進程很容易在系統需要內存時被優先殺死,因爲它屬於空進程(沒有任何活動組件的進程)。如果它的宿主進程被殺死,那麼正在工作的子線程也會被殺死。所以採用子線程來解決是不可靠的。
- public class IncomingSMSReceiver extends BroadcastReceiver {
- @Override public void onReceive(Context context, Intent intent) {
- //發送Intent啓動服務,由服務來完成比較耗時的操作
- Intent service = new Intent(context, XxxService.class);
- context.startService(service);
- }
- }
3.3常見系統廣播接收者
除了短信到來廣播Intent,Android還有很多廣播Intent,如:開機啓動、電池電量變化、時間已經改變等廣播Intent。
接收電池電量變化廣播Intent ,在AndroidManifest.xml文件中的<application>節點裏訂閱此Intent:
<receiver android:name=".IncomingSMSReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter>
</receiver>
接收開機啓動廣播Intent,在AndroidManifest.xml文件中的<application>節點裏訂閱此Intent:
<receiver android:name=".IncomingSMSReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
並且要進行權限聲明:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
接收短信廣播Intent,在AndroidManifest.xml文件中的<application>節點裏訂閱此Intent:
<receiver android:name=".IncomingSMSReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
在AndroidManifest.xml文件中添加以下權限:
<uses-permission android:name="android.permission.RECEIVE_SMS"/><!-- 接收短信權限 -->
<uses-permission android:name="android.permission.SEND_SMS"/><!-- 發送短信權限 -->
4.簡單實例
下面是整合了Service與BroadCastReceiver的一個小例子,主要實現的是,在後臺開通一個計數服務,當計數能被5整除時候則廣播該數。主要代碼如下:
ClientActivity綁定服務:
- public class ClientActivity extends Activity {
- /** Called when the activity is first created. */
- private ICountService countService;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- this.bindService(new Intent(this, CountService.class),
- this.serviceConnection, BIND_AUTO_CREATE);
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- this.unbindService(serviceConnection);
- }
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if(keyCode==KeyEvent.KEYCODE_BACK)
- {
- this.unbindService(serviceConnection);
- this.finish();
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
- private ServiceConnection serviceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- countService = (ICountService) service;// 對於本地服務,獲取的實例和服務onBind()返回的實例是同一個
- int i = countService.getCount();
- Log.v("CountService", "Count is " + i);
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- countService = null;
- }
- };
- }
計數服務代碼:
- package com.sulang.android.service;
- import android.app.Service;
- import android.content.Intent;
- import android.os.Binder;
- import android.os.IBinder;
- import android.util.Log;
- /*
- *@author 七里香的悔恨,2011-3-17
- *CountService.java
- *Blog:[url]http://bigboy.iteye.com/[/url]
- */
- public class CountService extends Service {
- private boolean quit=false;
- private int count;
- private ServiceBinder serviceBinder = new ServiceBinder();
- private final static String DIVIDE_RESULT="com.sulang.android.service.DIVIDE";
- @Override
- public IBinder onBind(Intent intent) {
- return serviceBinder;
- }
- public class ServiceBinder extends Binder implements ICountService {
- @Override
- public int getCount() {
- return count;
- }
- }
- @Override
- public void onCreate() {
- super.onCreate();
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (!quit) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {}
- count++;
- if(count%5==0)
- {
- Intent intent = new Intent(DIVIDE_RESULT);
- intent.putExtra("count", count);
- sendBroadcast(intent);
- }
- Log.i("CountService", count+"");
- }
- }
- }).start();
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- this.quit = true;
- }
- }
計數廣播接收者:
- package com.sulang.android.service;
- import android.content.BroadcastReceiver;
- import android.content.Context;
- import android.content.Intent;
- import android.widget.Toast;
- /*
- *@author 七里香的悔恨,2011-3-18
- *CountServiceBroadcast.java
- *Blog:[url]http://bigboy.iteye.com/[/url]
- */
- public class CountServiceBroadcast extends BroadcastReceiver {
- private final static String DIVIDE_RESULT="com.sulang.android.service.DIVIDE";
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if(action.equals(DIVIDE_RESULT))
- {
- int count = intent.getIntExtra("count", 0);
- Toast.makeText(context, "當前數字爲:"+count, Toast.LENGTH_LONG).show();
- }
- }
- /**
- * 使用代碼進行訂閱廣播
- * IntentFilter filter=new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
- * IncomingSMSReceiver receiver=new IncomingSMSReceiver();
- * registerReceiver(receiver,filter);
- */
- }
manifest文件:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.sulang.android.service"
- android:versionCode="1"
- android:versionName="1.0">
- <uses-sdk android:minSdkVersion="4" />
- <application android:icon="@drawable/icon" android:label="@string/app_name">
- <activity android:name=".ClientActivity"
- 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.sulang.android.service.Count"></action>
- <category android:name="android.intent.category.DEFAULT"></category>
- </intent-filter>
- </service>
- <receiver android:name=".CountServiceBroadcast">
- <intent-filter>
- <action android:name="com.sulang.android.service.DIVIDE" />
- <category android:name="android.intent.category.DEFAULT"></category>
- </intent-filter>
- </receiver>
- </application>
- </manifest>
具體效果圖如下: