學習筆記之Android四大核心元件詳解
概述
Android四大核心元件指的是Activity,Service,ContentProvider,BroadCastReceiver,核心元件都是由Android系統進行管理和維護的,一般都要在清單檔案中進行註冊或者在程式碼中動態註冊。
Activity
- 定義與作用: Activity的中文意思是活動,代表手機螢幕的一屏,或是平板電腦中的一個視窗,提供了和使用者互動的視覺化介面。Activity是用於處理UI相關業務的,比如載入介面、監聽使用者操作事件。
生命週期: 生命週期指的是Activity從建立到銷燬所執行的一系列方法,主要包括7個生命週期方法。詳細流程如下圖
裡面涉及了Activity的四個重要狀態,如下表
注:以上兩個圖表皆摘自《Android從入門到精通》。- 建立與配置 建立一個Activity需繼承自android.app.Activity這個類,然後重寫onCreate(),在onCreate()裡面呼叫setContentView(引數)來載入佈局,引數就是佈局檔案。
配置則需要在清單檔案的Application節點下面註冊Actvitiy,如果要首先啟動該Activity則新增帶有category節點且值為LAUNCHER的intent-filter節點,下面就是清單檔案的配置。
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<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>
</application>
4 . 啟動模式
Activity的啟動模式決定了啟用Activity時,是否建立新的物件,進而將影響到任務棧也叫回退棧。
在AndroidManifest.xml檔案中,可以為每個activity節點配置android:launchMode屬性,以決定該Activity的啟動模式,該屬性的值有:
—–standard:(預設值)標準模式:每次啟用Activity時,都會建立新的Activity物件
—–singleTop:棧頂時唯一,即當Activity處於棧頂位置時,每次啟用並不會建立新的Activity物件。但不在棧頂時,每次啟用時會建立新的物件。
—–singleTask:任務棧中唯一,即當棧中沒有該Activity時,將建立該Activity物件,當棧中已經有該Activity時,將不會建立新的物件,原本棧中位於該Activity之上的其它Activity將全部被強制出棧,且被啟用的Activity將自動獲得棧頂位置。
—–singleInstance:例項(物件)唯一,確保該Activity的物件一定只有1個,被設定為singleInstance的Activity將被置於一個專門的任務棧中,且該任務棧中有且僅有一個Activity。
什麼是任務棧(回退棧):
任務棧是用來存放所有激活了的Activity物件,啟用的Acitvity將會按照後進先出的棧結構顯示出來。因為螢幕只能顯示一個Activity,當有新的Activity被啟用時,原來正在顯示的Activity就會進行壓棧操作被壓到新Activity物件下方的位置。當按下”Back”鍵時棧頂Activity會執行彈棧操作,而在第2位的Activity將獲得棧頂位置,顯示在前臺。
service
- 定義與作用
— Service(服務)是一個沒有使用者介面的在後臺執行執行耗時操作的應用元件。其他應用元件能夠啟動Service,並且當用戶切換到另外的應用場景,Service將持續在後臺執行。另外,一個元件能夠繫結到一個service與之互動(IPC機制),例如,一個service可能會處理網路操作,播放音樂,操作檔案I/O或者與內容提供者(content provider)互動,所有這些活動都是在後臺進行,以上是Google文件的解釋,資料來源於大神部落格
— Service還有一個作用就是提升程序(每個應用都是一個程序)的優先順序,程序的優先順序指的是在Android系統中,會把正在執行的應用確定一個優先順序,當記憶體空間不足時,系統會根據程序的優先順序清理掉一部分程序佔用的記憶體空間,以獲得足夠的記憶體空間以供新啟用的應用執行。詳細的程序優先順序劃分如下,
1)前臺程序:應用程式存在Activity正位於前臺,可見並可控
2)可見程序:應用程式存在Activity處於區域性可見狀態,即區域性可見卻不可控
3)服務程序:應用程式存在正在執行的Service
4)後臺程序:應用程式的所有Activity均被置於後臺,沒有任何Activity可見
5) 空程序:已經退出的應用程式
service的程序優先順序詳細介紹請參考這篇博文,點此進入
2 . 狀態
1)啟動
【啟動service】
用Context類定義的startService(Intent)即可啟動Service元件,其中intent定義方法與跳轉Activity類似,只需把Actvity類換成Service類即可。其生命週期為啟動時onCreate()–>onStartCommand()–>銷燬時onDestroy(), 反覆呼叫startService()只會導致Service反覆執行onStartCommand()
【停止service】
呼叫Context類定義的stopService(Intent)即可停止Service元件,反覆呼叫並沒有任何效果,亦不會報告錯誤,即:即使停止沒有啟動的Service也不會出錯。也可以在Service類的內部,呼叫Service定義的stopSelf()方法,停止當前Service。
2)繫結
主要作用是實現元件間的通訊,實質的表現是Activity可以呼叫Service中的方法,使Service執行特定的業務,並且這些方法可以是帶返回值的方法,進而Activity可以通過獲取這些返回值,這樣就實現與Service的通訊。
【生命週期】
– onCreate() -> 當第1次繫結時執行
– onBind() -> 當第1次繫結時執行
– onDestroy() -> 當解除繫結時執行
【繫結與解綁】
呼叫bindService()方法可以實現Activity與Service的繫結,呼叫unbindService()可以解除繫結。在Activity被銷燬之前,必須解除與Service的繫結。
具體實現程式碼如下:
//在Activity中呼叫bindService()來實現服務繫結
Intent intent =new Intent(this,MyService.class);
//用於連線的物件,相當於元件間的一個連線紐帶
ServiceConnection conn=new ServiceConnection(){
@Override
public void onServiceConnected(
ComponentName name,
IBinder service) {
// 當Service已經連線
//引數IBinder service就是與service進行通訊的物件,通過這個物件可以呼叫Service裡的方法
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 當Service斷開連線
}
};
//繫結標誌
int FLAGS=BIND_AUTO_CREATE;
bindService(intent,conn,FLAGS);
//然後在Activity銷燬時解綁,這裡需要一個連線物件,所以需要在上面把conn設為全域性變數
@Override
protected void onDestroy() {
unbindService(conn);
super.onDestroy();
}
//MyService類需要繼承自Service,還需要在清單檔案中註冊
public class MyService extends Service {
@Override
public void onCreate() {
}
@Override
public IBinder onBind(Intent intent) {
//這裡需要返回一個IBinder物件,我們可以建立一個內部類來獲得這個物件
MyBinder binder = new MyBinder();
return binder;
}
/**這個內部類就是我們返回的IBinder類
Activity的conn裡連線上後就是得到這個物件才實現了元件間的通訊
*/
public class MyBinder extends Binder {
//這個內部類可以寫很多方法,來讓Activity呼叫,還可以是帶有返回值的方法
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
}
}
下圖形象地說明了Service兩種狀態的生命週期
3.service特性
【1】Service的粘性:
當Service被意外終止(非正常停止,即不是通過stopService()或stopSelf()停止)後,會在未來的某一刻自動重啟。
Service的粘性是通過onStartCommand()方法的返回值確定的,可用的值有:
—–Service.START_REDELIVER_INTENT -> 粘性的,且在自動重啟時,會重新給Service傳送Intent物件。
—–Service.START_STICKY -> 粘性的
—–Service.START_NOT_STICKY -> 非粘性的
—–Service.START_STICKY_COMPATIBILITY -> 粘性的,並且相容的
當需要Service是非粘性的,取值Service.START_NOT_STICKY;當需要Service是粘性的,並且還需要獲取Intent物件時,取值Service.START_REDELIVER_INTENT;否則,只是需要粘性的,不需要Intent時,取值super.onStartCommand()預設值。
【2】Service是單例的,在程式中一個Service類只會存在一個物件
【3】Service是沒有介面的,適合於在後臺進行耗時操作,但要注意Service仍然是執行在主執行緒中的,故耗時的操作還是需要開啟子執行緒來進行。
ContentProvider
1.作用 中文意思是內容提供者,ContentProvider可以將應用程式自身的資料對外(對其它應用程式)共享,使得其它應用可以對自身的資料進行增、刪、改、查操作。
Android系統使用了許多ContentProvider,將系統中的絕大部分常規資料進行對外共享,例如:聯絡人資料、通話記錄、簡訊、相簿、歌曲、視訊、日曆等等,一般這些資料都存放於一個個的資料庫中。
【實現】
由於ContentProvider可提供增、刪、改、查這些操作,通常結合SQLite使用。
ContentResolver是讀取由ContentProvider共享的資料的工具。通過Context類定義的getContentResolver()方法,可以獲取ContentResolver物件。如果您不打算與其他應用共享資料,則無需開發自己的提供程式。
2.訪問Content Provider
Content Provider以一個或多個表(與在關係型資料庫中找到的表類似)的形式將資料呈現給外部應用。 行表示提供程式收集的某種資料型別的例項,行中的每個列表示為例項收集的每條資料。
應用從具有 ContentResolver物件的Content Provider訪問資料。 此物件具有呼叫提供程式物件(ContentProvider 的某個具體子類的例項)中同名方法的方法。 ContentResolver 方法可提供持續儲存的基本“CRUD”(建立、檢索、更新和刪除)功能。
客戶端應用程序中的 ContentResolver 物件和擁有提供程式的應用中的 ContentProvider 物件可自動處理跨程序通訊。 ContentProvider 還可充當其資料儲存區和表格形式的資料外部顯示之間的抽象層。
注:要訪問提供程式,您的應用通常需要在其清單檔案中請求特定許可權。 內容提供程式許可權部分詳細介紹了此內容。
例如,要從使用者字典提供程式中獲取字詞及其語言區域的列表,則需呼叫 ContentResolver.query()。 query() 方法會呼叫使用者字典提供程式所定義的 ContentProvider.query() 方法。 以下程式碼行顯示了 ContentResolver.query() 呼叫:
// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // The content URI of the words table,URI對映的資料表名
mProjection, // The columns to return for each row,我們所要我的選擇查詢的表的列名,字串陣列
mSelectionClause // Selection criteria,查詢條件相當於SQL中的where a =? and b = ?
mSelectionArgs, // Selection criteria,字串陣列,替代上面條件中的?佔位符
mSortOrder); // The sort order for the returned rows,排列的順序相當於SQL中的order(欄位)
3.URI 資源訪問格式
內容 URI 是用於在Content Provider中標識資料的 URI。內容 URI 包括整個提供程式的符號名稱(其授權)和一個指向表的名稱(路徑)。 當您呼叫客戶端方法來訪問提供程式中的表時,該表的內容 URI 將是其引數之一。
在前面的程式碼行中,常量 CONTENT_URI 包含使用者字典的“字詞”表的內容 URI。 ContentResolver 物件會分析出 URI 的授權,並通過將該授權與已知提供程式的系統表進行比較,來“解析”提供程式。 然後, ContentResolver 可以將查詢引數分派給正確的提供程式。
ContentProvider 使用內容 URI 的路徑部分來選擇要訪問的表。 提供程式通常會為其公開的每個表顯示一條路徑。
在前面的程式碼行中,“字詞”表的完整 URI 是:
content://user_dictionary/words
其中,user_dictionary 是提供程式的授權,需要在清單檔案中註冊,words 是表的路徑; content://(架構)始終顯示,並將此標識為內容 URI。
許多提供程式都允許您通過將 ID 值追加到 URI 末尾來訪問表中的單個行。 例如,要從使用者字典中檢索 _ID 為 4 的行,則可使用此內容 URI:
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
在檢索到一組行後想要更新或刪除其中某一行時通常會用到 ID 值。
注:Uri 和 Uri.Builder 類包含根據字串構建格式規範的 URI 物件的便利方法。 ContentUris 包含一些可以將 ID 值輕鬆追加到 URI 後的方法。 前面的程式碼段就是使用 withAppendedId() 將 ID 追加到 UserDictionary 內容 URI 後。
4.顯示資料
ContentResolver.query() 方法始終會返回符合以下條件的 Cursor:包含查詢的表為匹配查詢選擇條件的行指定的列, Cursor 物件為其包含的行和列提供隨機讀取訪問許可權。 通過使用 Cursor 方法,您可以迴圈訪問結果中的行、確定每個列的資料型別、從列中獲取資料,並檢查結果的其他屬性。 某些 Cursor 實現會在提供程式的資料發生更改時自動更新物件或在 Cursor 更改時觸發觀察程式物件中的方法。
注:提供程式可能會根據發出查詢的物件的性質來限制對列的訪問。 例如,聯絡人提供程式會限定只有同步介面卡才能訪問某些列,因此不會將它們返回至 Activity 或服務。
如果沒有與選擇條件匹配的行,則提供程式會返回 Cursor.getCount() 為 0(空遊標)的 Cursor 物件。
如果出現內部錯誤,查詢結果將取決於具體的提供程式。它可能會選擇返回 null,或引發 Exception。
由於 Cursor 是行“列表”,因此顯示 Cursor 內容的較好方式是通過 SimpleCursorAdapter 將其與 ListView 關聯。
以下程式碼段會建立一個包含由查詢檢索到的 Cursor 的 SimpleCursorAdapter 物件,並將此物件設定為 ListView 的介面卡:
// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
UserDictionary.Words.WORD, // Contract class constant containing the word column name
UserDictionary.Words.LOCALE // Contract class constant containing the locale column name
};
// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.dictWord, R.id.locale};
// Creates a new SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
getApplicationContext(), // The application's Context object
R.layout.wordlistrow, // A layout in XML for one row in the ListView
mCursor, // The result from the query
mWordListColumns, // A string array of column names in the cursor
mWordListItems, // An integer array of view IDs in the row layout
0); // Flags (usually none are needed)
// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);
注:要通過 Cursor 支援 ListView,遊標必需包含名為 _ID 的列。 正因如此,前文顯示的查詢會為“字詞”表檢索 _ID 列,即使 ListView 未顯示該列。 此限制也解釋了為什麼大多數提供程式的每個表都具有 _ID 列。
獲取某個列的值
// 得到words表中WORD的欄位標籤,也即是上面查詢時列名的位置mWordListColumns的下標,這裡應該是第一個
int index =mCursor.getColumnIndex(UserDictionary.Words.WORD);
if (mCursor != null) {
while (mCursor.moveToNext()) {
// 得到該下標列名的值.
newWord = mCursor.getString(index);
}
} else {
}
5.建立Content Provider
實現 ContentProvider 類,實現它的抽象方法。
query()
從您的提供程式檢索資料。使用引數選擇要查詢的表、要返回的行和列以及結果的排序順序。 將資料作為 Cursor 物件返回。
insert()
在您的提供程式中插入一個新行。使用引數選擇目標表並獲取要使用的列值。 返回新插入行的內容 URI。
update()
更新您提供程式中的現有行。使用引數選擇要更新的表和行,並獲取更新後的列值。 返回已更新的行數。
delete()
從您的提供程式中刪除行。使用引數選擇要刪除的表和行。 返回已刪除的行數。
getType()
返回內容 URI 對應的 MIME 型別。實現內容提供程式 MIME 型別部分對此方法做了更詳盡的描述。
onCreate()
初始化您的提供程式。Android 系統會在建立您的提供程式後立即呼叫此方法。 請注意,ContentResolver 物件嘗試訪問您的提供程式時,系統才會建立它。
設計內容 URI
內容 URI 是用於在提供程式中標識資料的 URI。內容 URI 包括整個提供程式的符號名稱(其授權)和一個指向表或檔案的名稱(路徑)。 可選 ID 部分指向表中的單個行。 ContentProvider 的每一個數據訪問方法都將內容 URI 作為引數;您可以利用這一點確定要訪問的表、行或檔案。
設計授權
提供程式通常具有單一授權,該授權充當其 Android 內部名稱。為避免與其他提供程式發生衝突,您應該使用網際網路網域所有權(反向)作為提供程式授權的基礎。 由於此建議也適用於 Android 軟體包名稱,因此您可以將提供程式授權定義為包含該提供程式的軟體包名稱的副檔名。 例如,如果您的 Android 軟體包名稱為
com.example.<appname>
就應使用com.example.<appname>.provider
授權。
設計路徑結構
開發者通常通過追加指向單個表的路徑來根據許可權建立內容 URI。 例如,如果您有兩個表:table1 和 table2,則可以通過合併上一示例中的許可權來生成 內容 URI com.example..provider/table1 和 com.example..provider/table2。路徑並不限定於單個段,也無需為每一級路徑都建立一個表。
處理內容 URI ID
按照慣例,提供程式通過接受末尾具有行所對應 ID 值的內容 URI 來提供對錶中單個行的訪問。 同樣按照慣例,提供程式會將該 ID 值與表的 _ID 列進行匹配,並對匹配的行執行請求的訪問。
這一慣例為訪問提供程式的應用的常見設計模式提供了便利。應用會對提供程式執行查詢,並使用 CursorAdapter 以 ListView 顯示生成的 Cursor。 定義 CursorAdapter 的條件是, Cursor 中的其中一個列必須是 _ID
使用者隨後從 UI 上顯示的行中選取其中一行,以檢視或修改資料。 應用會從支援 ListView 的 Cursor 中獲取對應行,獲取該行的 _ID 值,將其追加到內容 URI,然後向提供程式傳送訪問請求。 然後,提供程式便可對使用者選取的特定行執行查詢或修改。
內容 URI 模式
為幫助您選擇對傳入的內容 URI 執行的操作,提供程式 API 加入了實用類 UriMatcher,它會將內容 URI“模式”對映到整型值。 您可以在一個 switch 語句中使用這些整型值,為匹配特定模式的一個或多個內容 URI 選擇所需操作。
內容 URI 模式使用萬用字元匹配內容 URI:
*:匹配由任意長度的任何有效字元組成的字串
#:匹配由任意長度的數字字元組成的字串
以設計和編碼內容 URI 處理為例,假設一個具有授權
com.example.app.provider 的提供程式能識別以下指向表的內容 URI:
content://com.example.app.provider/table1:一個名為 table1 的表
content://com.example.app.provider/table2/dataset1:一個名為 dataset1 的表
content://com.example.app.provider/table2/dataset2:一個名為 dataset2 的表
content://com.example.app.provider/table3:一個名為 table3 的表
提供程式也能識別追加了行 ID 的內容 URI,例如,content://com.example.app.provider/table3/1 對應由 table3 中 1 標識的行的內容 URI。
可以使用以下內容 URI 模式:
content://com.example.app.provider/*
匹配提供程式中的任何內容 URI。
content://com.example.app.provider/table2/*:
匹配表 dataset1 和表 dataset2 的內容 URI,但不匹配 table1 或 table3 的內容 URI。
content://com.example.app.provider/table3/#:匹配 table3 中單個行的內容 URI,如 content://com.example.app.provider/table3/6 對應由 6 標識的行的內容 URI。
在清單檔案中註冊實現content provider的類
與 Activity 和 Service 元件類似,必須使用 provider 元素在清單檔案中為其應用定義 ContentProvider 的子類。 Android 系統會從該元素獲取以下資訊:
授權 (android:authorities)
用於在系統內標識整個提供程式的符號名稱,也即是上面所說的URI包含路徑名錶名的字串。
提供程式類名 ( android:name )
實現 ContentProvider 的類。實現 ContentProvider 類中對此類做了更詳盡的描述。
BraodCast Receiver
1.概述
廣播接收器,顧名思義這是用於接收應用傳送的廣播的系統元件。廣播是一種1對多的通訊方式,即存在1個傳送方,若干個接收方。在Android系統,把具有這樣的資料的傳遞方式的機制稱之為“廣播”。Android系統會在特定的情景下發出各種廣播,例如開機、鎖屏了、電量不足了、正在充電了、撥出電話了、被呼叫了……
廣播是一種跨程序的、“全裝置之內”的通訊方式。
2.傳送廣播
呼叫Context物件的sendBroadcast(Intent)即可傳送廣播,在引數Intent物件中,應該呼叫setAction()方法配置廣播的“頻道號”,即是相當於收音機要接收某個電臺的頻段,只有註冊了相同的Action的廣播接收者才可以接收到該廣播。
3.接收廣播
自定義類,繼承自android.content.BroadcastReceiver後需要註冊,註冊時,使用IntentFilter配置與傳送方相同的Action,重寫onReceiver()方法實現對廣播的處理。
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
String log = sb.toString();
Log.d(TAG, log);
Toast.makeText(context, log, Toast.LENGTH_LONG).show();
}
}
4.註冊廣播接收器
廣播接收者的註冊可以區分為靜態註冊和動態註冊:
- 靜態註冊:在清單檔案AndroidManifest.xml中,在application節點下使用receiver節點進行註冊。這種方式註冊的廣播接收者必須是一個單獨的只實現BroadcastReceiver的類,不能是內部類。且這樣的廣播接收者是常駐型的,即從APP安裝到手機上的那一刻即開始處於接收廣播狀態,且直至該APP被從手機移除。
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
- 動態註冊:在程式中呼叫Context物件的registerReceiver(BroadcastReceiver, IntentFilter)方法進行註冊。這種方式可以註冊以內部類的形式存在的廣播接收者,且這種方式的廣播接收者僅當註冊後才開始接收廣播,並且在呼叫了Context物件的unregisterReceiver(BroadcastReceiver)方法後就會停止接收廣播。
BroadcastReceiver br = new MyBroadcastReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(br, filter);
5.無序廣播與有序廣播
1. 普通的廣播即為無序廣播,誰都可以接收,並不會相互打擾。
2. 有序廣播:呼叫sendOrderedBroadcast(Intent, String permission)方法傳送的廣播,各廣播接收者在接收廣播時,會存在一定的先後順序,即某接收者會先收到廣播,其他接收者後收到廣播,廣播會在各接收者之間按照一定的先後順序進行傳遞。在廣播的傳遞過程中,先接收到廣播的接收者可以對廣播進行攔截或篡改。
6.有序廣播的接收者們的優先順序
有序廣播的接收者們的優先順序用於確定接收的先後順序,優先順序越高的接收者,將更優先接收到廣播,反之,則更靠後接收到廣播。
1. 註冊廣播時,在廣播對應的IntentFilter中的priority屬性直接決定優先順序,該屬性值為int型別的數值,取值越大,則優先順序越高!
2. 如果存在多個廣播接收者配置的priority屬性值相同,則動態註冊的廣播接收者的優先順序高於靜態註冊的廣播接收者。
3. 如果根據以上2條規則都無法確定優先順序,則根據註冊的先後順序確定各接收者們的優先順序。
7.有序廣播的攔截或篡改
1. 【攔截】在廣播接收者中,使用abortBroadcast()方法,可以終止有序廣播向後繼續傳遞,即後續的接收者們將無法接收到該廣播。注意:該方法只能在接收有序廣播時呼叫!
2. 【篡改】在廣播接收者中,呼叫setResult()方法,可以向廣播中新增資料,並在後續的接收者中,可以通過getResult()獲取這些資料,同時,後續的接收者也可以再次呼叫setResult()方法重新向廣播中寫入資料,即覆蓋原有的資料,以實現篡改。