曼妙琳瓏心 Android 面試題(2)

Android 的廣播機制





在 Android 裏面有各種各樣的廣播,比如電池的使用狀態,電話的接收和短信的接收都會產生一個廣播,應用程序開發者也可以監聽這些廣播並做出程序邏輯的處理。下面我畫一張粗略的圖來幫助大家理解廣播的運行機制。

Android 中有各式各樣的廣播,各種廣播在Android 系統中運行,當系統/應用程序運行時便會向 Android 註冊各種廣播,Android 接收到廣播會便會判斷哪種廣播需要哪種事件,然後向不同需要事件的應用程序註冊事件,不同的廣播可能處理不同的事件也可能處理相同的廣播事件,這時就需要 Android 系統爲我們做篩選。

案例分析:
一個經典的電話黑名單,首先通過將黑名單號碼保存在數據庫裏面,當來電時,我們接收到來電廣播並將黑名單號碼與數據庫中的某個數據做匹配,如果匹配的話則做出相應的處理,比如掛掉電話、比如靜音等等。。。
 
Demo 分析:
下面通過一個小DEMO 來講解一下廣播在Android 中如何編寫,在Demo中我們設置了一個按鈕爲按鈕設置點擊監聽通過點擊發送廣播,在後臺中接收到廣播並打印LOG信息。代碼如下:
 
BroadCastActivity 頁面代碼
public class BroadCastActivity extends Activity {
    
public static final String ACTION_INTENT_TEST = "com.terry.broadcast.test";

    

    @Override
    
public void onCreate(Bundle savedInstanceState) {
        
super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button btn 
= (Button) findViewById(R.id.Button01);
        btn.setOnClickListener(
new OnClickListener() {

            @Override
            
public void onClick(View v) {
                
// TODO Auto-generated method stub
                Intent intent = new Intent(ACTION_INTENT_TEST);
                sendBroadcast(intent);
            }
        });
    }
}
 


 


接收器代碼如下:


 
public class myBroadCast extends BroadcastReceiver {

     
    
public myBroadCast() {
        Log.v("BROADCAST_TAG""myBroadCast");
    }

    @Override
    
public void onReceive(Context context, Intent intent) {
        
// TODO Auto-generated method stub
        Log.v("BROADCAST_TAG""onReceive");
    }

}

 

Android 廣播的生命週期


在上面的接收器中,繼承了BroadcastReceiver 並重寫了它的onReceive 並構造了一個函數,下面通過圖片來一步一步認識 Android 廣播的生命週期。當我點擊一下按鈕,它向Android 發送了一個廣播,如下圖:





這時我們再點擊一下按鈕,它還是會再向 Android 系統發送廣播,此時日誌信息如下:





下面本人畫一張圖像,描述了Android 中廣播的生命週期,其次它並不像Activity 一樣複雜,運行原理很簡單如下圖:





下面來看一下SDK給出的解釋:





大意爲:如果一個廣播處理完onReceive 那麼系統將認定此對象將不再是一個活動的對象,也就會finished掉它。


至此,大家應該能明白 Android 的廣播生命週期的原理,代碼也不用多介紹,很簡單的一個發送廣播並處理廣播的Demo。


 Android 如何判斷並篩選廣播?


前
面說過 Android 的廣播有各式各樣,那麼Android 
系統是如何幫我們處理我們需要哪種廣播併爲我們提供相應的廣播服務呢?這裏有一點需要大家注意,每實現一個廣播接收類必須在我們應用程序中的 
manifest 中顯式的註明哪一個類需要廣播,併爲其設置過濾器,如下圖:





Tip:action 代表一個要執行的動作,在Andriod 中有很action 比如 ACTION_VIEW,ACTION_EDIT



那麼有些人會問了,如果我在一個廣播接收器中要處理多個動作呢?那要如何去處 理?
 
在Android 的接收器中onReceive 以經爲我們想到的,同樣的你必須在Intent-filter 裏面註冊該動作,可以是系統的廣播動作也可以是自己需要的廣播,之後你之需要在onReceive 方法中,通過intent.getAction()判斷傳進來的動作即可做出不同的處理,不同的動作。具體大家可以去嘗試測試一下。




小結:
 




  • 在Android 中如果要發送一個廣播必須使用sendBroadCast 向系統發送對其感興趣的廣播接收器中。
  • 使用廣播必須要有一個intent 對象必設置其action動作對象
  • 使用廣播必須在配置文件中顯式的指明該廣播對象
  • 每次接收廣播都會重新生成一個接收廣播的對象
  • 在BroadCast 中儘量不要處理太多邏輯問題,建議複雜的邏輯交給Activity 或者 Service 去處理

  Android廣播機制(兩種註冊方法) 

