2014年10月11日 星期六

[Android Telephony]框架結構簡析

原文出處: http://fanli7.net/a/JAVAbiancheng/shejimoshi/20130218/308209.html
Android Telephony涉及的框架結構如圖1所示。
圖1  Android Telephony框架結構
通過圖1可以發現Android Telephony框架結構的一些規律,具體如下。
  • Android Telephony的業務應用跨越了AP和BP。AP與BP相互通信,符合前面介紹的智能手機硬件基本結構。
  • Android系統在AP上運行,而Telephony運行在Linux Kernel之上的User Space空間。
  • Android Telephony也采用了分層結構的設計,共跨越了三層Java Applications、Java Frameworks和User Libraries層,與Android操作系統整體分層結構保持一致。
  • Android Telephony從上到下共分三層:Telephony應用、Telephony框架、RIL(Radio Interface Layer,無線通信接口層,主要位於UserLibraries層中的HAL層,接下來詳細介紹HAL)。
  • BP SoftWare在BP上運行,主要負責實際的無線通信能力處理。

1 系統運行庫層的HAL層

HAL(Hardware Abstraction Layer,硬件抽象層)在Linux和Windows操作系統平台下有不同的實現方式。
Windows下的HAL位於操作系統的最底層,它直接操作物理硬件設備,使用抽象接口來隔離不同硬件的具體實現,为上層的操作系統和設備驅動程序提供一個統一接口,起到對硬件的抽象作用。這样更換硬件時,編寫硬件的驅動只要實現符合HAL定義的標准接口即可,而上層應用並不會受到影響,不必關心具體來實現的是什麼硬件。
Linux 下的HAL與Windows下的HAL不太一样,HAL並不是位於操作系統的最底層直接操作硬件,相反,它位於操作系統核心層和驅動程序之上,是一個運行在User Space用戶空間中的服務程序。

2 簡析HAL結構

