1.Activity(界面展示型組件)
Activity生命周期。
- 啟動Activity: onCreate()—>onStart()—>onResume(),Activity進入運行狀態(tài)。
- Activity退居后臺: 當前Activity轉(zhuǎn)到新的Activity界面或按Home鍵回到主屏: onPause()—>onStop(),進入停滯狀態(tài)。
- Activity返回前臺: onRestart()—>onStart()—>onResume(),再次回到運行狀態(tài)。
- Activity退居后臺,且系統(tǒng)內(nèi)存不足, 系統(tǒng)會殺死這個后臺狀態(tài)的Activity(此時這個Activity引用仍然處在任務(wù)棧中,只是這個時候引用指向的對象已經(jīng)為null),若再次回到這個Activity,則會走onCreate()–>onStart()—>onResume()(將重新走一次Activity的初始化生命周期)
- 鎖屏:onPause()->onStop()
- 解鎖:onStart()->onResume()
onCreate:初始化Activity
onStart:Activity可見(黑屏)
onResume:Activity界面可見(執(zhí)行了,才展現(xiàn)出Activity真正的界面)
onPause:另外一個Activity遮蓋住當前的Activity,但并不是完全不可見(半透明),只是不再接收事件的處理
onStop:Activity完全處于后臺,對用戶不可見
onDestroy:銷毀Activity
Activity啟動模式
背景知識:
一個app有一個任務(wù)棧(stack),而一個任務(wù)??梢杂卸鄠€任務(wù)(task),一個task可以有多個activity.
standard
標準模式,每次都會新創(chuàng)建一個activity實例放入任務(wù)棧中
singleTop
棧頂復用模式,如果棧頂存在該activity的實例,則不需要重新創(chuàng)建
singleTask
棧內(nèi)復用模式,如果棧內(nèi)存在該activity的實例,則不需要重新創(chuàng)建,并且為清楚該activity之上的activity,使得該activity位于棧頂
singleInstance
加強版的singleTask,并且這種模式的activity將被單獨放在一個任務(wù)內(nèi),即一個task里面
Activity緩存方法
背景:當Activity a進入到b一段時間之后,系統(tǒng)可能會回收掉a,此時重新返回到a,a將被重新創(chuàng)建一次,這時a中的臨時數(shù)據(jù)和狀態(tài)可能就丟失了
解決辦法:在回調(diào)方法onSaveInstanceState中保存數(shù)據(jù)(這個方法一定會在activity被回收之前調(diào)用),然后從onCreate(Bundle savedInstanceState)方法中的bundle取出保存的值
注意:布局中的每一個View默認實現(xiàn)了onSaveInstanceState()方法,這樣的話,這個UI的任何改變都會自動的存儲和在activity重新創(chuàng)建的時候自動的恢復。但是這種情況只有在你為這個UI提供了唯一的ID之后才起作用,如果沒有提供ID,將不會存儲它的狀態(tài)。
Activity切屏問題
對android:configChanges屬性,一般認為有以下幾點:
1、不設(shè)置Activity的android:configChanges時,切屏會重新調(diào)用各個生命周期,切橫屏時會執(zhí)行一次,切豎屏時會執(zhí)行兩次
2、設(shè)置Activity的android:configChanges="orientation"時,切屏還是會重新調(diào)用各個生命周期,切橫、豎屏時只會執(zhí)行一次
3、設(shè)置Activity的android:configChanges="orientation|keyboardHidden"時,切屏不會重新調(diào)用各個生命周期,只會執(zhí)行onConfigurationChanged方法
但是,自從Android 3.2(API 13),在設(shè)置Activity的android:configChanges="orientation|keyboardHidden"后,還是一樣 會重新調(diào)用各個生命周期的。因為screen size也開始跟著設(shè)備的橫豎切換而改變。所以,在AndroidManifest.xml里設(shè)置的MiniSdkVersion和 TargetSdkVersion屬性大于等于13的情況下,如果你想阻止程序在運行時重新加載Activity,除了設(shè)置"orientation", 你還必須設(shè)置"ScreenSize"。
解決方法:
android:configChanges="orientation|keyboardHidden|screenSize"
Activity通用配置
<activity android:name=".demo.list.slide.MessageListActivity" android:configChanges="orientation|keyboardHidden" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan"></activity>
2.Service(計算型組件)
兩種開啟服務(wù)的方式start和bind
start型和bind型的區(qū)別
start型雖然由activity啟動,但是生命周期不依賴其啟動的activity
bind型的生命周期依賴其啟動的activity,即activity被finish掉,則該服務(wù)被停止
注意:多個activity可以開啟同一個服務(wù),也就是說一個service可以與多個activity交互,且只有所有的activity都unbind服務(wù)了,這個服務(wù)才停止運行
Service生命周期
start型 (開啟)onCreate—>OnStartCommand (停止)OnDestroy
bind型 (開啟)onCreate—>OnBind (停止)OnUnbind—>OnDestroy
注意:OnCreate和OnBind只被調(diào)用一次,OnStartCommand可以被調(diào)用多次
onReind回調(diào)的條件
1.服務(wù)中onUnbind方法返回true
2.服務(wù)被解綁之后沒有被銷毀,再次綁定服務(wù)時
在 AndroidManifest.xml 里 Service 元素的常見選項
android:name ------------- 服務(wù)類名
android:label -------------- 服務(wù)的名字,如果此項不設(shè)置,那么默認顯示的服務(wù)名則為類名
android:icon -------------- 服務(wù)的圖標
android:permission ------- 申明此服務(wù)的權(quán)限,這意味著只有提供了該權(quán)限的應(yīng)用才能控制或連接此服務(wù)
android:process ---------- 表示該服務(wù)是否運行在另外一個進程,如果設(shè)置了此項,那么將會在包名后面加上這段字符串表示另一進程的名字
android:enabled ---------- 如果此項設(shè)置為 true,那么 Service 將會默認被系統(tǒng)啟動,不設(shè)置默認此項為 false
android:exported --------- 表示該服務(wù)是否能夠被其他應(yīng)用程序所控制或連接,不設(shè)置默認此項為 false
注:一般的Service工作在UI線程中,IntentService工作在子線程中
假設(shè)包名為com.sf.service
android:process=“:remote” // 屬于該應(yīng)用的私有進程,進程名為com.sf.service:remote
android:process=“com.sf.remote” //屬于全局進程,進程名為com.sf.remote
3.BroadcastReceiver(消息型組件)
動態(tài)注冊和靜態(tài)注冊
- 靜態(tài)注冊:在AndroidManifest.xml文件中進行注冊,當App退出后,Receiver仍然可以接收到廣播并且進行相應(yīng)的處理
- 動態(tài)注冊:在代碼中動態(tài)注冊,當App退出后,也就沒辦法再接受廣播了
4.ContentProvider(數(shù)據(jù)共享行組件)
實現(xiàn)數(shù)據(jù)的共享(同應(yīng)用不同進程之間或者不同應(yīng)用之間)
應(yīng)用場景:B應(yīng)用通過ContentProvider來暴露數(shù)據(jù)供其他應(yīng)用訪問,A(其他應(yīng)用)使用ContentResolver通過uri來對B應(yīng)用的數(shù)據(jù)庫進行操作
cr通過uri來定位不同的cp
cp中的URI的格式如下圖