在android下,要想接受廣播信息,那麼這個廣播接收器就得我們自己來實現了,我們可以繼承BroadcastReceiver,就可以有一個廣播接受器了。有個接受器還不夠,我們還得重寫BroadcastReceiver裏面的onReceiver方法,當來廣播的時候我們要幹什麼,這就要我們自己 來實現,不過我們可以搞一個信息防火牆。具體的代碼:

public class SmsBroadCastReceiver extends BroadcastReceiver    
{   
  
    @Override  
    public void onReceive(Context context, Intent intent)   
    {   
        Bundle bundle = intent.getExtras();   
        Object[] object = (Object[])bundle.get("pdus");   
        SmsMessage sms[]=new SmsMessage[object.length];   
        for(int i=0;i<object.length;i++)   
        {   
            sms[0] = SmsMessage.createFromPdu((byte[])object[i]);   
            Toast.makeText(context, "來 自"+sms[i].getDisplayOriginatingAddress()+" 的消息是:"+sms[i].getDisplayMessageBody(), Toast.LENGTH_SHORT).show();   
        }   
        //終止廣播,在這裏我們可以稍微處理,根據用戶輸入的號碼可以實現短信防火牆。   
        abortBroadcast();   
    }   
       
}  

  當實現了廣播接收器,還要設置廣播接收器接收廣播信息的類型,這裏是信息:android.provider.Telephony.SMS_RECEIVED

  我們就可以把廣播接收器註冊到系統裏面,可以讓系統知道我們有個廣播接收器。這裏有兩種,一種是代碼動態註冊:

//生成廣播處理   
smsBroadCastReceiver = new SmsBroadCastReceiver();   
//實例化過濾器並設置要過濾的廣播   
IntentFilter intentFilter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED"); 

//註冊廣播   
BroadCastReceiverActivity.this.registerReceiver(smsBroadCastReceiver, intentFilter);  

一種是在AndroidManifest.xml中配 置廣播

<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
      package
="spl.broadCastReceiver"  
      android:versionCode
="1"  
      android:versionName
="1.0">  
    <application android:icon="@drawable/icon" android:label="@string/app_name">  
        <activity android:name=".BroadCastReceiverActivity"  
                  android:label
="@string/app_name">  
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />  
                <category android:name="android.intent.category.LAUNCHER" />  
            </intent-filter>  
        </activity>  
           
        <!--廣播註冊-->  
        <receiver android:name=".SmsBroadCastReceiver">  
            <intent-filter android:priority="20">  
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>  
            </intent-filter>  
        </receiver>  
           
    </application>  
       
    <uses-sdk android:minSdkVersion="7" />  
       
    <!-- 權限申請 -->  
    <uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>  
       
</manifest>   

  兩種註冊類型的區別是:

     1)第一種不是常駐型廣播,也就是說廣播跟隨程序的生命週期。

     2)第二種是常駐型,也就是說當應用程序關閉後,如果有信息廣播來,程序也會被系統調用自動運行。


BroadcastReceiver用於監聽被廣播的事件

必須被註冊,有兩種方法:

1、在應用程序的代碼中註冊

註冊BroadcastReceiver:

registerReceiver(receiver,filter);

取消註冊BroadcastReceiver:

unregisterReceiver(receiver);

當BroadcastReceiver更新UI,通常會使用這樣的方法註冊。啓動Activity時候註冊 BroadcastReceiver,Activity不可見時候,取消註冊。

2、在androidmanifest.xml當中註冊

<receiver>

    <intent-filter>

     <action android:name = "android.intent.action.PICK"/>

    </intent-filter>

</receiver>

使用這樣的方法註冊弊端:它會始終處於活動狀態,畢竟是手機開發,cpu和電源資源比較少,一直處於活動耗費大,不利。


10.    請解釋下在單線程模型中Message、Handler、Message Queue、Looper之間的關係。


1. Android進程
    在瞭解Android線程之前得先了解一下Android的進程。當一個程序第一次啓動的時候,Android會啓動一個LINUX進程和一個主線程。默 認的情況下,所有該程序的組件都將在該進程和線程中運行。