通過前面的學習,我們知道Android是基於Linux Kernel的開源智能手機操作系統,所以在這裏重點介紹Linux下的HAL,就不單獨介紹Windows下的HAL結構了。
要想知道HAL結構,先看看來自於HAL 0.4.0 Specification的框架圖吧,如圖1-4所示(引用自http://people.redhat.com/davidz/hal-spec/hal-spec.html)。
圖2 HAL 0.4.0 Specification框架結構
HAL是一個位於操作系統和驅動程序之上,運行在用戶空間中的服務程序。其目的是为上層應用提供一個統一的查詢硬件設備的接口。我們都知道,抽象就是为了隔離變化,那麼這裏的HAL可以帶给我們什麼?首先,有了HAL接口,可以提前開始應用的開發,而不必關心具體實現的是什麼硬件;其次,硬件廠家如果需要更改硬件設備,只要按照HAL接口規範和標准提供對應的硬件驅動,而不需要改變應用;最後,HAL簡化了應用程序查詢硬件的邏輯,把這一部分的复雜性轉移给HAL統一處理,這样當一些應用程序使用HAL時,可以把對不同硬件的實際操作的复雜性也交给不同硬件廠家提供的庫函數來處理。
總之,HAL所謂的抽象並不提供對硬件的實際操作,對硬件的操作仍然由具體的驅動程序來完成。

3 Android为什麼引入HAL

HAL的一些優勢在前面章節已經提到,這裏回顧一下。Android引入HAL不僅因为其自身的優勢,而且還有一個非常重要的原因,就是为了保障在Android平台基於Linux開發的硬件驅動和應用程序不必遵循GPL(General Public License)許可而保持封閉,這保障了更多廠家的利益。我們都知道,Linux Kernel是開源的而且遵循GPL許可證,根據GPL許可證規定,對源碼的任何修改都必須向社會開源。
那麼Android是如何做到的呢?Linux Kernel和Android的許可證不一样,Linux Kernel是GPL許可證,Android是ASL(Apache Software License)許可證。ASL許可證規定,可以隨意使用源碼,不必開源,所以建立在Android之上的硬件驅動和應用程序都可以保持封閉。也就是說,只要把關鍵的驅動處理相關的主要邏輯轉移到Android平台內,在LinuxKernel中僅保留基礎的通信功能,即使開源一部分代碼,對廠家來講也不會有什麼損失。
Google選擇了這样做,並且特意修改Kernel,原本應該包括在Linux Kernel中的某些驅動關鍵處理邏輯,被轉移到了HAL層之中而達到了不必開源的目的。

4 Android中HAL的運行結構

Android源碼中實現了一部分HAL,包括Wi-Fi、GPS、RIL、Sensor等,這些代碼主要儲存於以下目錄:
  • Android_src/hardware/libhardware_legacy:老式HAL結構,采用直接調用so動態鏈接庫方式;
  • Android_src/hardware/libhardware:新式HAL結構,采用Stub代理方式調用;
  • Android_src/hardware/ril:RIL(Radio Interface Layer,無線通信接口層),作为本書重點關注和學習的內容,後面將以獨立章節詳細講解。
在Android中,HAL的運行機制是什麼样的呢?它有兩種運行機制,老式HAL和新式HAL,如圖3所示。
圖3 Android中HAL兩種運行結構
從圖3中不難看出,左邊是老的HAL結構,應用或框架通過so動態鏈接庫調用而達到對硬件驅動的訪問。在so動態鏈接庫裏,實現了對驅動的訪問邏輯處理。我們重點學習和理解HAL Stub方式, RIL也采用了此方式的設計思想。
HAL Stub 是一種Proxy代理概念,Stub雖然仍是以 *.so 的形式存在,但 HAL 已經將 *.so 的具體實現隱藏了起來。 Stub 向 HAL 提供operations方法,Runtime通過Stub提供的so獲取它的 operations方法,並告知Runtime的callback方法。這样Runtime和Stub都有對方調用的方法,一個應用的請求通過Runtime調用Stub的operations方法,而Stub響應operations方法並完成後,再調用Runtime的callback方法進行返回。這裏可能有一點繞,根據前面的描述再結合圖4所示會更容易理解。
 
圖4   HAL Stub結構
上層通過HAL提供的functions調用底層硬件,而底層硬件處理完成上層請求後或硬件狀態發生變化後,HAL層通過Runtime提供的callback接口回調上層應用。
HAL Stub 有一種包含關系,即 HAL 裏包含了很多的 Stub。 Runtime 只要說明請求類型,就可以取得並操作Stub對應的operations。其實現主要在 hardware.c 和 hardware.h 文件中,實質也是通過dlopen方法加載 .so動態鏈接庫,從而呼叫 *.so 裏的符號( symbol )實現。
------------------------------
本文節選自《深入理解Android : Telephony原理剖析與最佳實踐》,作者:楊青平。

2014年10月4日 星期六

[Telecom] UICC 架構

UICC的架構圖如下:
從圖中可以看出,UiccController是用來控制所有與卡相關的操作,通過UiccController,我們可以訪問IccRecords(SIM卡相關), CatService(STK相關),IccFileHandle(讀取SIM卡文件)。

[Android] 手機應用介面大全

Android手機應用介面大全

顯示網頁:

  1. Uri uri = Uri.parse("http://www.google.com");
  2. Intent it = new Intent(Intent.ACTION_VIEW,uri);
  3. startActivity(it);

顯示地圖:

  1. Uri uri = Uri.parse("geo:38.899533,-77.036476");
  2. Intent it = new Intent(Intent.Action_VIEW,uri);
  3. startActivity(it);

路徑規劃:

  1. Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=startLat%20startLng&daddr=endLat%20endLng&hl=en");
  2. Intent it = new Intent(Intent.ACTION_VIEW,URI);
  3. startActivity(it);

撥打電話:
調用撥號程式

  1. Uri uri = Uri.parse("tel:xxxxxx");
  2. Intent it = new Intent(Intent.ACTION_DIAL, uri);   
  3. startActivity(it);   

  1. Uri uri = Uri.parse("tel.xxxxxx");
  2. Intent it =new Intent(Intent.ACTION_CALL,uri);
  3. 要使用這個必須在設定檔中加入

發送SMS/MMS
調用發送短信的程式

  1. Intent it = new Intent(Intent.ACTION_VIEW);
  2. it.putExtra("sms_body", "The SMS text");
  3. it.setType("vnd.android-dir/mms-sms");
  4. startActivity(it);   

發送短信

  1. Uri uri = Uri.parse("smsto:0800000123");
  2. Intent it = new Intent(Intent.ACTION_SENDTO, uri);
  3. it.putExtra("sms_body", "The SMS text");
  4. startActivity(it);   

發送彩信

  1. Uri uri = Uri.parse("content://media/external/images/media/23");
  2. Intent it = new Intent(Intent.ACTION_SEND);
  3. it.putExtra("sms_body", "some text");
  4. it.putExtra(Intent.EXTRA_STREAM, uri);
  5. it.setType("image/png");
  6. startActivity(it);

發送Email

  1.
  2. Uri uri = Uri.parse("mailto:xxx@abc.com");
  3. Intent it = new Intent(Intent.ACTION_SENDTO, uri);
  4. startActivity(it);

  1. Intent it = new Intent(Intent.ACTION_SEND);
  2. it.putExtra(Intent.EXTRA_EMAIL, "me@abc.com");
  3. it.putExtra(Intent.EXTRA_TEXT, "The email body text");
  4. it.setType("text/plain");
  5. startActivity(Intent.createChooser(it, "Choose Email Client"));   

  1. Intent it=new Intent(Intent.ACTION_SEND);   
  2. String[] tos={"me@abc.com"};   
  3. String[] ccs={"you@abc.com"};   
  4. it.putExtra(Intent.EXTRA_EMAIL, tos);   
  5. it.putExtra(Intent.EXTRA_CC, ccs);   
  6. it.putExtra(Intent.EXTRA_TEXT, "The email body text");   
  7. it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");   
  8. it.setType("message/rfc822");   
  9. startActivity(Intent.createChooser(it, "Choose Email Client"));

添加附件

  1. Intent it = new Intent(Intent.ACTION_SEND);
  2. it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");
  3. it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/mysong.mp3");
  4. sendIntent.setType("audio/mp3");
  5. startActivity(Intent.createChooser(it, "Choose Email Client"));

播放多媒體

  1.   
  2. Intent it = new Intent(Intent.ACTION_VIEW);
  3. Uri uri = Uri.parse("file:///sdcard/song.mp3");
  4. it.setDataAndType(uri, "audio/mp3");
  5. startActivity(it);

  1. Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1");
  2. Intent it = new Intent(Intent.ACTION_VIEW, uri);
  3. startActivity(it);   

Uninstall 程式

  1. Uri uri = Uri.fromParts("package", strPackageName, null);
  2. Intent it = new Intent(Intent.ACTION_DELETE, uri);
  3. startActivity(it);

Android 源碼分析-打電話和發短信
  1. ITelephony介面和ISms介面以及AIDL
 在我們的Android應用中,當需要實現電話撥號時,我們需要進行如下調用
 ITelephony phone = (ITelephony)ITelephony.Stub.asInterface(ServiceManager.getService("phon"))
 phone.dial("10086");
   對於短信應用,我們需要的是調用SmsManager,代碼如下
   SmsManager manager = SmsManager.getDefault();
   manager.sendTextMessage("10086",null,"hi,this is sms",null,null);
   這裡,SmsManagerISms做了一層包裝,實質上是通過調用
   ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
   simISms.sendRawPdu....
   
  可以看到,應用都是採用AIDL來實現IPC的跨進程調度。
  對於AIDL應用,調用進程方存在的是一個實現介面的Proxy物件,通過Proxy物件與被調用進程中的Stub物件進行
  通訊來實現IPC的跨進程調度,所以,在被調用進程一端必定有一個ITelephony.Stub類以及ISms.Stub類的實現。

  2. PhoneInterfaceManagerSimSmsInterfaceManager
   ITelephony.Stub 的實現類為com.android.phone.PhoneInterfaceManager
   ISms.Stub的實現類為com.android.internal.telephony.gsm.SimSmsInterfaceManager
   從這兩個類的構造器的調用代碼裡可以很清楚的看到進行了Service的註冊工作
   ServiceManager.addService("phone",this);
   ServiceManager.addService("isms",this);

  3. PhoneApp,InCallScreen,PhoneUtils及其他相關類
  從SimSmsInteferManager的相關方法實現中可以看到,具體就是調用GSMPhoneSmsDispatcher實例來進行相關操作的。
  從PhoneInterfaceManager會維持一個Phone物件的引用,當撥號應用時,PhoneInterfaceManager會將構造好的Intent傳遞
PhoneApp應用,該IntentclassName指定則為InCallScreen,從中我們可以看到InCallScreen具體是通過PhoneUtils調用Phone
相關方法來實現。
  PhoneInterfaceManager怎麼獲取到對應的Phone物件,然後又怎麼將Phone物件傳遞到InCallScreen中呢?
  具體這裡涉及到了PhoneApp這個類,從這個類維護了一個 PhoneInterfaceManager的引用(phoneMgr)以及一個Phone引用(phone)
從該類的onCreate方法中我們可以清楚的看到,PhoneApp通過PhoneFactory獲取了一個Phone實例,並通過該實例實現了PhoneInterfaceManager物件。
因此,我們現在只需要關注PhoneFactory具體提供的是一個什麼樣的Phone實例了。
  另外,PhoneApp類還提供了一個靜態方法getInstanceInCallScreen調用,InCallScreen正是通過調用該方法獲得PhoneApp實例從而
獲得對應的Phone實例的。
  接下來,我們通過查看PhoneFactory的方法可以看到,Phone物件對應的就是一個GSMPhone實例。

  4GSMPhoneRIL
  從GSM的構造器可以看出,他依賴一個CommandInterface介面實例,通過PhoneFactorymakeDefaultPhones方法,我們可以看到,根據系統環境變數
ro.radio.noril來判斷是否需要採用RIL框架實現,如果該參數不為空,則採用Simultedcommands(主要是為了測試需要提供的類比實現).否則,採用RIL
  通過Google才知道,RIL其實是智慧手機上實現APBP之間通信的一種設計思想,具體大家可以參見這篇文章http://www.eetchina.com/ARTICLES ... DF?SOURCES=DOWNLOAD
  在RIL.java 中我們很驚喜的看到,RIL對對消息的處理是將消息通過LocalSocket發送到以rild為名稱的有名埠。這個有名Socket的創建在ril.cpp代碼中。
  s_fdListen = android_get_control_socket(SOCKET_NAME_RIL)
  原來Android通話和發短信的應用是JAVAC++代碼之間透過Socket連接來傳輸消息來實現的。

  5.關於C代碼與硬體之間的交互
  這部分工作其實就是C代碼通過串口發送AT指令來撥號,收發短信。


[Android] Android Manifest 詳解

文章來源:https://code.google.com/p/androidbmi/wiki/AndroidManifest

在執行「startActivity」函式時,應用程式並不是直接呼叫另一個 Activity,而是將「Intent」(意圖)傳進 Android 框架中。 Android 框架會查看 「startActivity」 呼叫所傳入的動作與 Intent 內容是否在註冊表中,如果符合,就啟動對應的服務或 Activity。
Android 系統中的每一個應用程式,在安裝的過程裡,都得事先在 Android 框架中註冊、登記這個應用程式所建立的 Activity,並事先註明會使用到的服務。譬如當我們在 Android 上安裝我們撰寫的 BMI 應用程式時,BMI 應用程式就會向 Android 框架登記相關資訊:BMI 應用程式將會用到 「Bmi」這個 Activity。
這份訊息存在於每個 Android 應用程式專案根目錄下的「AndroidManifest.xml」檔案中。如果我們在程式裡,要用到其他應用程式或服務所提供的功能,也需一併在此列出。
在安裝應用程式的時候,Android 框架會根據應用程式提供的這份清單,將資訊註冊於 Android 框架的註冊表中。
備註:
這麼說其實是不太精確的。Android 應用程式的運作流程,存在於四種載體中:
  1. Activity (活動)
  2. Broadcast Intent Receiver
  3. Service
  4. Content Provider
各種載體的相關內容會在後續章節提到時作解說。

預設的 Activity 清單

我們使用 eclipse Android 開發工具打開「BMI/AndroidManifest.xml」檔案。切換到「AndroidManifest.xml」分頁標籤,查看預設的 「BMI/AndroidManifest.xml」檔案原始碼:
1  xml version="1.0" encoding="utf-8"?>
2  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3      package="com.demo.android.bmi"
4      android:versionCode="1"
5      android:versionName="1.0">
6      <application android:icon="@drawable/icon">
7          <activity android:name=".Bmi" android:label="@string/app_name">
8              <intent-filter>
9                  <action android:name="android.intent.action.MAIN" />
10                 <category android:name="android.intent.category.LAUNCHER" />
11            </intent-filter>
12          </activity>
13     </application>
14     >
15 </manifest>
我們分行講解如下:
xml version="1.0" encoding="utf-8"?>
 xmlns:android="http://schemas.android.com/apk/res/android"
    ....>
    ....
AndroidManifest.xml」這個檔案也是以 XML 格式描述,每個 Android 應用程式都需要一個「AndroidManifest.xml」檔案,每份「AndroidManifest.xml」檔案的開頭都會出現這段敘述。而整個「AndroidManifest.xml」檔案的敘述,都包含在「manifest」(清單)這個主要標籤中。
package="com.demo.android.bmi"
「package」 是「manifest」(清單)標籤的一個特別屬性,範例中的內容可用來標明,這個應用程式的進入點存在於「com.demo.android.bmi」這個名稱空間/路徑中。
android:versionCode="1"
android:versionName="1.0"
「android:versionCode」和「android:versionName」是應用程式版本號。 這兩個屬性是可選的(非必要)。 「android:versionName」是給使用者看的版本號,如「1.0」、「2.0」。「android:versionCode」則是開發者用的內部版本號,一般使用流水號。
 android:icon="@drawable/icon" android:label="@string/app_name">
...
「manifest」標籤中主要包含一個「application」標籤(備註1)。「application」標籤裡面,定義了所有這個應用程式用到的 Activity、服務等資訊。「application」標籤中的「android:icon」屬性,定義了這個應用程式將顯示在 Android 主畫面中的應用程式圖示。「android:icon="@drawable/icon"」表示應用程式圖示的資源檔存在於 「res/drawable/icon」 中。圖示的大小必須超過 64x64 像素(Pixel)。「application」標籤中的「android:label」屬性可用來指定應用程式將顯示在 Home 主畫面上的名稱。也就是預設剛開好機時,可以從桌面下方拉出的應用程式列表。
 android:name=".Bmi" android:label="@string/app_name">
...
「application」標籤中所有用到的 Activity ,都要包含在一個個「activity」標籤中(備註2)。 Activity 是 Android 應用程式與使用者互動的主要元素,當使用者開啟一個應用程式,第一個看到的畫面就是一個 Activity。若是一個應用程式中包含多個畫面時,會定義多個不同的 Activity,我們也必須在「application」標籤中,使用多個「activity」標籤,為不同的 Activity 添加描述。如果我們已經在程式碼中定義好了 Activity ,卻忘了在「AndroidManifest.xml」檔案中加入對應的「activity」標籤,那麼在執行中呼叫到這個 Activity 的時候,將無法開啟這個 Activity。
「activity」標籤的「android:name」屬性,指出了這個 Activity 所對應的類別(class)。「activity」標籤中的「android:label」屬性可用來指定應用程式將顯示在 Activity 畫面上方的名稱。也可以在程式碼中透過「setTitle(“名稱”)」來動態修改。
因為在上一層「Manifest」標籤屬性中已經定義了「package="com.demo.android.bmi"」,因此在「activity」標籤的 「android:name」屬性中,「.Bmi」代表著「com.demo.android.bmi.Bmi」的簡寫。也可以寫成「Bmi」,一樣是代表「com.demo.android.bmi.Bmi」這個類別。

     android:name="android.intent.action.MAIN" />
     android:name="android.intent.category.LAUNCHER" />
「intent-filter」標籤定義了這個「activity」的性質。 「intent-filter」中主要包含了兩個標籤:「action」跟「category」標籤。 「action」標籤中的「android:name」屬性,其內容「android.intent.action.MAIN」表示:這個 Activity 是此應用程式的進入點(就像程式中常見的 main 主程式),開啟這個應用程式時,應先執行這個 Activity。 。常見的還有「android.intent.action.EDIT」等標籤,會在之後章節用上的時候講解。「category」標籤中的「android:name」屬性,其內容「android.intent.category.LAUNCHER」表示:這個 Activity 將顯示在 Launcher 的應用程式列表中。
我們把整個檔案合到一起看,可以總結出這個檔案所傳達的訊息:在「com.demo.android.bmi」路徑下的「Bmi.java」這個檔案中,已定義了一個主要的 Activity; 當我們打開 Android 的時候,顯示的是位於「res/drawable/icon」的圖示。一旦我們按下圖示來啟動這個應用程式,Android 應用程式框架會去尋找到定義了「android.intent.action.MAIN」內容的 「.Bmi」activity,並呼叫執行。
 android:minSdkVersion="X" />
Android SDK 1.1 版之後引入了這條敘述,「X」代表了目標版本代號。透過指定這個參數,系統可以依此辨別應用程式是否使用相容的 SDK 版本,好決定能否在這台機器上安裝執行。這也是一個可選填的選項。但如果我們的應用程式要發佈出去,一些強勢的通路如 Google Android Market 已規定所有新發佈的應用程式必須指定「android:minSdkVersion」這個參數。