<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 101,  comments - 29,  trackbacks - 0

            在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 閱讀(1248) 評論(0)  編輯  收藏

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


    網站導航:
     
    主站蜘蛛池模板: 久久国产免费一区| 中文字幕免费视频| 免费a级毛片无码a∨性按摩| 在线观看亚洲AV每日更新无码| 久久国产免费观看精品3| 日木av无码专区亚洲av毛片| 久久一本岛在免费线观看2020| 亚洲国产美国国产综合一区二区| 在线观看免费播放av片| 亚洲av永久无码精品网站| 久久精品一本到99热免费| 亚洲影视一区二区| 成人毛片免费网站| 日韩精品无码免费视频| 亚洲日本va中文字幕久久| 久久精品视频免费| 亚洲国产精品xo在线观看| 在线免费视频一区| 色多多A级毛片免费看| 亚洲国产精品无码久久一区二区| 青青青国产手机频在线免费观看 | 亚洲国产美女精品久久| 国拍在线精品视频免费观看| 亚洲国产成人精品无码区花野真一| 国产成人aaa在线视频免费观看| 国产精品内射视频免费| 4480yy私人影院亚洲| 免费av欧美国产在钱| 一日本道a高清免费播放 | 亚洲AV成人片色在线观看| 国产精品永久免费10000| 国产精品无码亚洲一区二区三区| 久久久亚洲精品蜜桃臀| 亚洲成人免费电影| 免费一级毛片在线播放放视频| 亚洲四虎永久在线播放| 国产福利免费观看| 99久久99久久精品免费观看| 国产精品成人亚洲| 亚洲精品亚洲人成在线观看麻豆| 一级毛片直播亚洲|