同 時,Android會爲每個應用程序分配一個單獨的LINUX用戶。Android會盡量保留一個正在運行進程,只在內存資源出現不足時,Android 會嘗試停止一些進程從而釋放足夠的資源給其他新的進程使用, 也能保證用戶正在訪問的當前進程有足夠的資源去及時地響應用戶的事件。Android會根據進程中運行的組件類別以及組件的狀態來判斷該進程的重要 性,Android會首先停止那些不重要的進程。按照重要性從高到低一共有五個級別:
前臺進程
前臺進程是用戶當前正在使用的進程。只有一些前臺進程可以在任何時候都存在。他們是最後一個被結束的,當內存低到根本連他們都不能運行的時候。一般來說, 在這種情況下,設備會進行內存調度,中止一些前臺進程來保持對用戶交互的響應。
可見進程
可見進程不包含前臺的組件但是會在屏幕上顯示一個可見的進程是的重要程度很高,除非前臺進程需要獲取它的資源,不然不會被中止。
服務進程
運 行着一個通過startService() 方法啓動的service,這個service不屬於上面提到的2種更高重要性的。service所在的進程雖然對用戶不是直接可見的,但是他們執行了用 戶非常關注的任務(比如播放mp3,從網絡下載數據)。只要前臺進程和可見進程有足夠的內存,系統不會回收他們。
後臺進程
運 行着一個對用戶不可見的activity(調用過 onStop() 方法).這些進程對用戶體驗沒有直接的影響,可以在服務進程、可見進程、前臺進 程需要內存的時候回收。通常,系統中會有很多不可見進程在運行,他們被保存在LRU (least recently used) 列表中,以便內存不足的時候被第一時間回收。如果一個activity正 確的執行了它的生命週期,關閉這個進程對於用戶體驗沒有太大的影響。
空進程
未運行任何程序組件。運行這些進程的唯一原因是作爲一個緩存,縮短下次程序需要重新使用的啓動時間。系統經常中止這些進程,這樣可以調節程序緩存和系統緩 存的平衡。
Android 對進程的重要性評級的時候,選取它最高的級別。另外,當被另外的一個進程依賴的時候,某個進程的級別可能會增高。一個爲其他進程服務的進程永遠不會比被服 務的進程重要級低。因爲服務進程比後臺activity進程重要級高,因此一個要進行耗時工作的activity最好啓動一個service來做這個工 作,而不是開啓一個子進程――特別是這個操作需要的時間比activity存在的時間還要長的時候。例如,在後臺播放音樂,向網上上傳攝像頭拍到的圖片, 使用service可以使進程最少獲取到“服務進程”級別的重要級,而不用考慮activity目前是什麼狀態。broadcast receivers做費時的工作的時候,也應該啓用一個服務而不是開一個線程。
2. 單線程模型
    當一個程序第一次啓動時,Android會同時啓動一個對應的主線程(Main Thread),主線程主要負責處理與UI相關的事件,如用戶的按鍵事件,用戶接觸屏幕的事件以及屏幕繪圖事件,並把相關的事件分發到對應的組件進行處 理。所以主線程通常又被叫做UI線程。在開發Android應用時必須遵守單線程模型的原則: Android UI操作並不是線程安全的並且這些操作必須在UI線程中執行。
2.1 子線程更新UI
Android的UI是單線程(Single-threaded)的。爲了避免拖住GUI,一些較費時的對象應該交給獨立的線程去執行。如果幕後的線程來 執行UI對象,Android就會發出錯誤訊息
CalledFromWrongThreadException。以後遇到這樣的異常拋出時就要知道怎麼回 事了!
2.2 Message Queue
     在單線程模型下,爲了解決類似的問題,Android設計了一個Message Queue(消息隊列), 線程間可以通過該Message Queue並結合Handler和Looper組件進行信息交換。下面將對它們進行分別介紹:
1. Message
    Message消息,理解爲線程間交流的信息,處理數據後臺線程需要更新UI,則發送Message內含一些數據給UI線程。
2. Handler
    Handler處理者,是Message的主要處理者,負責Message的發送,Message內容的執行處理。後臺線程就是通過傳進來的 Handler對象引用來sendMessage(Message)。而使用Handler,需要implement 該類的 handleMessage(Message)
方法,它是處理這些Message的操作內容,例如Update UI。通常需要子類化Handler來實現handleMessage方法。
3. Message Queue
    Message Queue消息隊列,用來存放通過Handler發佈的消息,按照先進先出執行。
    每個message queue都會有一個對應的Handler。Handler會向message queue通過兩種方法發送消息:sendMessage或post。這兩種消息都會插在message queue隊尾並按先進先出執行。但通過這兩種方法發送的消息執行的方式略有不同:通過sendMessage發送的是一個message對象,會被 Handler的handleMessage()函數處理;而通過post方法發送的是一個runnable對象,則會自己執行。
