android預載入activity
A. 安卓應用啟動詳解:從Zygote到你的Activity.onCreate()
翻譯自: https://android.jlelse.eu/android-application-launch-explained-from-zygote-to-your-activity-oncreate-8a8f036864b
這篇文章講解當用戶點擊應用圖標時,安卓如何啟動你的應用。安卓系統做了很多幕後工作,來使得你的launch activity對用戶可見。本文通過重要階段的講解和調用序列詳細講解這一過程。
安卓應用在這兩個方面是獨特的:
多個入口點 :Android應用程序由不同的組件組成,它們可以調用其他應用程序擁有的組件。這些組件大致對應於任何應用程序的多個入口點。因此,它們不同於具有像main()方法那樣的單個入口點的傳統應用程序。
擁有自己的小世界 :每個Android應用程序都生活在自己的世界中,它在單獨的進程中運行,擁有自己的Dalvik VM實例,並分配有唯一的用戶ID。
必要時會啟動Android進程。
每當用戶或其他系統組件請求執行屬於您應用程序的組件(可能是服務,活動或意圖接收器)時,Android系統都會為您的應用程序啟動一個新進程(如果尚未運行)。通常,進程一直運行直到被系統殺死。應用程序流程是按需創建的,在您看到應用程序的啟動活動啟動並運行之前,發生了許多事情。
每個應用程序都在其自己的進程中運行 :默認情況下,每個Android應用程序都在其自己的Android進程中運行,而這個進程只不過是一個Linux進程,而該進程首先需要一個執行線程。例如,當您單擊電子郵件中的超鏈接時,網頁將在瀏覽器窗口中打開。您的郵件客戶端和瀏覽器是兩個單獨的應用程序,它們分別在兩個單獨的進程中運行。click事件使Android平台啟動新進程,以便它可以在其自身進程的上下文中實例化瀏覽器活動。這對於應用程序中的任何其他組件同樣適用。
讓我們退後一會兒,快速瀏覽一下系統啟動過程。與大多數基於Linux的系統一樣,啟動載入程序在啟動時將載入內核並啟動init進程。然後,init會生成稱為「守護程序」的低級Linux進程,例如android debug守護程序,USB守護程序等。這些守護程序通常處理低級硬體介面,包括無線電介面。
然後,初始化過程會啟動一個非常有趣的過程,稱為「zygote'。
顧名思義,這是其餘Android應用程序的開始。這是初始化Dalvik虛擬機的第一個實例的過程。它還預載入Android應用程序框架和系統上安裝的各種應用程序使用的所有常見類。因此,它准備進行復制。它統計偵聽套接字介面上的將來請求,以產生新的虛擬機(VM)來管理新的應用程序進程。收到新請求後,它會分叉以創建一個新進程,該進程將獲取預先初始化的VM實例。
zygote之後,init啟動運行時過程。
然後zygote分叉以啟動一個名為System server的託管良好的進程。系統伺服器在其自己的上下文中啟動所有核心平台服務,例如活動管理器服務和硬體服務。
此時,完整的堆棧已准備就緒,可以啟動第一個應用程序流程-主頁應用程序,該應用程序顯示主屏幕(也稱為啟動器應用程序)。
click事件被轉換為 startActivity(intent), 並通過Binder IPC路由到 ActivityManagerService 。ActvityManagerService執行多個步驟
如您所見,當用戶單擊圖標並啟動新應用程序時,許多事情發生在幕後。這是全圖:
流程創建:
ActivityManagerService 通過調用 startProcessLocked() 方法創建一個新進程,該方法通過套接字連接將參數發送到Zygote進程。Zygote派生自己並調用 ZygoteInit.main() ,然後實例化 ActivityThread 對象並返回新創建的進程的進程ID。
默認情況下,每個進程都有一個線程。主線程有一個 Looper 實例來處理來自消息隊列的消息,並且它在 run() 方法的每次迭代中都調用 Looper.loop() 。 Looper 的工作是從消息隊列中彈出消息並調用相應的方法來處理它們。然後,ActivityThread通過隨後調用 Looper.prepareLoop() 和 Looper.loop()來 啟動消息循環。
以下序列詳細捕獲了調用序列:
<figcaption class="jv jw di dg dh jx jy bo b fc cp ga" data-selectable-paragraph="" style="box-sizing: inherit; font-weight: 400; font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 20px; margin-left: auto; margin-right: auto; max-width: 728px; font-size: 14px; color: rgb(117, 117, 117); margin-top: 10px; text-align: center;">Android應用啟動:單擊事件以執行Looper調用順序</figcaption>
應用程序綁定:
下一步是將此新創建的過程附加到特定應用程序。這是通過在線程對象上調用 bindApplication() 來完成的。此方法將 BIND_APPLICATION 消息發送到消息隊列。該消息由 Handler 對象檢索,該對象隨後調用 handleMessage() 方法以觸發特定於消息的操作 -handleBindApplication() 。此方法調用 makeApplication() 方法,該方法將應用程序特定的類載入到內存中。
下圖描述了該調用序列。
<figcaption class="jv jw di dg dh jx jy bo b fc cp ga" data-selectable-paragraph="" style="box-sizing: inherit; font-weight: 400; font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 20px; margin-left: auto; margin-right: auto; max-width: 728px; font-size: 14px; color: rgb(117, 117, 117); margin-top: 10px; text-align: center;">Android應用啟動:BIND_APPLICATION消息處理</figcaption>
啟動活動:
在上一步之後,系統包含負責應用程序的進程,並將應用程序類載入到進程的私有內存中。在新創建的流程和現有流程之間,啟動活動的調用順序很常見。
實際的啟動過程從 realStartActivity() 方法開始, 該 方法在應用程序線程對象上調用 sheleLaunchActivity() 。此方法將 LAUNCH_ACTIVITY 消息發送到消息隊列。該消息由 handleLaunchActivity() 方法處理,如下所示。
假設用戶單擊「視頻瀏覽器」應用程序。啟動該活動的調用順序如圖所示。
<figcaption class="jv jw di dg dh jx jy bo b fc cp ga" data-selectable-paragraph="" style="box-sizing: inherit; font-weight: 400; font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 20px; margin-left: auto; margin-right: auto; max-width: 728px; font-size: 14px; color: rgb(117, 117, 117); margin-top: 10px; text-align: center;">Android應用啟動: LAUNCH_ACTIVITY消息處理 </figcaption>
該活動通過 onCreate() 方法調用開始其託管生命周期。該活動通過 onRestart() 調用進入前台,並通過 onStart() 調用開始與用戶進行交互。
B. Android —— Activity的四種啟動模式
除了Activity的生命周期外,Activity的啟動模式也是一個難點,有時候為了滿足項目的特殊需求,就必須使用Activity的啟動模式。
在默認情況下,當我們多次啟動同一個Activity的時候,系統會創建多個實例並把它們放入任務棧中,但是有些場景重復創建多個實例,是沒有必要且浪費資源的,這就需要啟動模式來修改系統的默認行為。
下面,我將以理論+實踐的形式為大家介紹Activity的啟動模式。
這是系統的默認啟動模式,採用這種模式的Activity無論是否已經存在實例,都會重新創建一個實例,在這種模式下誰啟動了這個Activity,那麼這個Activity就運行在啟動它的那個Activity所在的棧中。
實踐:MainActivity 採用 standard 模式
在這種模式下,如果新的Activity已經位於任務棧的棧頂,那麼此Activity不會被重新創建,同時它的 NewIntent 方法將會被回調。如果新Activity的實例已存在但不是位於棧頂,那麼新Activity依然會被創建。
實踐:MainActivity 採用 singleTop 模式
MainActivity 採用 singleTop 模式,SecondActivity採用 standard 模式
這是一種單實例模式,在這種模式下,只要Activity在一個棧中存在,那麼多次啟動此Activity都不會重新創建實例,而是回調 onNewIntent() 。
實踐:MainActivity 採用 singleTask 模式
MainActivity 採用 singleTask 模式,SecondActivity採用 standard 模式
這是一種加強的 singleTask 模式,它除了具有 singleTask 模式的所有特性外,還加強了一點,那就是具有此模式的Activity只能單獨的位於一個任務棧中。
實踐:MainActivity 採用 singleInstance 模式
MainActivity 採用 singleInstance 模式,SecondActivity採用 standard 模式
以上就是Activity啟動模式的介紹。
歡迎留言指出錯誤。
C. Android之Activity全面解析,有些知識點容易忘記
Activity作為安卓四大組件之一,是最重要也是用得最多的組件,涉及的知識點非常多,有些知識點平時開發很少用到,但在某些場景下需要特別注意,本文詳細整理了Activity涉及的知識點,供開發參考。
針對Activity可以提出很多問題,如:
Activity 的生命周期?
Activity 之間的通信方式?
Activity 各種情況下的生命周期?
橫豎屏切換時 Activity 的生命周期?
前台切換到後台,然後再回到前台時 Activity 的生命周期?
彈出 Dialog 的時候按 Home 鍵時 Activity 的生命周期?
兩個Activity之間跳轉時的生命周期?
下拉狀態欄時 Activity 的生命周期?
Activity 與 Fragment 之間生命周期比較?
Activity 的四種 LaunchMode(啟動模式)的區別?
Activity 狀態保存與恢復?
Activity的轉場動畫有哪些實現方式?
Activity的生命周期中怎麼獲取控制項寬高?
onNewIntent的執行時機?
如何連續退出多個Activity?
如何把Acitivty設置成Dialog樣式 ,android:theme="@android:style/Theme.Dialog"
關於橫豎屏切換的生命周期,對應不同的手機,由於廠商定製的原因,會有不同的效果,如設置了configChanges="orientation」在有些手機會執行各個生命周期,但有些手機卻不會執行。
網上常見的結論如下:
但實際的測試如下:
可以看出,不同廠商的手機切屏生命周期會有差異。
從API 13以上,當設備在橫豎切屏時,「屏幕尺寸」也會發生變化,因此為了杜絕切屏導致頁面銷毀重建,需要加上screenSize,使用設置4,即 android:configChanges="orientation|keyboardHidden|screenSize" .
Activity的四種狀態如下:
在activity處於paused或者stoped狀態下,如果系統內存緊張,可能會被銷毀,當重回該activity時會重建,正常返回和被回收後返回的生命周期如下:
如果是回收後返回,onCreate的參數savedInstanceState不為空。
有哪些場景會觸發onNewIntent回調呢?跟啟動模式有關,首先該Activity實例已經存在,再次啟動才可能觸發。一種情況是啟動模式是singleTask或者singleInstance,無論該activity在棧中哪個位置,都會觸發onNewIntent回調,並且把上面其他acitivity移除,另一種情況是啟動模式是singleTop或者以FLAG_ACTIVITY_SINGLE_TOP啟動,並且該activity實例在棧頂,會觸發onNewIntent,如果不在棧頂是重新創建的,不會觸發。
在實際業務開發中,往往碰到需要連續退出多個activity實例,下面整理了幾種常見方法:
● 發送特定廣播
1、在需要處理連續退出的activity注冊該特定廣播;
2、發起退出的activity發送該特定廣播;
3、接收到該廣播的activity 調用finish結束頁面。
● 遞歸退出
1、用startActivityForResult啟動新的activity;
2、前一個頁面finish時,觸發onActvityResult回調,再根據requestCode和resultCode處理是否finish,達到遞歸退出的效果。
● FLAG_ACTIVITY_CLEAR_TOP
通過intent.setFlag(Intent.FLAG_ACTIVITY_CLEAR_TOP)啟動新activity,如果棧中已經有該實例,則會把該activity之上的所有activity關閉,達到singleTop啟動模式的效果。
● 自定義activity棧
1、自定義activity列表,新打開activity則加入棧中,關閉則移除棧;
2、需要退出多個activity時,則循環從棧中移除activity實例,並調用finish。
在討論Activity啟動模式經常提到任務棧,那到底什麼是任務棧?
任務是一個Activity的集合,它使用棧的方式來管理其中的Activity,這個棧又被稱為返回棧(back stack),棧中Activity的順序就是按照它們被打開的順序依次存放的。返回棧是一個典型的後進先出(last in, first out)的數據結構。下圖通過時間線的方式非常清晰地向我們展示了多個Activity在返回棧當中的狀態變化:
taskAffinity 任務相關性,可以用於指定一個Activity更加願意依附於哪一個任務,在默認情況下,同一個應用程序中的所有Activity都具有相同的affinity, 名字為應用的包名。當然了,我們可以為每個 Activity 都單獨指定 taskAffinity 屬性(不與包名相同)。taskAffinity 屬性主要和 singleTask 啟動模式和 allowTaskReparenting 屬性配對使用,在其他情況下沒有意義。
taskAffinity 有下面兩種應用場景:
分為顯示啟動和隱式啟動。
(1)顯示啟動
直接指定待調整的Activity類名。
(2)隱式啟動
Intent 能夠匹配目標組件的 IntentFilter 中所設置的過濾信息,如果不匹配將無法啟動目標 Activity。IntentFilter 的過濾信息有 action、category、data。
IntentFilter 需要注意的地方有以下:
● 一個 Activity 中可以有多個 intent-filter
● 一個 intent-filter 同時可以有多個 action、category、data
● 一個 Intent 只要能匹配任何一組 intent-filter 即可啟動對應 Activity
● 新建的 Activity 必須加上以下這句,代表能夠接收隱式調用
<category android:name="android.intent.category.DEFAULT" />
只要匹配一個action即可跳轉,注意的是action要區分大小寫。
規則:如果intent中有category,則所有的都能匹配到intent-filter中的category,intent中的category數量可用少於intent-filter中的。另外,單獨設置category是無法匹配activity的,因為category屬性是一個執行Action的附加信息。
intent不添加category會匹配默認的,即 「android:intent.category.DEFAULT」
如果上面例子,如果去掉intent.setAction("action_name"),則會拋出異常:
規則:類似action,但data有復雜的結構,只要匹配一個data並且與data中所有屬性都一致就能匹配到Activity,只要有1個屬性不匹配,都無法找到activity。
data的結構:
data 主要是由 URI 和 mimeType 組成的。
URI 可配置很多信息,的結構如下:
與url類似,例如:
mineType:指資源類型包括文本、圖片、音視頻等等,例如:text/plain、 image/jpeg、video/* 等
下面看下data匹配的例子:
只匹配scheme
只匹配scheme也是能匹配到activity的。
匹配scheme、host、port
將上面的data改為
匹配mineType
如果有mineType,則不能僅設置setData或setMineType了,因為setData會把mineType置為null,而setMineType會把data置為null,導致永遠無法匹配到activity,要使用setDataAndType。
使用scheme的默認值contentfile
注意該方法需要在startAtivity方法或者是finish方法調用之後立即執行,不能延遲,但可以在子線程執行。
而在windowAnimationStyle中存在四種動畫:
activityOpenEnterAnimation // 打開新的Activity並進入新的Activity展示的動畫
activityOpenExitAnimation // 打開新的Activity並銷毀之前的Activity展示的動畫
activityCloseEnterAnimation //關閉當前Activity進入上一個Activity展示的動畫
activityCloseExitAnimation // 關閉當前Activity時展示的動畫
overridePendingTransition的方式比較生硬,方法也比較老舊了,不適用於MD風格,google提供了新的轉場動畫ActivityOptions,並提供了兼容包ActivityOptionsCompat。
我們知道在onCreate和onResume裡面直接獲取到控制項寬高為0,那有什麼辦法獲取到控制項的實際寬高?只要有onWindowFocusChanged、view.post、ViewTreeObserver三種方式獲取。
當用戶點擊桌面圖標啟動APP時,背後的流程如下:
我們看到的手機桌面是Launch程序的界面,點擊應用圖標會觸發點擊事件,調用startActivity(intent),然後通過Binder IPC機制,與ActivityManagerService(AMS)通訊,AMS執行一系列操作,最終啟動目前應用,大概流程如下:
通過PackageManager的resolveIntent()收集跳轉intent對象的指向信息,然後通過grantUriPermissionLocked()方法來驗證用戶是否有足夠的許可權去調用該intent對象指向的Activity。如果有許可權,則在新的task中啟動目標activity,如果發現沒有進程,則先創建進程。
如果進程不存在,AMS會調用startProcessLocked創建新的進程,在該方法中,會通過socket的通訊方式通知zygote進程孵化新的進程並返回pid,在新的進程中會初始化ActivityThread,並依次調用Looper.prepareLoop()和Looper.loop()來開啟消息循環。
創建好進程後下一步要將Application和進程綁定起來,AMS會調用上一節創建的ActivityThread對象的bindAppliction方法完成綁定工作,該方法會發送一條BIND_APPLICATION的消息,最終會調用handleBindApplication方法處理消息,並調用makeApplication方法處理消息,載入APP的classes到內存中。
通過前面的步驟,系統已經擁有了該Application的進程,後續的啟動則是從已存在其他進程中啟動Acitivity,即調用realStartAcitvityLocked,該方法會調用Application的主線程對象ActivityThread的sheleLaunchActivity方法,在方法中會發送LAUNCH_ACTIVITY到消息隊列,最終通過handleLaunchActivity處理消息,完成Acitivty的啟動。
Activity
Activity 的 36 大難點,你會幾個?「建議收藏」
[譯]Android Application啟動流程分析