前言
本文內(nèi)容來自 Google 開發(fā)者平臺(tái)上的 Android 指南
應(yīng)用基礎(chǔ)知識(shí) | Android Developer
Android 應(yīng)用在系統(tǒng)內(nèi)的存在方式
Android SDK 工具將代碼,連同任何數(shù)據(jù)和資源文件,編譯到一個(gè) APK:Android 軟件包,即帶有 .apk 后綴的存檔文件中。一個(gè) APK 文件包含 Android 應(yīng)用的所有內(nèi)容,它是基于 Android 系統(tǒng)的設(shè)備用來安裝應(yīng)用的文件。
安裝到設(shè)備后,每個(gè) Android 應(yīng)用都運(yùn)行在自己的安全沙箱內(nèi):
Android 操作系統(tǒng)是一種多用戶 Linux 系統(tǒng),其中的每個(gè)應(yīng)用都是一個(gè)不同的用戶;
默認(rèn)情況下,系統(tǒng)會(huì)為每個(gè)應(yīng)用分配一個(gè)唯一的 Linux 用戶 ID(該 ID 僅由系統(tǒng)使用,應(yīng)用并不知曉)。系統(tǒng)為應(yīng)用中的所有文件設(shè)置權(quán)限,使得只有分配給該應(yīng)用的用戶 ID 才能訪問這些文件;
每個(gè)進(jìn)程都具有自己的虛擬機(jī) (VM),因此應(yīng)用代碼是在與其他應(yīng)用隔離的環(huán)境中運(yùn)行;
默認(rèn)情況下,每個(gè)應(yīng)用都在其自己的 Linux 進(jìn)程內(nèi)運(yùn)行。Android 會(huì)在需要執(zhí)行任何應(yīng)用組件時(shí)啟動(dòng)該進(jìn)程,然后在不再需要該進(jìn)程或系統(tǒng)必須為其他應(yīng)用恢復(fù)內(nèi)存時(shí)關(guān)閉該進(jìn)程。
Android 系統(tǒng)可以通過這種方式實(shí)現(xiàn)最小權(quán)限原則。也就是說,默認(rèn)情況下,每個(gè)應(yīng)用都只能訪問執(zhí)行其工作所需的組件,而不能訪問其他組件。這樣便營造出一個(gè)非常安全的環(huán)境,在這個(gè)環(huán)境中,應(yīng)用無法訪問系統(tǒng)中其未獲得權(quán)限的部分。
不過,應(yīng)用仍然可以通過一些途徑與其他應(yīng)用共享數(shù)據(jù)以及訪問系統(tǒng)服務(wù):
可以安排兩個(gè)應(yīng)用共享同一 Linux 用戶 ID,在這種情況下,它們能夠相互訪問彼此的文件。 為了節(jié)省系統(tǒng)資源,可以安排具有相同用戶 ID 的應(yīng)用在同一 Linux 進(jìn)程中運(yùn)行,并共享同一 VM(應(yīng)用還必須使用相同的證書簽署)。
應(yīng)用可以請(qǐng)求訪問設(shè)備數(shù)據(jù)(如用戶的聯(lián)系人、短信、可裝載存儲(chǔ)裝置 [SD 卡]、相機(jī)、藍(lán)牙等)的權(quán)限。 用戶必須明確授予這些權(quán)限。
應(yīng)用組件
應(yīng)用組件是 Android 應(yīng)用的基本構(gòu)建基塊。每個(gè)組件都是一個(gè)不同的入口點(diǎn),系統(tǒng)可以通過它進(jìn)入應(yīng)用。 并非所有組件都是用戶的實(shí)際入口點(diǎn),有些組件相互依賴,但每個(gè)組件都以獨(dú)立實(shí)體形式存在,并發(fā)揮特定作用 — 每個(gè)組件都是唯一的構(gòu)建基塊,有助于定義應(yīng)用的總體行為。
共有四種不同的應(yīng)用組件類型。每種類型都服務(wù)于不同的目的,并且具有定義組件的創(chuàng)建和銷毀方式的不同生命周期。
以下便是這四種應(yīng)用組件類型:
Activity
Activity 表示具有用戶界面的單一屏幕。例如,電子郵件應(yīng)用可能具有一個(gè)顯示新電子郵件列表的 Activity、一個(gè)用于撰寫電子郵件的 Activity 以及一個(gè)用于閱讀電子郵件的 Activity。 每一個(gè) Activity 都獨(dú)立于其他 Activity 而存在。 因此,其他應(yīng)用可以啟動(dòng)其中任何一個(gè) Activity(如果電子郵件應(yīng)用允許)。 例如,相機(jī)應(yīng)用可以啟動(dòng)電子郵件應(yīng)用內(nèi)用于撰寫新電子郵件的 Activity,以便用戶共享圖片。
服務(wù)
服務(wù)是一種在后臺(tái)運(yùn)行的組件,用于執(zhí)行長時(shí)間運(yùn)行的操作或?yàn)檫h(yuǎn)程進(jìn)程執(zhí)行作業(yè)。 服務(wù)不提供用戶界面。 例如,當(dāng)用戶位于其他應(yīng)用中時(shí),服務(wù)可能在后臺(tái)播放音樂或者通過網(wǎng)絡(luò)獲取數(shù)據(jù),但不會(huì)阻斷用戶與 Activity 的交互。 諸如 Activity 等其他組件可以啟動(dòng)服務(wù),讓其運(yùn)行或與其綁定以便與其進(jìn)行交互。
內(nèi)容提供程序
內(nèi)容提供程序管理一組共享的應(yīng)用數(shù)據(jù)。你可以將數(shù)據(jù)存儲(chǔ)在文件系統(tǒng)、SQLite 數(shù)據(jù)庫、網(wǎng)絡(luò)上或應(yīng)用可以訪問的任何其他永久性存儲(chǔ)位置。 其他應(yīng)用可以通過內(nèi)容提供程序查詢數(shù)據(jù),甚至修改數(shù)據(jù)(如果內(nèi)容提供程序允許)。 例如,Android 系統(tǒng)可提供管理用戶聯(lián)系人信息的內(nèi)容提供程序。 因此,任何具有適當(dāng)權(quán)限的應(yīng)用都可以查詢內(nèi)容提供程序的某一部分(如 ContactsContract.Data),以讀取和寫入有關(guān)特定人員的信息。內(nèi)容提供程序也適用于讀取和寫入應(yīng)用不共享的私有數(shù)據(jù)。 例如,記事本示例應(yīng)用使用內(nèi)容提供程序來保存筆記。
內(nèi)容提供程序作為 ContentProvider 的子類實(shí)現(xiàn),并且必須實(shí)現(xiàn)讓其他應(yīng)用能夠執(zhí)行事務(wù)的一組標(biāo)準(zhǔn) API。
廣播接收器
廣播接收器是一種用于響應(yīng)系統(tǒng)范圍廣播通知的組件。 許多廣播都是由系統(tǒng)發(fā)起的 — 例如,通知屏幕已關(guān)閉、電池電量不足或已拍攝照片的廣播。應(yīng)用也可以發(fā)起廣播 — 例如,通知其他應(yīng)用某些數(shù)據(jù)已下載至設(shè)備,并且可供其使用。 盡管廣播接收器不會(huì)顯示用戶界面,但它們可以創(chuàng)建狀態(tài)欄通知,在發(fā)生廣播事件時(shí)提醒用戶。 但廣播接收器更常見的用途只是作為通向其他組件的“通道”,設(shè)計(jì)用于執(zhí)行極少量的工作。 例如,它可能會(huì)基于事件發(fā)起一項(xiàng)服務(wù)來執(zhí)行某項(xiàng)工作。
廣播接收器作為 BroadcastReceiver 的子類實(shí)現(xiàn),并且每條廣播都作為 Intent 對(duì)象進(jìn)行傳遞。
Android 系統(tǒng)設(shè)計(jì)的獨(dú)特之處在于,任何應(yīng)用都可以啟動(dòng)其他應(yīng)用的組件。 例如,如果想讓用戶使用設(shè)備的相機(jī)拍攝照片,很可能有另一個(gè)應(yīng)用可以執(zhí)行該操作,那么你的應(yīng)用就可以利用該應(yīng)用,而不是開發(fā)一個(gè) Activity 來自行拍攝照片。
當(dāng)系統(tǒng)啟動(dòng)某個(gè)組件時(shí),會(huì)啟動(dòng)該應(yīng)用的進(jìn)程(如果尚未運(yùn)行),并實(shí)例化該組件所需的類。 例如,如果你的應(yīng)用啟動(dòng)相機(jī)應(yīng)用中拍攝照片的 Activity,則該 Activity 會(huì)在屬于相機(jī)應(yīng)用的進(jìn)程,而不是你的應(yīng)用的進(jìn)程中運(yùn)行。因此,與大多數(shù)其他系統(tǒng)上的應(yīng)用不同,Android 應(yīng)用并沒有單一入口點(diǎn)(例如,沒有 main() 函數(shù))。
由于系統(tǒng)在單獨(dú)的進(jìn)程中運(yùn)行每個(gè)應(yīng)用,且其文件權(quán)限會(huì)限制對(duì)其他應(yīng)用的訪問,因此你的應(yīng)用無法直接啟動(dòng)其他應(yīng)用中的組件。因此,要想啟動(dòng)其他應(yīng)用中的組件,你必須向系統(tǒng)傳遞一則消息,說明你想啟動(dòng)特定組件的 Intent,系統(tǒng)隨后便會(huì)為你啟動(dòng)該組件。
啟動(dòng)組件
四種組件類型中的三種 — Activity、服務(wù)和廣播接收器 — 通過名為 Intent 的異步消息進(jìn)行啟動(dòng)。Intent 會(huì)在運(yùn)行時(shí)將各個(gè)組件相互綁定(可以將 Intent 視為從其他組件請(qǐng)求操作的信使),無論組件屬于你的應(yīng)用還是其他應(yīng)用。
Intent 使用 Intent 對(duì)象創(chuàng)建,它定義的消息用于啟動(dòng)特定組件或特定類型的組件 — Intent 可以是顯式的,也可以是隱式的。
對(duì)于 Activity 和服務(wù), Intent 定義要執(zhí)行的操作(例如,“查看”或“發(fā)送”某個(gè)內(nèi)容),并且可以指定要執(zhí)行操作的數(shù)據(jù)的 URI(以及正在啟動(dòng)的組件可能需要了解的信息)。 例如, Intent 傳達(dá)的請(qǐng)求可以是啟動(dòng)一個(gè)顯示圖像或打開網(wǎng)頁的 Activity。 在某些情況下,你可以啟動(dòng) Activity 來接收結(jié)果,在這種情況下,Activity 也會(huì)在 Intent 中返回結(jié)果(例如,你可以發(fā)出一個(gè) Intent,讓用戶選取某位聯(lián)系人并將其返回,返回 Intent 包括指向所選聯(lián)系人的 URI)。
對(duì)于廣播接收器, Intent 只會(huì)定義要廣播的通知(例如,指示設(shè)備電池電量不足的廣播只包括指示“電池電量不足”的已知操作字符串)。
Intent 不會(huì)啟動(dòng)另一個(gè)組件類型 - 內(nèi)容提供程序,后者會(huì)在成為 ContentResolver 的請(qǐng)求目標(biāo)時(shí)啟動(dòng)。 內(nèi)容解析程序通過內(nèi)容提供程序處理所有直接事務(wù),使得通過提供程序執(zhí)行事務(wù)的組件可以無需執(zhí)行事務(wù),而是改為在 ContentResolver 對(duì)象上調(diào)用方法。 這會(huì)在內(nèi)容提供程序與請(qǐng)求信息的組件之間留出一個(gè)抽象層(以確保安全)。
每種類型的組件有不同的啟動(dòng)方法:
可以通過將 Intent 傳遞到 startActivity() 或 startActivityForResult()(需要 Activity 返回結(jié)果時(shí))來啟動(dòng) Activity(或?yàn)槠浒才判氯蝿?wù))。
可以通過將 Intent 傳遞到 startService() 來啟動(dòng)服務(wù)(或?qū)?zhí)行中的服務(wù)下達(dá)新指令)。 或者,也可以通過將 Intent 傳遞到 bindService() 來綁定到該服務(wù)。
可以通過將 Intent 傳遞到 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast() 等方法來發(fā)起廣播;
可以通過在 ContentResolver 上調(diào)用 query() 來對(duì)內(nèi)容提供程序執(zhí)行查詢。
清單文件
在 Android 系統(tǒng)啟動(dòng)應(yīng)用組件之前,系統(tǒng)必須通過讀取應(yīng)用的 AndroidManifest.xml 文件(“清單”文件)確認(rèn)組件存在。 應(yīng)用必須在此文件中聲明其所有組件,該文件必須位于應(yīng)用項(xiàng)目目錄的根目錄中。
除了聲明應(yīng)用的組件外,清單文件還有許多其他作用,如:
確定應(yīng)用需要的任何用戶權(quán)限,如互聯(lián)網(wǎng)訪問權(quán)限或?qū)τ脩袈?lián)系人的讀取權(quán)限
根據(jù)應(yīng)用使用的 API,聲明應(yīng)用所需的最低 API 級(jí)別
聲明應(yīng)用使用或需要的硬件和軟件功能,如相機(jī)、藍(lán)牙服務(wù)或多點(diǎn)觸摸屏幕
應(yīng)用需要鏈接的 API 庫(Android 框架 API 除外),如 Google 地圖庫
其他功能
聲明組件
清單文件的主要任務(wù)是告知系統(tǒng)有關(guān)應(yīng)用組件的信息。例如,清單文件可以像下面這樣聲明 Activity:
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:icon="@drawable/app_icon.png" ... >
<activity android:name="com.example.project.ExampleActivity"
android:label="@string/example_label" ... >
</activity>
...
</application>
</manifest>
在 <application> 元素中,android:icon 屬性指向標(biāo)識(shí)應(yīng)用的圖標(biāo)所對(duì)應(yīng)的資源。
在 <activity> 元素中,android:name 屬性指定 Activity 子類的完全限定類名,android:label 屬性指定用作 Activity 的用戶可見標(biāo)簽的字符串。
必須通過以下方式聲明所有應(yīng)用組件:
Activity 的
<activity>元素服務(wù)的
<service>元素廣播接收器的
<receiver>元素內(nèi)容提供程序的
<provider>元素
包括在源代碼中,但未在清單文件中聲明的 Activity、服務(wù)和內(nèi)容提供程序?qū)ο到y(tǒng)不可見,因此也永遠(yuǎn)不會(huì)運(yùn)行。 不過,廣播接收器可以在清單文件中聲明或在代碼中動(dòng)態(tài)創(chuàng)建(如 BroadcastReceiver 對(duì)象)并通過調(diào)用 registerReceiver() 在系統(tǒng)中注冊。
聲明組件功能
如上文啟動(dòng)組件中所述,可以使用 Intent 來啟動(dòng) Activity、服務(wù)和廣播接收器。通過在 Intent 中顯式命名目標(biāo)組件(使用組件類名)來執(zhí)行此操作。當(dāng)然也可以使用隱式 Intent。隱式 Intent 的作用無非是描述要執(zhí)行的操作類型(還可選擇指定要執(zhí)行的操作所針對(duì)的數(shù)據(jù)),讓系統(tǒng)能夠在設(shè)備上找到可執(zhí)行該操作的組件,并啟動(dòng)該組件。 如果有多個(gè)組件可以執(zhí)行 Intent 所描述的操作,則由用戶選擇使用哪一個(gè)組件。
系統(tǒng)通過將接收到的 Intent 與設(shè)備上的其他應(yīng)用的清單文件中提供的 Intent 過濾器進(jìn)行比較來確定可以響應(yīng) Intent 的組件。
在清單文件中聲明 Activity 時(shí),可以選擇性地加入聲明 Activity 功能的 Intent 過濾器,以便響應(yīng)來自其他應(yīng)用的 Intent。 你可以通過將 <intent-filter> 元素作為組件聲明元素的子項(xiàng)進(jìn)行添加來為你的組件聲明 Intent 過濾器。
例如,電子郵件應(yīng)用包含一個(gè)用于撰寫新電子郵件的 Activity,則可以像下面這樣聲明一個(gè) Intent 過濾器來響應(yīng) “send” Intent(以發(fā)送新電子郵件):
<manifest ... >
...
<application ... >
<activity android:name="com.example.project.ComposeEmailActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<data android:type="*/*" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
然后,如果另一個(gè)應(yīng)用創(chuàng)建了一個(gè)包含 ACTION_SEND 操作的 Intent,并將其傳遞到 startActivity(),則系統(tǒng)可能會(huì)啟動(dòng)該 Activity,以便用戶能夠草擬并發(fā)送電子郵件。
聲明應(yīng)用要求
基于 Android 系統(tǒng)的設(shè)備多種多樣,并非所有設(shè)備都提供相同的特性和功能。 為防止將應(yīng)用安裝在缺少應(yīng)用所需特性的設(shè)備上,你必須通過在清單文件中聲明設(shè)備和軟件要求,為應(yīng)用支持的設(shè)備類型明確定義一個(gè)配置文件。 其中的大多數(shù)聲明只是為了提供信息,系統(tǒng)不會(huì)讀取它們,但 Google Play 等外部服務(wù)會(huì)讀取它們,以便當(dāng)用戶在其設(shè)備中搜索應(yīng)用時(shí)為用戶提供過濾功能。
例如,如果你的應(yīng)用需要相機(jī),并使用 Android 2.1(API 級(jí)別 7)中引入的 API,則應(yīng)該像下面這樣在清單文件中以要求形式聲明這些信息:
<manifest ... >
<uses-feature android:name="android.hardware.camera.any"
android:required="true" />
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
...
</manifest>
現(xiàn)在,沒有相機(jī)且 Android 版本低于 2.1 的設(shè)備將無法從 Google Play 安裝該應(yīng)用。
不過,你也可以聲明使用相機(jī),但并不要求必須使用。 在這種情況下,你必須將 required 屬性設(shè)置為 "false",并在運(yùn)行時(shí)檢查設(shè)備是否具有相機(jī),然后根據(jù)需要停用任何相機(jī)功能。
應(yīng)用資源
Android 應(yīng)用并非只包含代碼,它還需要與源代碼分離的資源,如圖像、音頻文件以及任何與應(yīng)用的視覺呈現(xiàn)有關(guān)的內(nèi)容。 例如,你應(yīng)該通過 XML 文件定義 Activity 用戶界面的動(dòng)畫、菜單、樣式、顏色和布局。使用應(yīng)用資源能夠在不修改代碼的情況下輕松地更新應(yīng)用的各種特性,并可通過提供備用資源集讓你能夠針對(duì)各種設(shè)備配置(如不同的語言和屏幕尺寸)優(yōu)化你的應(yīng)用。
對(duì)于 Android 項(xiàng)目中包括的每一項(xiàng)資源,SDK 構(gòu)建工具都會(huì)定義一個(gè)唯一的整型 ID,可以利用它來引用應(yīng)用代碼或 XML 中定義的其他資源中的資源。 例如,如果應(yīng)用包含一個(gè)名為 logo.png 的圖像文件(保存在 res/drawable/ 目錄中),則 SDK 工具會(huì)生成一個(gè)名為 R.drawable.logo 的資源 ID,你可以利用它來引用該圖像并將其插入到用戶界面。
提供與源代碼分離的資源的其中一個(gè)最重要優(yōu)點(diǎn)在于,你可以提供針對(duì)不同設(shè)備配置的備用資源。例如,通過在 XML 中定義 UI 字符串,你可以將字符串翻譯為其他語言,并將這些字符串保存在單獨(dú)的文件中。 然后,Android 系統(tǒng)會(huì)根據(jù)向資源目錄名稱追加的語言限定符(如為法語字符串值追加 res/values-fr/)和用戶的語言設(shè)置,對(duì)應(yīng)用 UI 使用相應(yīng)的語言字符串。
Android 支持許多不同的備用資源限定符。限定符是一種加入到資源目錄名稱中,用來定義這些資源適用的設(shè)備配置的簡短字符串。 比如,根據(jù)設(shè)備的屏幕方向和尺寸為 Activity 創(chuàng)建不同的布局。 例如,當(dāng)設(shè)備屏幕為縱向(長型)時(shí),你可能想要一種垂直排列按鈕的布局;但當(dāng)屏幕為橫向(寬型)時(shí),應(yīng)按水平方向排列按鈕。 要想根據(jù)方向更改布局,可以定義兩種不同的布局,然后對(duì)每個(gè)布局的目錄名稱應(yīng)用相應(yīng)的限定符。 然后,系統(tǒng)會(huì)根據(jù)當(dāng)前設(shè)備方向自動(dòng)應(yīng)用相應(yīng)的布局。