4. Looper
    Looper是每條線程裏的Message Queue的管家。Android沒有Global的Message Queue,而Android會自動替主線程(UI線程)建立Message Queue,但在子線程裏並沒有建立Message Queue。所以調用Looper.getMainLooper()得到的主線程的Looper不爲NULL,但調用Looper.myLooper() 得到當前線程的Looper就有可能爲NULL。
    對於子線程使用Looper,API Doc提供了正確的使用方法:


    這個Message機制的大概流程:
    1. 在Looper.loop()方法運行開始後,循環地按照接收順序取出Message Queue裏面的非NULL的Message。
    2. 一開始Message Queue裏面的Message都是NULL的。當Handler.sendMessage(Message)到Message Queue,該函數裏面設置了那個Message對象的target屬性是當前的Handler對象。隨後Looper取出了那個Message,則調用 該Message的target指向的Hander的dispatchMessage函數對Message進行處理。
    在dispatchMessage方法裏,如何處理Message則由用戶指定,三個判斷,優先級從高到低:
    1) Message裏面的Callback,一個實現了Runnable接口的對象,其中run函數做處理工作;
    2) Handler裏面的mCallback指向的一個實現了Callback接口的對象,由其handleMessage進行處理;
    3) 處理消息Handler對象對應的類繼承並實現了其中handleMessage函數,通過這個實現的handleMessage函數處理消息。
    由此可見,我們實現的handleMessage方法是優先級最低的!
    3. Handler處理完該Message (update UI) 後,Looper則設置該Message爲NULL,以便回收!
    在網上有很多文章講述主線程和其他子線程如何交互,傳送信息,最終誰來執行處理信息之類的,個人理解是最簡單的方法——判斷Handler對象裏面的 Looper對象是屬於哪條線程的,則由該線程來執行!
    1. 當Handler對象的構造函數的參數爲空,則爲當前所在線程的Looper;
    2. Looper.getMainLooper()得到的是主線程的Looper對象,Looper.myLooper()得到的是當前線程的Looper對 象。

現在來看一個例子,模擬從網絡獲取數據,加載到ListView的過程:

這個例子,我自己寫完後覺得還是有點亂,要稍微整理才能看明白線程間交互的過程以及數據的前後變化。隨後瞭解到AsyncTask類,相應修改後就很容易 明白了!


11.    AIDL的全稱是什麼?如何工作?能處理哪些類型的數據

詳情請參看:http://buaadallas.blog.51cto.com/399160/372090

部分概念:
Android每個應用程序都可以有自己的進程在寫UI應用的時候經常要用到Service. 在不同的進程中怎樣傳遞對象呢?  顯然, Java中不允許跨進程內存共享因此傳遞對象只能把對象拆分成操作系統能理解的簡單形式以達到跨界對象訪問的目的J2EE,採用RMI的方式可以通過序列化傳遞對象Android則採用AIDL的方式理論上AIDL可以傳遞Bundle,實際上做起來卻比較麻煩.



AIDL(AndRoid接口描述語言)是一種藉口描述語言編譯器可以通過aidl文 件生成一段代碼,通過預先定義的接口達到兩個進程內部通信進程的目的如 果需要在一個Activity訪問另一個Service中的某個對象需 要先將對象轉化成AIDL可識別的參數(可能是多個參數), 然後使用AIDL來 傳遞這些參數在消息的接收端使用這些參數組裝成自己需要的對象.

AIDLIPC的機制和COMCORBA類似是基於接口的,但它是輕量級的。它使用代理類在客戶端和實現層間傳遞值如果要使用AIDL, 需要完成2件 事情: 1. 引入AIDL的相關類.; 2. 調用aidl產生的class.

AIDL的 創建方法:

AIDL語 法很簡單,可以用來聲明一個帶一個或多個方法的接口,也可以傳 遞參數和返回值。 由於遠程調用的需要這些參數和返回值並不是任何 類型.下面是些AIDL支持的數據類型:

1. 不 需要import聲明的簡單Java編程語言類型(int,boolean)

2. String, CharSequence不需要特殊聲明

3. List, MapParcelables類 型, 這些類型內所包含的數據成員也只能是簡單數據類型, String等其他比支持的類型.

(另 外: 我沒嘗試Parcelables, 在Eclipse+ADT下編譯不過, 或許以後會有所支持).

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