Activity是Android四大組件中用來顯示界面和操作互動的,一般來說,我們手機上都會打開多個App,每個App又有多個Activity,在用戶看來,這些Activity的創建、回退、跳轉、復用等,都是用戶體驗,甚至是業務邏輯的一部分,所以Android提供了完備的管理機制。
先來梳理一下基礎知識。
Process、AMS、ActivityStack、ActivityTask
- Process,系統為App提供的進程(Process)資源,默認一個App一個Process,我們也可以要求為一個App提供多個Process。
- AMS,系統開機啟動時,會啟動ActivityManagerService(簡稱AMS)來管理四大組件以及Intent、pendingintent、apk進程、task和釋放組件內存等。AMS是一個獨立的進程(SystemServie、AMS、PMS、WMS等系統服務,實質都是一個個App,各自擁有自己的進程),系統只有一個AMS,而且因為AMS在獨立的進程上,所以AMS和App之間,需要通過IPC機制進行跨進程通信。
- ActivityStack ,AMS為了管理Activity實例,會建立并管理一個ActivityStack,用ActivityStackSupervisor來更新這個棧,因為系統只有一個AMS,所以ActivityStack也只有一個。ActivityStack是一個管理者的角色,會管理協調ActivityRecord和TaskRecord,以及所有的任務棧。
- ActivityTask,系統中的所有Activity,在面向用戶時可能分成多個組,這是按照任務棧(Task)來組織的,我們常說的Activity啟動模式,其實是操作任務棧(Task),而不是操作Activity棧(Stack)。
組織形式大概是這樣的:
(我們可以用gethashcode()獲取Activity實例的唯一標識、用gettaskaffinity()獲取Activity所在的任務棧,用adb shell dumpsys activity命令可以列出所有的Activity)
可見,Activity管理的核心在于AMS,AMS中分兩個維度來管理Activity,一個是ActivityStack,只有一個,用ActivityRecord來記錄對應的Activity信息,主要用來管理系統中有哪些Activity;另一個是ActivityTask,可能有多個,用TaskRecord來記錄對應的Activity信息,主要用來把Activity組合成為多個Task棧,以便按照特定的業務邏輯展示給用戶。
所以,要管理多個Activity之間的關系,主要依靠任務棧Task。
任務棧(Task)
長按Home鍵(有的設備是長按菜單鍵),那些最近打開的App記錄,就是任務棧Task。Task任務棧也叫BackStack回退棧,是后進先出棧,在一個Task棧里一直按Back鍵,就會不斷回退到上一個Activity(包括其他App的Activity),直到回退至Home,所以,用戶感知到的Activity界面回退邏輯,是由Task棧決定的,管理Activity的回退跳轉邏輯,其實就是管理Task任務棧。
Task不止一個,我們有可能會把現有的Task整體移動到后臺,然后創建一個新的Task,點開Notification、點擊Home鍵(或在Activity中調用moveTaskToBack(true),false值表示當前Activity必須在棧底)、App創建NewTask等行為,都可能把Task移動到后臺,創建新的Task。
對Task任務棧的管理,主要包括Activity啟動模式和IntentFlag創建方式兩部分。
Activity的四種啟動模式
啟動Activity的重點在于,如何復用已經存在的Activity實例,所以四種啟動模式實際上是四種復用模式,針對復用策略,Android中定義了四種啟動模式(android:launchMode):
- "standard",默認,不復用,每次創建新實例。
- "singleTop",棧頂復用,檢查棧頂,如果不是要啟動的Activity,創建新實例;如果是,就復用。
- "singleTask",棧中復用,檢查任務棧,如果沒有要啟動的Activity,創建新實例;如果有,就把該Activity上方的其他Activity全部推出任務棧,復用該Activity。
- "singleInstance",單例模式,獨享一個任務棧,且只有一個任務棧,只有
一個Activity實例。
這四種啟動模式可以在manifest文件中定義,也可以在intent中用代碼動態賦值(例如添加FLAG_ACTIVITY_SINGLE_TOP標識,等同于設置singleTop屬性)。
這四種模式中,standard和singleTop類型的Activity可能出現多個實例,singleTask和singleInstance類型則只能有一個實例。
如果要使用startActivityForResult,在5.0以前還有使用限制,只允許standard<->singleTop、singleTask->standard、singleTask->singleTop,其他跳轉因為無法確?;氐皆瑼ctivity,可能會遇到RESULT_CANCELD問題導致無法接收返回值。
如果Activity被復用了,就會回調onNewIntent()函數。
正常啟動的Activity生命周期是onCreate()-->onStart()-->onResume()...。
復用的Activity生命周期是onNewIntent()-->onResart()-->onStart()--> onResume()...
一些常見的IntentFlag
除了上一節的四種啟動模式之外,我們還可以在代碼中設置intentFlag,用更豐富的形式來定義Activity的創建和復用策略。
intentFlag有時候需要和taskAffinity屬性(android:taskAffinity)配合使用,taskAffinity指的是Activity的任務親和性,即Activity與哪個Task更親和,默認值是應用的包名,taskAffinity屬性在2種情況下起作用:啟動 activity的Intent對象添加了FLAG_ACTIVITY_NEW_TASK標記,或activity的allowTaskReparenting屬性為true。
一些常見的IntentFlag如下:
- FLAG_ACTIVITY_NEW_TASK (默認)
其實這種標識下,系統會根據Activity的taskAffinity去創建一個新的Task任務棧,并向任務棧中壓入一個新創建的Activity實例。
不過,一般情況下App中的taskAffinity都是同一個默認值(包名),這個Task已經存在了,這樣的話,運行效果就相當于standard啟動模式,在已有的Task中壓入一個新建的Activity。
還需要說明的是,如果要用startActivityForResult的形式來調起新的Activity并等待返回結果,就不能使用FLAG_ACTIVITY_NEW_TASK標記,否則調用方的Activity可能會立即收到onActivityResult,實際上就是回調失效了。這是因為使用startActivityForResult時,兩個Activity需要在同一個Task里,Android認為不同Task之間的Activity是不能傳遞數據的。 - FLAG_ACTIVITY_SINGLE_TOP
相當于singleTop啟動模式。 - FLAG_ACTIVITY_CLEAR_TOP
模式情況下,會把目標Activity頂部的所有Activity都銷毀,同時連同目標Activity也銷毀,然后重建目標Activity。
如果和FLAG_ACTIVITY_SINGLE_TOP一起使用,即:
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
就會復用目標Activity,這時相當于singleTask啟動模式(CLEAR_TOP這個名字也揭示了singleTask的實質)。
FLAG_ACTIVITY_CLEAR_TOP往往和FLAG_ACTIVITY_NEW_TASK一起使用。用2個標記可以定位已存在的activity并讓它處于可以響應intent的位置。
- FLAG_ACTIVITY_CLEAR_TASK
清空目標TASK,把新建的Activity作為棧底的Activity,必須與FLAG_ACTIVITY_NEW_TASK一起使用。 - FLAG_ACTIVITY_REORDER_TO_FRONT
如果Task中已經有實例,把該實例挪到棧頂。(優先級低于FLAG_ACTIVITY_CLEAR_TOP)。 - FLAG_ACTIVITY_BROUGHT_TO_FRONT
這個標志一般不是由程序代碼設置的,而是在launchMode中設置singleTask模式時系統幫你設定。 - FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
任務重置,在Activity導致新建Task或挪動到Task頂部時使用。 - FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
為Task設置一個還原點,當Task恢復到前臺時,找到有這個屬性的Activity,把這個Activity頂部的其他Activity,連同這個Activity一起銷毀。 - FLAG_ACTIVITY_MULTIPLE_TASK
Android系統為了節省資源,默認是復用Activity的,在打開Activity時,總是先搜索存在的task棧,去尋找匹配intent的一個activity。
但是,如果使用了FLAG_ACTIVITY_MULTIPLE_TASK標識(和FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK一起使用),就會跳過復用搜索步驟,直接創建一個新的task棧,并且在里面啟動新的activity。 - FLAG_ACTIVITY_NO_USER_ACTION,用戶行為標識,用來區分離開當前Activity是用戶行為還是系統行為(比如呼入電話,會導致當前Activity退至后臺,但這不是用戶行為),如果不是用戶行為,就不會執行生命周期中的onUserLeaveHint(),這樣我們可以知道用戶有沒有做某些操作(比如是否看到了推送的通知)。
- FLAG_ACTIVITY_NO_ANIMATION
Activity切換時不展示動畫。
其他一些與棧相關的Activity屬性:
- android:taskAffinity
親和性,在intentFlag標識中和FLAG_ACTIVITY_NEW_TASK一起作用;或者與android:allowTaskReparenting共同起作用。 - android:allowTaskReparenting
允許重排任務棧,就是說Activity可以更換所在的Task任務棧,Activity可能在TaskA中,此時系統把一個TaskB移動到前臺,如果TaskA和TaskB的affinity相同,那么Activity就會被挪到TaskB。 - android:excludeFromRecents
Activity不添加到最近打開的應用列表中。 - android:alwaysRetainTaskState
保存Task棧中的Activity,當Task被移動到后臺后,如果長期沒有使用,Android可能會去回收內存,僅保留棧底的Activity,其他Activity全部回收掉,如果alwaysRetainTaskState屬性為true,系統就不會進行回收。 - android:clearTaskOnLaunch
催促回收Task棧(與alwaysRetainTaskState相反),這個屬性會被賦給棧底的Activity,一旦用戶離開Task,系統就會立即回收棧中的Activity,僅保留棧底的Activity。 - android:finishOnTaskLaunch
催促回收Activity,被賦予這個屬性的Activity,一旦用戶退出Task,Activity就會立即被系統回收,Task中不再保留這個Activity,如果恰好是棧底Activity,那么Task也就清空了。 - android:noHistory
清空歷史,離開Activity后,立即回收Activity,Task任務棧中不保存該Activity。noHistory和finishOnTaskLaunch的區別在于銷毀Activity的時機,noHistory是在離開Activity時銷毀,finishOnTaskLaunch是在切換Task時銷毀。
Scheme跳轉(頁面內跳轉)
我們知道,Activity跳轉是通過Intent來實現的,在定義Intent時,有三種形式:
- 類名
定義intent時設置目標Activity的類名
intent.setClass(context,ActivityA.class);
setClass其實就是setComponent,相當于
intent.setComponent(new ComponentName(context, ActivityA.class)); - Action
需要在manifest中定義action
<intent-filter> <action android:name="your.action.name" />
定義intent時設置Action
intent.setAction(your.action.name); - Scheme
需要在manifest中定義scheme
定義intent時設置url
new Intent(Intent.ACTION_VIEW, Uri.parse(Url) )
在這三種形式中,Scheme是最靈活,適用最廣的,因為只需要一個url,就可以實現跳轉。
Scheme是一種頁面內跳轉協議,在Android和IOS都有使用,Activity向系統注冊URL Scheme,就可以通過特定的URL,直接打開App中的某些頁面,常用于支持以下幾種場景:
- 服務器下發跳轉路徑,客戶端根據服務器下發跳轉路徑,創建Intent,(new Intent(Intent.ACTION_VIEW, Uri.parse(Url) ) ) ,實現跳轉;
- Web頁面點擊,根據具體跳轉路徑判斷,如果(url.indexOf(H5Constant.SCHEME) != -1),就去創建Intent,(Intent intent = new Intent(Intent.ACTION_VIEW, uri) ) ,實現跳轉;
- App端收到服務器端下發的通知,從消息中解析NotifeConstant.VALID_ACTION_URL得到URL,然后創建Intent,(Intent intent =H5Constant.buildSchemeFromUrl(Url)),實現跳轉。
Scheme協議中,URL的格式為:
[scheme:][//domain][:port][path][?query params][#fragment]
其中,scheme、domain、path都需要在manifest中定義。
<activity android:name=".TargetActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<!--允許從瀏覽器打開App-->
<category android:name="android.intent.category.BROWSABLE"/>
<!--可以定義多組scheme,多個URL指向同一個Activity-->
<data android:scheme="biz1"
android:host="com.buy"
android:path="/path"/>
<data android:scheme="biz2"
android:host="com.earn"
android:path="/route"/>
</intent-filter>
</activity>
被啟動的Activity可以從Intent中找到URL中的值
Intent intent = getIntent();
String scheme = intent.getScheme();
Uri uri = intent.getData();
String tab= uri.getQueryParameter("your param");
引用
Activity啟動模式圖文詳解:standard, singleTop, singleTask 以及 singleInstance
Android中Activity四種啟動模式和taskAffinity屬性詳解
android深入解析Activity的launchMode啟動模式,Intent Flag,taskAffinity
一次搞定Process和Task