posts - 101,  comments - 29,  trackbacks - 0

          常用鏈接

          留言簿

          隨筆檔案(101)

          我的連接

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

                  在Android系統中,廣播(Broadcast)是在組件之間傳播數據(Intent)的一種機制;這些組件甚至是可以位于不同的進程中,這樣它就像Binder機制一樣,起到進程間通信的作用;本文通過一個簡單的例子來學習Android系統的廣播機制,為后續分析廣播機制的源代碼作準備。

                  在Android系統中,為什么需要廣播機制呢?廣播機制,本質上它就是一種組件間的通信方式,如果是兩個組件位于不同的進程當中,那么可以用Binder機制來實現,如果兩個組件是在同一個進程中,那么它們之間可以用來通信的方式就更多了,這樣看來,廣播機制似乎是多余的。然而,廣播機制卻是不可替代的,它和Binder機制不一樣的地方在于,廣播的發送者和接收者事先是不需要知道對方的存在的,這樣帶來的好處便是,系統的各個組件可以松耦合地組織在一起,這樣系統就具有高度的可擴展性,容易與其它系統進行集成。

                  在軟件工程中,是非常強調模塊之間的高內聚低耦合性的,不然的話,隨著系統越來越龐大,就會面臨著越來越難維護的風險,最后導致整個項目的失敗。Android應用程序的組織方式,可以說是把這種高內聚低耦合性的思想貫徹得非常透徹,在任何一個Activity中,都可以使用一個簡單的Intent,通過startActivity或者startService,就可以把另外一個Activity或者Service啟動起來為它服務,而且它根本上不依賴這個Activity或者Service的實現,只需要知道它的字符串形式的名字即可,而廣播機制更絕,它連接收者的名字都不需要知道。

                  不過話又說回來,廣播機制在Android系統中,也不算是什么創新的東西。如果讀者了解J2EE或者COM,就會知道,在J2EE中,提供了消息驅動Bean(Message-Driven Bean),用來實現應用程序各個組件之間的消息傳遞;而在COM中,提供了連接點(Connection Point)的概念,也是用來在應用程序各個組間間進行消息傳遞。無論是J2EE中的消息驅動Bean,還是COM中的連接點,或者Android系統的廣播機制,它們的實現機理都是消息發布/訂閱模式的事件驅動模型,消息的生產者發布事件,而使用者訂閱感興趣的事件。

                  廢話說了一大堆,現在開始進入主題了,和前面的文章一樣,我們通過具體的例子來介紹Android系統的廣播機制。在這個例子中,有一個Service,它在另外一個線程中實現了一個計數器服務,每隔一秒鐘就自動加1,然后將結果不斷地反饋給應用程序中的界面線程,而界面線程中的Activity在得到這個反饋后,就會把結果顯示在界面上。為什么要把計數器服務放在另外一個線程中進行呢?我們可以把這個計數器服務想象成是一個耗時的計算型邏輯,如果放在界面線程中去實現,那么勢必就會導致應用程序不能響應界面事件,最后導致應用程序產生ANR(Application Not Responding)問題。計數器線程為了把加1后的數字源源不斷地反饋給界面線程,這時候就可以考慮使用廣播機制了。

                  首先在Android源代碼工程中創建一個Android應用程序工程,名字就稱為Broadcast吧。關于如何獲得Android源代碼工程,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文;關于如何在Android源代碼工程中創建應用程序工程,請參考在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務一文。這個應用程序工程定義了一個名為shy.luo.broadcast的package,這個例子的源代碼主要就是實現在這里了。下面,將會逐一介紹這個package里面的文件。 

                  首先,我們在src/shy/luo/broadcast/ICounterService.java文件中定義計數器的服務接口:

          package shy.luo.broadcast;
          
          public interface ICounterService {
                  public void startCounter(int initVal);
                  public void stopCounter();
          }
          
                 這個接口很簡單,它只有兩個成員函數,分別用來啟動和停止計數器;啟動計數時,還可以指定計數器的初始值。

           

                 接著,我們來看一個應用程序的默認Activity的實現,在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的實現也很簡單,它在創建(onCreate)的時候,會調用bindService函數來把計數器服務(CounterService)啟動起來,它的第二個參數serviceConnection是一個ServiceConnection實例。計數器服務啟動起來后,系統會調用這個實例的onServiceConnected函數將一個Binder對象傳回來,通過調用這個Binder對象的getService函數,就可以獲得計數器服務接口。這里,把這個計數器服務接口保存在MainActivity的counterService成員變量中。同樣,當我們調用unbindService停止計數器服務時,系統會調用這個實例的onServiceDisconnected函數告訴MainActivity,它與計數器服務的連接斷開了。

                  注意,這里通過調用bindService函數來啟動Service時,這個Service與啟動它的Activity是位于同一個進程中,它不像我們在前面一篇文章Android系統在新進程中啟動自定義服務過程(startService)的原理分析中所描述那樣在新的進程中啟動服務,后面我們再寫一篇文章來分析bindService啟動服務的過程。

           

                  在MainActivity的onResume函數中,通過調用registerReceiver函數注冊了一個廣播接收器counterActionReceiver,它是一個BroadcastReceiver實例,并且指定了這個廣播接收器只對CounterService.BROADCAST_COUNTER_ACTION類型的廣播感興趣。當CounterService發出一個CounterService.BROADCAST_COUNTER_ACTION類型的廣播時,系統就會把這個廣播發送到counterActionReceiver實例的onReceiver函數中去。在onReceive函數中,從參數intent中取出計數器當前的值,顯示在界面上。

                 MainActivity界面上有兩個按鈕,分別是Start Counter和Stop Counter按鈕,點擊前者開始計數,而點擊后者則停止計數。

                 計數器服務CounterService實現在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;
          	}
          }

                  這個計數器服務實現了ICounterService接口。當這個服務被binderService函數啟動時,系統會調用它的onBind函數,這個函數返回一個Binder對象給系統。上面我們說到,當MainActivity調用bindService函數來啟動計數器服務器時,系統會調用MainActivity的ServiceConnection實例serviceConnection的onServiceConnected函數通知MainActivity,這個服務已經連接上了,并且會通過這個函數傳進來一個Binder遠程對象,這個Binder遠程對象就是來源于這里的onBind的返回值了。

                  函數onBind返回的Binder對象是一個自定義的CounterBinder實例,它實現了一個getService成員函數。當系統通知MainActivity,計數器服務已經啟動起來并且連接成功后,并且將這個Binder對象傳給MainActivity時,MainActivity就會把這個Binder對象強制轉換為CounterBinder實例,然后調用它的getService函數獲得服務接口。這樣,MainActivity就通過這個Binder對象和CounterService關聯起來了。

           

                  當MainActivity調用計數器服務接口的startCounter函數時,計數器服務并不是直接進入計數狀態,而是通過使用異步任務(AsyncTask)在后臺線程中進行計數。這里為什么要使用異步任務來在后臺線程中進行計數呢?前面我們說過,這個計數過程是一個耗時的計算型邏輯,不能把它放在界面線程中進行,因為這里的CounterService啟動時,并沒有在新的進程中啟動,它與MainActivity一樣,運行在應用程序的界面線程中,因此,這里需要使用異步任務在在后臺線程中進行計數。

                  異步任務AsyncTask的具體用法可以參考官方文檔http://developer.android.com/reference/android/os/AsyncTask.html。它的大概用法是,當我們調用異步任務實例的execute(task.execute)方法時,當前調用線程就返回了,系統啟動一個后臺線程來執行這個異步任務實例的doInBackground函數,這個函數就是我們用來執行耗時計算的地方了,它會進入到一個循環中,每隔1秒鐘就把計數器加1,然后進入休眠(Thread.sleep),醒過來,再重新這個計算過程。在計算的過程中,可以通過調用publishProgress函數來通知調用者當前計算的進度,好讓調用者來更新界面,調用publishProgress函數的效果最終就是直入到這個異步任務實例的onProgressUpdate函數中,這里就可以把這個進度值以廣播的形式(sendBroadcast)發送出去了,這里的進度值就定義為當前計數服務的計數值。

                  當MainActivity調用計數器服務接口的stopCounter函數時,會告訴函數doInBackground停止執行計數(stop = true),于是,函數doInBackground就退出計數循環,然后將最終計數結果返回了,返回的結果最后進入到onPostExecute函數中,這個函數同樣通過廣播的形式(sendBroadcast)把這個計數結果廣播出去。

                  計算器服務就介紹到這里了,下面我們看看應用程序的配置文件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> 
                  這個配置文件很簡單,只是告訴系統,它有一個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源代碼中的模塊一文。
                    執行以下命令進行編譯和打包:
          USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Broadcast        
          USER-NAME@MACHINE-NAME:~/Android$ make snod   
                   這樣,打包好的Android系統鏡像文件system.img就包含我們前面創建的Broadcast應用程序了。
                   再接下來,就是運行模擬器來運行我們的例子了。關于如何在Android源代碼工程中運行模擬器,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文。
                   執行以下命令啟動模擬器:
          USER-NAME@MACHINE-NAME:~/Android$ emulator
                  模擬器啟動起,就可以App Launcher中找到Broadcast應用程序圖標,接著把它啟動起來,然后點擊界面上的Start Counter按鈕,就可以把計數器服務啟動起來了,計數器服務又通過廣播把計數值反饋給MainActivity,于是,我們就會在MainActivity界面看到計數器的值不斷地增加了:

           

                  這樣,使用廣播的例子就介紹完了。回顧一下,使用廣播的兩個步驟:

                  1. 廣播的接收者需要通過調用registerReceiver函數告訴系統,它對什么樣的廣播有興趣,即指定IntentFilter,并且向系統注冊廣播接收器,即指定BroadcastReceiver:

          IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION);
          registerReceiver(counterActionReceiver, counterActionFilter);
                  這里,指定感興趣的廣播就是CounterService.BROADCAST_COUNTER_ACTION了,而指定的廣播接收器就是counterActonReceiver,它是一個BroadcastReceiver類型的實例。

           

                  2. 廣播的發送者通過調用sendBroadcast函數來發送一個指定的廣播,并且可以指定廣播的相關參數:

          Intent intent = new Intent(BROADCAST_COUNTER_ACTION);
          intent.putExtra(COUNTER_VALUE, counter);
          sendBroadcast(intent)
                  這里,指定的廣播為CounterService.BROADCAST_COUNTER_ACTION,并且附帶的帶參數當前的計數器值counter。調用了sendBroadcast函數之后,所有注冊了CounterService.BROADCAST_COUNTER_ACTION廣播的接收者便可以收到這個廣播了。

           

                  在第1步中,廣播的接收者把廣播接收器注冊到ActivityManagerService中;在第2步中,廣播的發送者同樣是把廣播發送到ActivityManagerService中,由ActivityManagerService去查找注冊了這個廣播的接收者,然后把廣播分發給它們。

                  在第2步的分發的過程,其實就是把這個廣播轉換成一個消息,然后放入到接收器所在的線程消息隊列中去,最后就可以在消息循環中調用接收器的onReceive函數了。這里有一個要非常注意的地方是,由于ActivityManagerService把這個廣播放進接收器所在的線程消息隊列后,就返回了,它不關心這個消息什么時候會被處理,因此,對廣播的處理是異步的,即調用sendBroadcast時,這個函數不會等待這個廣播被處理完后才返回。

                  下面,我們以一個序列圖來總結一下,廣播的注冊和發送的過程:


                  虛線上面Step 1到Step 4步是注冊廣播接收器的過程,其中Step 2通過LoadedApk.getReceiverDispatcher在LoadedApk內部創建了一個IIntentReceiver接口,并且傳遞給ActivityManagerService;虛線下面的Step 5到Step 11是發送廣播的過程,在Step 8中,ActivityManagerService利用上面得到的IIntentReceiver遠程接口,調用LoadedApk.performReceiver接口,LoadedApk.performReceiver接口通過ActivityThread.H接口的post函數將這個廣播消息放入到ActivityThread的消息隊列中去,最后這個消息在LoadedApk的Args.run函數中處理,LoadedApk.Args.run函數接著調用MainActivity.BroadcastReceiver的onReceive函數來最終處理這個廣播。

                  文章開始的時候,我們提到,舉這個例子的最終目的,是為了進一步學習Android系統的廣播機制,因此,在接下來的兩篇文章中,我們將詳細描述上述注冊廣播接收器和發送廣播的過程:

                  1. Android應用程序注冊廣播接收器(registerReceiver)的過程分析;

                  2. Android應用程序發送廣播(sendBroadcast)的過程分析。

                  相信學習完這兩篇文章后,能夠加深對Android系統廣播機制的了解,敬請關注。

          作者:Luoshengyang 發表于2011-8-31 1:12:32 原文鏈接
          閱讀:5451 評論:19 查看評論
          posted on 2012-04-17 21:32 mixer-a 閱讀(1251) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 刚察县| 茶陵县| 夏津县| 离岛区| 华宁县| 青阳县| 宜州市| 时尚| 乌兰浩特市| 达拉特旗| 丹棱县| 石渠县| 崇信县| 博客| 盘山县| 迁安市| 宁武县| 通道| 东丰县| 长阳| 梓潼县| 上饶市| 巴里| 旬邑县| 渑池县| 东辽县| 山阴县| 陇川县| 灯塔市| 富平县| 辽宁省| 香格里拉县| 淮阳县| 那曲县| 中阳县| 政和县| 莱芜市| 招远市| 昭平县| 绥阳县| 宝兴县|