目錄
- 活動
- 活動是什么
- 活動的相關操作
- 手動創建活動
- 活動中使用Toast
- 活動中使用Menu
- 銷毀一個活動
- 使用Intent實現活動間啟動
- 顯示啟動
- 隱式啟動
- 活動間數據傳遞
- 活動的生命周期
- 返回棧
- 活動的狀態
- 活動的生存期
- 活動的啟動流程
- 活動的回收和重建
- 如何在活動銷毀前保存狀態
- 活動的啟動模式
- standard
- singleTop
- singleTask
- singleInstance
- 活動的優先級
- 常見面試問題:
活動
活動是什么
Activity是最容易吸引用戶的地方,它是一種可以包含用戶界面的組件,主要用于和用戶進行交互。一個應用程序中可以包含零個或多個Activity。
活動的相關操作
手動創建活動
- 創建空的安卓項目:項目類型選擇“Add No Activity”
- 創建Activity:右鍵點擊項目的java目錄,選擇新建空的Activity,并且不要勾選Generate Layout File和Launcher Activity這兩個選項;
勾選Generate Layout File表示會自動為FirstActivity創建一個對應的布局文件,勾選Launcher Activity表示會自動將FirstActivity設置為當前項目的主Activity。 - 重寫onCreate()方法:
class FirstActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)}
}
- 創建布局資源文件:在項目的app/src/main/res目錄新建layout目錄,然后新建布局資源文件,名稱為first_layout,根元素選擇LinearLayout;通過可視化布局編輯器編輯布局文件。
一個簡單的布局資源文件對應的源碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"><Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1"
/></LinearLayout>
如果你需要在XML中引用一個id,就使用@id/id_name;
如果你需要在XML中定義一個id,則要使用@+id/id_name;
android:layout_width指定了當前元素的寬度,這里使用match_parent表示讓當前元素和父元素一樣寬;
android:layout_height指定了當前元素的高度,這里使用wrap_content表示當前元素的高度只要能剛好包含里面的內容就行;
android:text指定了元素中顯示的文字內容。
- 加載布局資源文件:在onCreate()方法中加入如下代碼
class FirstActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.first_layout) // 加載布局資源文件}
}
項目中添加的任何資源都會在R文件中生成一個相應的資源id,因此我們剛才創建的first_layout.xml布局的id現在已經添加到R文件中了。在代碼中引用布局文件的方法你也已經學過了,只需要調用R.layout.first_layout就可以得到first_layout.xml布局的id,然后將這個值傳入setContentView()方法即可。
- 在AM中注冊活動
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.activitytest"><application...><activity android:name=".FirstActivity"android:label="This is FirstActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application>
</manifest>
在標簽中,我們使用了android:name來指定具體注冊哪一個Activity,那么這里填入的.FirstActivity是什么意思呢?其實這不過是com.example.activitytest.FirstActivity的縮寫而已。由于在最外層的標簽中已經通過package屬性指定了程序的包名是com.example.activitytest,因此在注冊Activity時,這一部分可以省略,直接使用.FirstActivity就足夠了。
配置主Activity的方法其實就是在標簽的內部加入標簽,并在這個標簽里添加和這兩句聲明即可。
另外需要注意,如果你的應用程序中沒有聲明任何一個Activity作為主Activity,這個程序仍然是可以正常安裝的,只是你無法在啟動器中看到或者打開這個程序。這種程序一般是作為第三方服務供其他應用在內部進行調用的。
還可以使用android:label指定Activity中標題欄的內容,標題欄是顯示在Activity最頂部的;需要注意的是,給主Activity指定的label不僅會成為標題欄中的內容,還會成為啟動器(Launcher)中應用程序顯示的名稱。
活動中使用Toast
什么是Toast:Toast是Android系統提供的一種非常好的提醒方式,在程序中可以使用它將一些短小的信息通知給用戶,這些信息會在一段時間后自動消失,并且不會占用任何屏幕空間。
首先需要定義一個彈出Toast的觸發點,正好界面上有個按鈕,那我們就讓這個按鈕的點擊事件作為彈出Toast的觸發點吧:
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.first_layout)val button1: Button = findViewById(R.id.button1) // 可省略button1.setOnClickListener {Toast.makeText(this, "You clicked Button 1", Toast.LENGTH_SHORT).show()}
}
第一個
參數是Context,也就是Toast要求的上下文,由于Activity本身就是一個Context對象,因此這里直接傳入this即可。第二個參數是Toast顯示的文本內容。第三個參數是Toast顯示的時長,有兩個內置常量可以選擇:Toast.LENGTH_SHORT和Toast.LENGTH_LONG。
Kotlin編寫的Android項目在app/build.gradle文件的頭部默認引入了一個kotlin-android-extensions插件,這個插件會根據布局文件中定義的控件id自動生成一個具有相同名稱的變量,我們可以在Activity里直接使用這個變量,而不用再調用findViewById()方法。
活動中使用Menu
新建Menu布局文件;
重寫onCreateOptionsMenu()方法;
定義菜單響應事件:重寫onOptionsItemSelected()方法;
銷毀一個活動
按回退鍵,或者在代碼中執行finish()方法。
使用Intent實現活動間啟動
什么是Intent:Intent是Android程序中各組件之間進行交互的一種重要方式,它不僅可以指明當前組件想要執行的動作,還可以在不同組件之間傳遞數據。Intent一般可用于啟動Activity、啟動Service以及發送廣播等場景。
Intent大致可以分為兩種:顯式Intent和隱式Intent。
顯示啟動
Intent有多個構造函數的重載,其中一個是Intent(Context packageContext, Class<?> cls)。這個構造函數接收兩個參數:
第一個參數Context要求提供一個啟動Activity的上下文;
第二個參數Class用于指定想要啟動的目標Activity,通過這個構造函數就可以構建出Intent的“意圖”。
那么接下來我們應該怎么使用這個Intent呢?Activity類中提供了一個startActivity()方法,專門用于啟動Activity,它接收一個Intent參數,這里我們將構建好的Intent傳入startActivity()方法就可以啟動目標Activity了。
button1.setOnClickListener {val intent = Intent(this, SecondActivity::class.java)startActivity(intent)
}
隱式啟動
隱式Intent則含蓄了許多,它并不明確指出想要啟動哪一個Activity,而是指定了一系列更為抽象的action和category等信息,然后交由系統去分析這個Intent,并幫我們找出合適的Activity去啟動。
通過在標簽下配置的內容,可以指定當前Activity能夠響應的action和category。
使用隱式Intent,不僅可以啟動自己程序內的Activity,還可以啟動其他程序的Activity,這就使多個應用程序之間的功能共享成為了可能。
還可以通過intent.setData(Uri uri);接口設置欲訪問的數據。
與此對應,我們還可以在標簽中再配置一個標簽,用于更精確地指定當前Activity能夠響應的數據。標簽中主要可以配置以下內容。
android:scheme。用于指定數據的協議部分,如上例中的https部分。
android:host。用于指定數據的主機名部分,如上例中的www.baidu.com部分。
android:port。用于指定數據的端口部分,一般緊隨在主機名之后。
android:path。用于指定主機名和端口之后的部分,如一段網址中跟在域名之后的內容。
android:mimeType。用于指定可以處理的數據類型,允許使用通配符的方式進行指定。
只有當標簽中指定的內容和Intent中攜帶的Data完全一致時,當前Activity才能夠響應該Intent。
活動間數據傳遞
- 傳遞數據給下一個活動:將數據封裝在intent對象中,例如需要傳遞字符串對象,使用Intent.putExtra(String key, String value),接受端活動中調用父類的getIntent()方法可獲得傳遞過來的intent,然后通過intent.getStringExtra(String key)即可獲得value。
- 傳遞數據給上一個活動:
啟動活動:在啟動活動時,使用startActivityForResult(Intent intent, int requestCode)方法,第二個參數請求碼用于標識是哪次接口調用開啟的活動,然后銷毀后返回的結果。
活動銷毀前封裝要返回的結果:將要返回的結果通過Intent對象封裝,活動銷毀前調用setResult(RESULT_OK, intent)接口,設置要返回的結果。第一個參數是狀態碼,用于向上一個活動返回處理結果,一般取值為RESULT_OK RESULT_CANCLED。
銷毀活動:調用finish()接口。
接收結果:活動銷毀后,會在上一個活動的onAcitivityResult(int requestCode, int resultCode, Intent data)方法中接收到剛剛銷毀的活動返回的結果。
活動的生命周期
返回棧
其實Android是使用任務(task)來管理Activity的,一個任務就是一組存放在棧里的Activity的集合,這個棧也被稱作返回棧(back stack)。棧是一種后進先出的數據結構,在默認情況下,每當我們啟動了一個新的Activity,它就會在返回棧中入棧,并處于棧頂的位置。而每當我們按下Back鍵或調用finish()方法去銷毀一個Activity時,處于棧頂的Activity就會出棧,前一個入棧的Activity就會重新處于棧頂的位置。系統總是會顯示處于棧頂的Activity給用戶。
活動的狀態
運行狀態:當一個Activity位于返回棧的棧頂時,Activity就處于運行狀態。
暫停狀態:當一個Activity不再處于棧頂位置,但仍然可見時,Activity就進入了暫停狀態。
你可能會覺得,既然Activity已經不在棧頂了,怎么會可見呢?這是因為并不是每一個Activity都會占滿整個屏幕,比如對話框形式的Activity只會占用屏幕中間的部分區域。處于暫停狀態的Activity仍然是完全存活著的,系統也不愿意回收這種Activity(因為它還是可見的,回收可見的東西都會在用戶體驗方面有不好的影響),只有在內存極低的情況下,系統才會去考慮回收這種Activity。
停止狀態:當一個Activity不再處于棧頂位置,并且完全不可見的時候,就進入了停止狀態。系統仍然會為這種Activity保存相應的狀態和成員變量,但是這并不是完全可靠的,當其他地方需要內存時,處于停止狀態的Activity有可能會被系統回收。
銷毀狀態:一個Activity從返回棧中移除后就變成了銷毀狀態。系統最傾向于回收處于這種狀態的Activity,以保證手機的內存充足。
活動的生存期
七個關鍵回調函數:
onCreate():在Activity第一次被創建的時候調用。你應該在這個方法中完成Activity的初始化操作,比如加載布局、綁定事件等。
onStart():由不可見變為可見的時候調用。
onResume():在Activity準備好和用戶進行交互的時候調用。此時的Activity一定位于返回棧的棧頂,并且處于運行狀態。
onPause():這個方法在系統準備去啟動或者恢復另一個Activity的時候調用。我們通常會在這個方法中將一些消耗CPU的資源釋放掉,以及保存一些關鍵數據,但這個方法的執行速度一定要快,不然會影響到新的棧頂Activity的使用。
onStop():在Activity完全不可見的時候調用。它和onPause()方法的主要區別在于,如果啟動的新Activity是一個對話框式的Activity,那么onPause()方法會得到執行,而onStop()方法并不會執行。
onDestroy():在Activity被銷毀之前調用,之后Activity的狀態將變為銷毀狀態。
onRestart():在Activity由停止狀態變為運行狀態之前調用,也就是Activity被重新啟動了。
基于以上關鍵回調函數,活動的三種生存期:
完整生存期:在onCreate()方法和onDestroy()方法之間所經歷的就是完整生存期。
可見生存期:在onStart()方法和onStop()方法之間所經歷的就是可見生存期。在可見生存期內,Activity對于用戶總是可見的,即便有可能無法和用戶進行交互。我們可以通過這兩個方法合理地管理那些對用戶可見的資源。比如在onStart()方法中對資源進行加載,而在onStop()方法中對資源進行釋放,從而保證處于停止狀態的Activity不會占用過多內存。
前臺生存期:Activity在onResume()方法和onPause()方法之間所經歷的就是前臺生存期。在前臺生存期內,Activity總是處于運行狀態,此時的Activity是可以和用戶進行交互的,我們平時看到和接觸最多的就是這個狀態下的Activity。
活動的啟動流程
onCreate(): 首次被創建時觸發,可以做一些初始化工作,如布局加載。
onStart(): 正在被啟動,還沒有顯示在前臺。
onRestart(): 正在重新啟動,一般情況是活動從不可見重新變為可見狀態時,被調用。
onResume(): 表示已經可見了,并且出現在前臺并且開始活動。準備和用戶交互的時候調用,此時活動一定在返回棧棧頂
onPause(): 表示正在停止,仍然可見。onPause中不可以進行耗時操作,會影響到新的活動的顯示。在系統準備去啟動或者恢復另一個活動的時候調用,通常該函數用于釋放一些系統資源以及保存數據
onStop(): 表示活動即將停止,已經不可見了,位于后臺。在活動完全不可見的時候調用
onDestroy(): 表示即將銷毀,可以做一些回收資源的工作。
活動的回收和重建
被回收時保存狀態數據:被回收前調用onSaveInstanceState(Bundle outState)方法保存數據,通過調用Bundle對象的putString(),putInt()等方法保存,傳入key和value。
被重建時恢復狀態數據:被重建時通過onCreate(Bundle savedInstanceState)方法恢復數據,通過調用Bundle對象的getString(“key”),getInt(“key”)等方法恢復狀態數據。
通過Bundle在活動間傳遞數據:可以將數據保存在Bundle中,然后再將其保存在Intent中,實現活動間信息的傳遞。
如何在活動銷毀前保存狀態
由于系統資源不足,會自動回收某些停止狀態的活動,當按返回鍵后,其實還是會重新顯示出來這些已經被回收銷毀的活動,這是因為此時會重新創建活動onCreate(),但是需要思考:如何在活動重建后已經能夠讓用戶看到銷毀前一些輸入的信息?這就需要在銷毀前保存活動的數據信息。
保存數據:通過onSaveInstanceState(Bundle outState)接口。Bundle提供了一系列的方法用于保存數據,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型數據,以此類推。每個保存方法需要傳入兩個參數,第一個參數是鍵,用于后面從Bundle中取值,第二個參數是真正要保存的內容。
恢復數據:onCreate()方法其實也有一個Bundle類型的參數。這個參數在一般情況下都是null,但是如果在Activity被系統回收之前,你通過onSaveInstanceState()方法保存數據,這個參數就會帶有之前保存的全部數據,我們只需要再通過相應的取值方法將數據取出即可。
活動的啟動模式
啟動模式一共有4種,分別是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通過給標簽指定android:launchMode屬性來選擇啟動模式。
standard
在standard模式下,每當啟動一個新的Activity,它就會在返回棧中入棧,并處于棧頂的位置。對于使用standard模式的Activity,系統不會在乎這個Activity是否已經在返回棧中存在,每次啟動都會創建一個該Activity的新實例。
singleTop
當Activity的啟動模式指定為singleTop,在啟動Activity時如果發現返回棧的棧頂已經是該Activity,則認為可以直接使用它,不會再創建新的Activity實例。
singleTask
當Activity的啟動模式指定為singleTask,每次啟動該Activity時,系統首先會在返回棧中檢查是否存在該Activity的實例,如果發現已經存在則直接使用該實例,并把在這個Activity之上的所有其他Activity統統出棧,如果沒有發現就會創建一個新的Activity實例。
singleInstance
指定為singleInstance模式的Activity會啟用一個新的返回棧來管理這個Activity(其實如果singleTask模式指定了不同的taskAffinity,也會啟動一個新的返回棧)
那么這樣做有什么意義呢?想象以下場景,假設我們的程序中有一個Activity是允許其他程序調用的,如果想實現其他程序和我們的程序可以共享這個Activity的實例,應該如何實現呢?使用前面3種啟動模式肯定是做不到的,因為每個應用程序都會有自己的返回棧,同一個Activity在不同的返回棧中入棧時必然創建了新的實例。而使用singleInstance模式就可以解決這個問題,在這種模式下,會有一個單獨的返回棧來管理這個Activity,不管是哪個應用程序來訪問這個Activity,都共用同一個返回棧,也就解決了共享Activity實例的問題。
可以通過在AndroidManifest.xml中通過給標簽指定android:launchMode指定啟動模式。
- 標準模式:standard
每啟動一次Activity, 就會創建一個新的Activity實例并置于棧頂。誰啟動了這個Activity, 那么這個Activity就運行在啟動它的那個Activity所在的棧中。 - 棧頂復用模式:singleTop
如果需要新建的Activity位于任務棧棧頂, 那么此Activity的實例就不會重建, 而是重用棧頂的實例。
如果棧頂不是新建的Activity,就會創建該Activity新的實例, 并放入棧頂。 - 棧內復用模式:singleTask
該模式是一種單例模式, 即一個棧內只有一個該Activity實例。 該模式, 可以通過在AndroidManifest文件的Activity中指定該Activity需要加載到那個棧中, 即singleTask的Activity可以指定想要加載的目標棧。 singleTask和taskAffinity配合使用, 指定開啟的Activity加入到哪個棧中。
在這種模式下, 如果Activity指定的棧不存在, 則創建一個棧, 并把創建的Activity壓入棧內;
如果Activity指定的棧存在, 如果其中沒有該Activity實例, 則會創建Activity并壓入棧頂;
如果其中有該Activity實例, 則把該Activity實例之上的Activity殺死清除出棧, 重用并讓該Activity實例處在棧頂, 然后調用onNewIntent()方法。
4. 單例模式:singleInstance
該種啟動模式下,會啟用一個新的返回棧管理此種活動,以滿足不同應用共享某活動的情況。
作為棧內復用模式( singleTask) 的加強版,打開該Activity時, 直接創建一個新的任務棧, 并創建該Activity實例放入新棧中。 一旦該模式的Activity實例已經存在于某個
棧中, 任何應用再激活該Activity時都會重用該棧中的實例。
可以在啟動Activity時, 通過Intent的addFlags()方法設置啟動模式,啟動模式對應的Flags:
(1)FLAG_ACTIVITY_NEW_TASK 其效果與指定Activity為singleTask模式一致。
(2)FLAG_ACTIVITY_SINGLE_TOP 其效果與指定Activity為singleTop模式一致。
(3)FLAG_ACTIVITY_CLEAR_TOP 具有此標記位的Activity, 當它啟動時, 在同一個任務棧中所有位于它上面的Activity都要出棧。 如果和singleTask模式一起出現,若被啟動的Activity已經存在棧中, 則清除其之上的Activity, 并調用該Activity的onNewIntent方法。 如果被啟動的Activity采用standard模式, 那么該Activity連同之上的所有Activity出棧, 然后創建新的Activity實例并壓入棧中。
活動的優先級
資源內存不足導致優先級低的互動會被殺死。從高到低:
前臺活動
可見但非前臺活動
后臺活動
常見面試問題:
說下Activity生命周期?
Activity的啟動流程?
onStart 和 onResume、onPause 和 onStop 的區別?
Activity A 啟動另一個Activity B 會調用哪些方法?如果B是透明主題的又或是個DialogActivity呢?
此問題考察當活動A依然可見時,onStop()是不會調用的。
Activity A跳轉Activity B,再按返回鍵,生命周期執行的順序?
橫豎屏切換,按home鍵,按返回鍵,鎖屏與解鎖屏幕,跳轉透明Activity界面,啟動一個 Theme 為 Dialog 的 Activity,彈出Dialog時Activity的生命周期?
說下onSaveInstanceState()方法的作用 ? 何時會被調用?
onSaveInstanceState(),onRestoreInstanceState的調用時機?
Activity的onNewIntent()方法什么時候會執行?
onCreate和onRestoreInstance方法中恢復數據時的區別?
activity的啟動模式和使用場景?
activty間傳遞數據的方式?
Activity之間傳遞數據的方式Intent是否有大小限制,如果傳遞的數據量偏大,有哪些方案?
顯示啟動和隱式啟動?
跨App啟動Activity的方式,注意事項?
scheme使用場景,協議格式,如何使用?
用于定義本Activity能夠響應的啟動申請所攜帶的協議格式。
ANR 的四種場景?
耗時2個月,終于串通ANR的一切
Activity任務棧是什么?
有哪些Activity常用的標記位Flags?
Activity的數據是怎么保存的,進程被Kill后,保存的數據怎么恢復的?