在Android系統(tǒng)中,廣播(Broadcast)是在組件之間傳播數(shù)據(jù)(Intent)的一種機制;這些組件甚至是可以位于不同的進程中,這樣它就像Binder機制一樣,起到進程間通信的作用;本文通過一個簡單的例子來學習Android系統(tǒng)的廣播機制,為后續(xù)分析廣播機制的源代碼作準備。
在Android系統(tǒng)中,為什么需要廣播機制呢?廣播機制,本質(zhì)上它就是一種組件間的通信方式,如果是兩個組件位于不同的進程當中,那么可以用Binder機制來實現(xiàn),如果兩個組件是在同一個進程中,那么它們之間可以用來通信的方式就更多了,這樣看來,廣播機制似乎是多余的。然而,廣播機制卻是不可替代的,它和Binder機制不一樣的地方在于,廣播的發(fā)送者和接收者事先是不需要知道對方的存在的,這樣帶來的好處便是,系統(tǒng)的各個組件可以松耦合地組織在一起,這樣系統(tǒng)就具有高度的可擴展性,容易與其它系統(tǒng)進行集成。
在軟件工程中,是非常強調(diào)模塊之間的高內(nèi)聚低耦合性的,不然的話,隨著系統(tǒng)越來越龐大,就會面臨著越來越難維護的風險,最后導致整個項目的失敗。Android應用程序的組織方式,可以說是把這種高內(nèi)聚低耦合性的思想貫徹得非常透徹,在任何一個Activity中,都可以使用一個簡單的Intent,通過startActivity或者startService,就可以把另外一個Activity或者Service啟動起來為它服務,而且它根本上不依賴這個Activity或者Service的實現(xiàn),只需要知道它的字符串形式的名字即可,而廣播機制更絕,它連接收者的名字都不需要知道。
不過話又說回來,廣播機制在Android系統(tǒng)中,也不算是什么創(chuàng)新的東西。如果讀者了解J2EE或者COM,就會知道,在J2EE中,提供了消息驅(qū)動Bean(Message-Driven Bean),用來實現(xiàn)應用程序各個組件之間的消息傳遞;而在COM中,提供了連接點(Connection Point)的概念,也是用來在應用程序各個組間間進行消息傳遞。無論是J2EE中的消息驅(qū)動Bean,還是COM中的連接點,或者Android系統(tǒng)的廣播機制,它們的實現(xiàn)機理都是消息發(fā)布/訂閱模式的事件驅(qū)動模型,消息的生產(chǎn)者發(fā)布事件,而使用者訂閱感興趣的事件。
廢話說了一大堆,現(xiàn)在開始進入主題了,和前面的文章一樣,我們通過具體的例子來介紹Android系統(tǒng)的廣播機制。在這個例子中,有一個Service,它在另外一個線程中實現(xiàn)了一個計數(shù)器服務,每隔一秒鐘就自動加1,然后將結(jié)果不斷地反饋給應用程序中的界面線程,而界面線程中的Activity在得到這個反饋后,就會把結(jié)果顯示在界面上。為什么要把計數(shù)器服務放在另外一個線程中進行呢?我們可以把這個計數(shù)器服務想象成是一個耗時的計算型邏輯,如果放在界面線程中去實現(xiàn),那么勢必就會導致應用程序不能響應界面事件,最后導致應用程序產(chǎn)生ANR(Application Not Responding)問題。計數(shù)器線程為了把加1后的數(shù)字源源不斷地反饋給界面線程,這時候就可以考慮使用廣播機制了。
首先在Android源代碼工程中創(chuàng)建一個Android應用程序工程,名字就稱為Broadcast吧。關于如何獲得Android源代碼工程,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文;關于如何在Android源代碼工程中創(chuàng)建應用程序工程,請參考在Ubuntu上為Android系統(tǒng)內(nèi)置Java應用程序測試Application Frameworks層的硬件服務一文。這個應用程序工程定義了一個名為shy.luo.broadcast的package,這個例子的源代碼主要就是實現(xiàn)在這里了。下面,將會逐一介紹這個package里面的文件。
首先,我們在src/shy/luo/broadcast/ICounterService.java文件中定義計數(shù)器的服務接口:
package shy.luo.broadcast; public interface ICounterService { public void startCounter(int initVal); public void stopCounter(); }這個接口很簡單,它只有兩個成員函數(shù),分別用來啟動和停止計數(shù)器;啟動計數(shù)時,還可以指定計數(shù)器的初始值。
接著,我們來看一個應用程序的默認Activity的實現(xiàn),在src/shy/luo/broadcast/MainActivity.java文件中:
package shy.luo.broadcast; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity implements OnClickListener { private final static String LOG_TAG = "shy.luo.broadcast.MainActivity"; private Button startButton = null; private Button stopButton = null; private TextView counterText = null; private ICounterService counterService = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); startButton = (Button)findViewById(R.id.button_start); stopButton = (Button)findViewById(R.id.button_stop); counterText = (TextView)findViewById(R.id.textview_counter); startButton.setOnClickListener(this); stopButton.setOnClickListener(this); startButton.setEnabled(true); stopButton.setEnabled(false); Intent bindIntent = new Intent(MainActivity.this, CounterService.class); bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); Log.i(LOG_TAG, "Main Activity Created."); } @Override public void onResume() { super.onResume(); IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION); registerReceiver(counterActionReceiver, counterActionFilter); } @Override public void onPause() { super.onPause(); unregisterReceiver(counterActionReceiver); } @Override public void onDestroy() { super.onDestroy(); unbindService(serviceConnection); } @Override public void onClick(View v) { if(v.equals(startButton)) { if(counterService != null) { counterService.startCounter(0); startButton.setEnabled(false); stopButton.setEnabled(true); } } else if(v.equals(stopButton)) { if(counterService != null) { counterService.stopCounter(); startButton.setEnabled(true); stopButton.setEnabled(false); } } } private BroadcastReceiver counterActionReceiver = new BroadcastReceiver(){ public void onReceive(Context context, Intent intent) { int counter = intent.getIntExtra(CounterService.COUNTER_VALUE, 0); String text = String.valueOf(counter); counterText.setText(text); Log.i(LOG_TAG, "Receive counter event"); } }; private ServiceConnection serviceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { counterService = ((CounterService.CounterBinder)service).getService(); Log.i(LOG_TAG, "Counter Service Connected"); } public void onServiceDisconnected(ComponentName className) { counterService = null; Log.i(LOG_TAG, "Counter Service Disconnected"); } }; }
MainActivity的實現(xiàn)也很簡單,它在創(chuàng)建(onCreate)的時候,會調(diào)用bindService函數(shù)來把計數(shù)器服務(CounterService)啟動起來,它的第二個參數(shù)serviceConnection是一個ServiceConnection實例。計數(shù)器服務啟動起來后,系統(tǒng)會調(diào)用這個實例的onServiceConnected函數(shù)將一個Binder對象傳回來,通過調(diào)用這個Binder對象的getService函數(shù),就可以獲得計數(shù)器服務接口。這里,把這個計數(shù)器服務接口保存在MainActivity的counterService成員變量中。同樣,當我們調(diào)用unbindService停止計數(shù)器服務時,系統(tǒng)會調(diào)用這個實例的onServiceDisconnected函數(shù)告訴MainActivity,它與計數(shù)器服務的連接斷開了。
注意,這里通過調(diào)用bindService函數(shù)來啟動Service時,這個Service與啟動它的Activity是位于同一個進程中,它不像我們在前面一篇文章Android系統(tǒng)在新進程中啟動自定義服務過程(startService)的原理分析中所描述那樣在新的進程中啟動服務,后面我們再寫一篇文章來分析bindService啟動服務的過程。
在MainActivity的onResume函數(shù)中,通過調(diào)用registerReceiver函數(shù)注冊了一個廣播接收器counterActionReceiver,它是一個BroadcastReceiver實例,并且指定了這個廣播接收器只對CounterService.BROADCAST_COUNTER_ACTION類型的廣播感興趣。當CounterService發(fā)出一個CounterService.BROADCAST_COUNTER_ACTION類型的廣播時,系統(tǒng)就會把這個廣播發(fā)送到counterActionReceiver實例的onReceiver函數(shù)中去。在onReceive函數(shù)中,從參數(shù)intent中取出計數(shù)器當前的值,顯示在界面上。
MainActivity界面上有兩個按鈕,分別是Start Counter和Stop Counter按鈕,點擊前者開始計數(shù),而點擊后者則停止計數(shù)。
計數(shù)器服務CounterService實現(xiàn)在src/shy/luo/broadcast/CounterService.java文件中:
package shy.luo.broadcast; import android.app.Service; import android.content.Intent; import android.os.AsyncTask; import android.os.Binder; import android.os.IBinder; import android.util.Log; public class CounterService extends Service implements ICounterService { private final static String LOG_TAG = "shy.luo.broadcast.CounterService"; public final static String BROADCAST_COUNTER_ACTION = "shy.luo.broadcast.COUNTER_ACTION"; public final static String COUNTER_VALUE = "shy.luo.broadcast.counter.value"; private boolean stop = false; private final IBinder binder = new CounterBinder(); public class CounterBinder extends Binder { public CounterService getService() { return CounterService.this; } } @Override public IBinder onBind(Intent intent) { return binder; } @Override public void onCreate() { super.onCreate(); Log.i(LOG_TAG, "Counter Service Created."); } @Override public void onDestroy() { Log.i(LOG_TAG, "Counter Service Destroyed."); } public void startCounter(int initVal) { AsyncTask<Integer, Integer, Integer> task = new AsyncTask<Integer, Integer, Integer>() { @Override protected Integer doInBackground(Integer... vals) { Integer initCounter = vals[0]; stop = false; while(!stop) { publishProgress(initCounter); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } initCounter++; } return initCounter; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); int counter = values[0]; Intent intent = new Intent(BROADCAST_COUNTER_ACTION); intent.putExtra(COUNTER_VALUE, counter); sendBroadcast(intent); } @Override protected void onPostExecute(Integer val) { int counter = val; Intent intent = new Intent(BROADCAST_COUNTER_ACTION); intent.putExtra(COUNTER_VALUE, counter); sendBroadcast(intent); } }; task.execute(0); } public void stopCounter() { stop = true; } }
這個計數(shù)器服務實現(xiàn)了ICounterService接口。當這個服務被binderService函數(shù)啟動時,系統(tǒng)會調(diào)用它的onBind函數(shù),這個函數(shù)返回一個Binder對象給系統(tǒng)。上面我們說到,當MainActivity調(diào)用bindService函數(shù)來啟動計數(shù)器服務器時,系統(tǒng)會調(diào)用MainActivity的ServiceConnection實例serviceConnection的onServiceConnected函數(shù)通知MainActivity,這個服務已經(jīng)連接上了,并且會通過這個函數(shù)傳進來一個Binder遠程對象,這個Binder遠程對象就是來源于這里的onBind的返回值了。
函數(shù)onBind返回的Binder對象是一個自定義的CounterBinder實例,它實現(xiàn)了一個getService成員函數(shù)。當系統(tǒng)通知MainActivity,計數(shù)器服務已經(jīng)啟動起來并且連接成功后,并且將這個Binder對象傳給MainActivity時,MainActivity就會把這個Binder對象強制轉(zhuǎn)換為CounterBinder實例,然后調(diào)用它的getService函數(shù)獲得服務接口。這樣,MainActivity就通過這個Binder對象和CounterService關聯(lián)起來了。
當MainActivity調(diào)用計數(shù)器服務接口的startCounter函數(shù)時,計數(shù)器服務并不是直接進入計數(shù)狀態(tài),而是通過使用異步任務(AsyncTask)在后臺線程中進行計數(shù)。這里為什么要使用異步任務來在后臺線程中進行計數(shù)呢?前面我們說過,這個計數(shù)過程是一個耗時的計算型邏輯,不能把它放在界面線程中進行,因為這里的CounterService啟動時,并沒有在新的進程中啟動,它與MainActivity一樣,運行在應用程序的界面線程中,因此,這里需要使用異步任務在在后臺線程中進行計數(shù)。
異步任務AsyncTask的具體用法可以參考官方文檔http://developer.android.com/reference/android/os/AsyncTask.html。它的大概用法是,當我們調(diào)用異步任務實例的execute(task.execute)方法時,當前調(diào)用線程就返回了,系統(tǒng)啟動一個后臺線程來執(zhí)行這個異步任務實例的doInBackground函數(shù),這個函數(shù)就是我們用來執(zhí)行耗時計算的地方了,它會進入到一個循環(huán)中,每隔1秒鐘就把計數(shù)器加1,然后進入休眠(Thread.sleep),醒過來,再重新這個計算過程。在計算的過程中,可以通過調(diào)用publishProgress函數(shù)來通知調(diào)用者當前計算的進度,好讓調(diào)用者來更新界面,調(diào)用publishProgress函數(shù)的效果最終就是直入到這個異步任務實例的onProgressUpdate函數(shù)中,這里就可以把這個進度值以廣播的形式(sendBroadcast)發(fā)送出去了,這里的進度值就定義為當前計數(shù)服務的計數(shù)值。
當MainActivity調(diào)用計數(shù)器服務接口的stopCounter函數(shù)時,會告訴函數(shù)doInBackground停止執(zhí)行計數(shù)(stop = true),于是,函數(shù)doInBackground就退出計數(shù)循環(huán),然后將最終計數(shù)結(jié)果返回了,返回的結(jié)果最后進入到onPostExecute函數(shù)中,這個函數(shù)同樣通過廣播的形式(sendBroadcast)把這個計數(shù)結(jié)果廣播出去。
計算器服務就介紹到這里了,下面我們看看應用程序的配置文件AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="shy.luo.broadcast" android:versionCode="1" android:versionName="1.0"> <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=".CounterService" android:enabled="true"> </service> </application> </manifest>這個配置文件很簡單,只是告訴系統(tǒng),它有一個Activity和一個Service。
再來看MainActivity的界面文件,它定義在res/layout/main.xml文件中:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center"> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginBottom="10px" android:orientation="horizontal" android:gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="4px" android:gravity="center" android:text="@string/counter"> </TextView> <TextView android:id="@+id/textview_counter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="0"> </TextView> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center"> <Button android:id="@+id/button_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="@string/start"> </Button> <Button android:id="@+id/button_stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="@string/stop" > </Button> </LinearLayout> </LinearLayout>這個界面配置文件也很簡單,等一下我們在模擬器把這個應用程序啟動起來后,就可以看到它的截圖了。
應用程序用到的字符串資源文件位于res/values/strings.xml文件中:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Broadcast</string> <string name="counter">Counter: </string> <string name="start">Start Counter</string> <string name="stop">Stop Counter</string> </resources>最后,我們還要在工程目錄下放置一個編譯腳本文件Android.mk:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := Broadcast include $(BUILD_PACKAGE)接下來就要編譯了。有關如何單獨編譯Android源代碼工程的模塊,以及如何打包system.img,請參考如何單獨編譯Android源代碼中的模塊一文。
執(zhí)行以下命令進行編譯和打包:
USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Broadcast USER-NAME@MACHINE-NAME:~/Android$ make snod這樣,打包好的Android系統(tǒng)鏡像文件system.img就包含我們前面創(chuàng)建的Broadcast應用程序了。
再接下來,就是運行模擬器來運行我們的例子了。關于如何在Android源代碼工程中運行模擬器,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文。
執(zhí)行以下命令啟動模擬器:
USER-NAME@MACHINE-NAME:~/Android$ emulator模擬器啟動起,就可以App Launcher中找到Broadcast應用程序圖標,接著把它啟動起來,然后點擊界面上的Start Counter按鈕,就可以把計數(shù)器服務啟動起來了,計數(shù)器服務又通過廣播把計數(shù)值反饋給MainActivity,于是,我們就會在MainActivity界面看到計數(shù)器的值不斷地增加了:
這樣,使用廣播的例子就介紹完了。回顧一下,使用廣播的兩個步驟:
1. 廣播的接收者需要通過調(diào)用registerReceiver函數(shù)告訴系統(tǒng),它對什么樣的廣播有興趣,即指定IntentFilter,并且向系統(tǒng)注冊廣播接收器,即指定BroadcastReceiver:
IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION); registerReceiver(counterActionReceiver, counterActionFilter);這里,指定感興趣的廣播就是CounterService.BROADCAST_COUNTER_ACTION了,而指定的廣播接收器就是counterActonReceiver,它是一個BroadcastReceiver類型的實例。
2. 廣播的發(fā)送者通過調(diào)用sendBroadcast函數(shù)來發(fā)送一個指定的廣播,并且可以指定廣播的相關參數(shù):
Intent intent = new Intent(BROADCAST_COUNTER_ACTION); intent.putExtra(COUNTER_VALUE, counter); sendBroadcast(intent)這里,指定的廣播為CounterService.BROADCAST_COUNTER_ACTION,并且附帶的帶參數(shù)當前的計數(shù)器值counter。調(diào)用了sendBroadcast函數(shù)之后,所有注冊了CounterService.BROADCAST_COUNTER_ACTION廣播的接收者便可以收到這個廣播了。
在第1步中,廣播的接收者把廣播接收器注冊到ActivityManagerService中;在第2步中,廣播的發(fā)送者同樣是把廣播發(fā)送到ActivityManagerService中,由ActivityManagerService去查找注冊了這個廣播的接收者,然后把廣播分發(fā)給它們。
在第2步的分發(fā)的過程,其實就是把這個廣播轉(zhuǎn)換成一個消息,然后放入到接收器所在的線程消息隊列中去,最后就可以在消息循環(huán)中調(diào)用接收器的onReceive函數(shù)了。這里有一個要非常注意的地方是,由于ActivityManagerService把這個廣播放進接收器所在的線程消息隊列后,就返回了,它不關心這個消息什么時候會被處理,因此,對廣播的處理是異步的,即調(diào)用sendBroadcast時,這個函數(shù)不會等待這個廣播被處理完后才返回。
下面,我們以一個序列圖來總結(jié)一下,廣播的注冊和發(fā)送的過程:
虛線上面Step 1到Step 4步是注冊廣播接收器的過程,其中Step 2通過LoadedApk.getReceiverDispatcher在LoadedApk內(nèi)部創(chuàng)建了一個IIntentReceiver接口,并且傳遞給ActivityManagerService;虛線下面的Step 5到Step 11是發(fā)送廣播的過程,在Step 8中,ActivityManagerService利用上面得到的IIntentReceiver遠程接口,調(diào)用LoadedApk.performReceiver接口,LoadedApk.performReceiver接口通過ActivityThread.H接口的post函數(shù)將這個廣播消息放入到ActivityThread的消息隊列中去,最后這個消息在LoadedApk的Args.run函數(shù)中處理,LoadedApk.Args.run函數(shù)接著調(diào)用MainActivity.BroadcastReceiver的onReceive函數(shù)來最終處理這個廣播。
文章開始的時候,我們提到,舉這個例子的最終目的,是為了進一步學習Android系統(tǒng)的廣播機制,因此,在接下來的兩篇文章中,我們將詳細描述上述注冊廣播接收器和發(fā)送廣播的過程:
1. Android應用程序注冊廣播接收器(registerReceiver)的過程分析;
2. Android應用程序發(fā)送廣播(sendBroadcast)的過程分析。
相信學習完這兩篇文章后,能夠加深對Android系統(tǒng)廣播機制的了解,敬請關注。