Authority:授權(quán)信息,用以區(qū)別不同的ContentProvider
Path:表名,用以區(qū)分ContentProvider中的不同的數(shù)據(jù)表
Id:id號,用以區(qū)別表中不同的數(shù)據(jù)
具體用法
1.在B應(yīng)用中,創(chuàng)建一個數(shù)據(jù)庫,將數(shù)據(jù)存儲入對應(yīng)的數(shù)據(jù)庫
2.在B應(yīng)用中,創(chuàng)建一個MusicProvider,繼承自ContentProvider
在MusicProvider中給數(shù)據(jù)庫分配一個URI,供A應(yīng)用定位
默認該Provider需要實現(xiàn)如下六個方法,onCreate(), query(Uri, String[], String, String[], String),insert(Uri, ContentValues), update(Uri, ContentValues, String, String[]), delete(Uri, String, String[]), getType(Uri)
這些方法其實就是對數(shù)據(jù)庫進行操作,然后給數(shù)據(jù)庫分配一個Uri,供其他應(yīng)用定位
注意:ContentProvider需要在AndroidManifest.xml中注冊
<provider
android:authorities="com.sf.contentproviderb"
android:name=".provider.MusicProvider" />
3.在A應(yīng)用在通過ContentResolver,實現(xiàn)對B應(yīng)用中數(shù)據(jù)庫的增刪改查
Cursor query(Uri uri,String[] projection, String selection, String[] selectionArgs, String sortOrder)
uri:資源定位
projection:查詢的列
selection:查詢的條件
selectionArgs:查詢條件的參數(shù)(如果selection中有占位符,就需要此參數(shù))
sortOrder:排序(如MediaStore.Audio.Media._ID,按照id對數(shù)據(jù)進行排序,默認升序)
//降序 MediaStore.Audio.Media._ID+” DESC”
當然想要跨應(yīng)用訪問,還需要增加以下操作
首先我們先介紹下,共享數(shù)據(jù)所涉及到的幾個重要標簽:
android:exported 設(shè)置此provider是否可以被其他應(yīng)用使用。
android:readPermission 該provider的讀權(quán)限的標識
android:writePermission 該provider的寫權(quán)限標識
android:permission 該provider讀寫權(quán)限標識
android:grantUriPermissions 臨時權(quán)限標識,true時,意味著該provider下所有數(shù)據(jù)均可被臨時使用;false時,則反之,但可以通過設(shè)置<grant-uri-permission>標簽來指定哪些路徑可以被臨時使用。這么說可能還是不容易理解,我們舉個例子,比如你開發(fā)了一個郵箱應(yīng)用,其中含有附件需要第三方應(yīng)用打開,但第三方應(yīng)用又沒有向你申請該附件的讀權(quán)限,但如果你設(shè)置了此標簽,則可以在start第三方應(yīng)用時,傳入FLAG_GRANT_READ_URI_PERMISSION或FLAG_GRANT_WRITE_URI_PERMISSION來讓第三方應(yīng)用臨時具有讀寫該數(shù)據(jù)的權(quán)限。
3.1 在B應(yīng)用聲明一個權(quán)限
<permission android:name="com.sf.contentproviderb.READ" android:protectionLevel="normal"/>
provider標簽改為
<provider
android:authorities="com.sf.contentproviderb"
android:name=".provider.MusicProvider"
android:readPermission="com.sf.contentproviderb.READ"
android:exported="true"
/>
3.2 在A應(yīng)用中添加權(quán)限
<uses-permission android:name="com.sf.contentproviderb.READ"/>
這樣就可以在A中訪問B中的數(shù)據(jù)了
注意以下幾點:
如果在B中未申明權(quán)限,則在A中不加權(quán)限也可以訪問
數(shù)據(jù)庫不能隨便close,否則可能會導致訪問數(shù)據(jù)出現(xiàn)問題(SQLiteOpenHelper內(nèi)部只緩存一個數(shù)據(jù)庫的連接)
e1.ContentProvider中使用過數(shù)據(jù)庫不能關(guān)閉(重寫的那幾個方法,不要關(guān)閉數(shù)據(jù)庫),其他程序才可以通過這個provider訪問數(shù)據(jù)
e2.如果存在多線程同時操作數(shù)據(jù)庫,線程A通過getWritableDatabase()獲取了一個SQLiteDatabase 用來進行刪除記錄操作,
線程B通過getReadableDatabase()獲取了一個SQLiteDatabase 用來進行其它操作,操作已完成正在close()。
當線程A還在進行刪除記錄操作的時候,線程B調(diào)用了SQLiteDatabase.close()方法斷開了數(shù)據(jù)庫連接,
那么將會導致線程A的刪除操作出現(xiàn)異常
可以考慮在Activity退出或者App退出時close
具體代碼見ContentProviderA 和ContentProviderB