前言
學習過android的同學都是知道Activity,Activity是Android組件中最基本也是最為常見用的四大組件之一,用戶可以用來交互為了完成某項任務。
Activity中所有操作都與用戶密切相關,是一個負責與用戶交互的組件,可以通過setContentView(View)來顯示指定控件。
在一個android應用中,一個Activity通常就是一個單獨的屏幕,它上面可以顯示一些控件也可以監聽并處理用戶的事件做出響應。Activity之間通過Intent進行通信。
同時,Activity也有這自己的生命周期,不同的生命周期Activity的交互狀態也有所不同。
而HarmonyOS的UIAbility就和Android的activity非常相似,今天就介紹一下UIAbility是什么,是如何和用戶交互,頁面間跳轉,以及各個生命周期的作用。
UIAbility概述
UIAbility是一種包含用戶界面的應用組件,主要用于和用戶進行交互。UIAbility也是系統調度的單元,為應用提供窗口在其中繪制界面。
每一個UIAbility實例,都對應于一個最近任務列表中的任務。
一個應用可以有一個UIAbility,也可以有多個UIAbility,如下圖所示。例如瀏覽器應用可以通過一個UIAbility結合多頁面的形式讓用戶進行的搜索和瀏覽內容;而聊天應用增加一個“外賣功能”的場景,則可以將聊天應用中“外賣功能”的內容獨立為一個UIAbility,當用戶打開聊天應用的“外賣功能”,查看外賣訂單詳情,此時有新的聊天消息,即可以通過最近任務列表切換回到聊天窗口繼續進行聊天對話。
一個UIAbility可以對應于多個頁面,建議將一個獨立的功能模塊放到一個UIAbility中,以多頁面的形式呈現。例如新聞應用在瀏覽內容的時候,可以進行多頁面的跳轉使用。
UIAbility內頁面的跳轉和數據傳遞
我們知道,在Android中,activity之間頁面跳轉是通過startActivity方法進行跳轉的,二頁面間參數傳遞則是通過Intent進行通信。而作為HarmonyOS的應用程序入口—UIAbility同樣支持相同的功能。
UIAbility的數據傳遞包括有UIAbility內頁面的跳轉和數據傳遞、UIAbility間的數據跳轉和數據傳遞,這里主要講解UIAbility內頁面的跳轉和數據傳遞。
頁面間的導航可以通過頁面路由router模塊來實現。頁面路由模塊根據頁面url找到目標頁面,從而實現跳轉。通過頁面路由模塊,可以使用不同的url訪問不同的頁面,包括跳轉到UIAbility內的指定頁面、用UIAbility內的某個頁面替換當前頁面、返回上一頁面或指定的頁面等。具體使用方法請參見ohos.router (頁面路由)。
頁面跳轉和參數接收
在使用頁面路由之前,需要先導入router模塊,如下代碼所示。
import router from '@ohos.router';
頁面跳轉的幾種方式,根據需要選擇一種方式跳轉即可。
- 方式一:API9及以上,router.pushUrl()方法新增了mode參數,可以將mode參數配置為router.RouterMode.Single單實例模式和router.RouterMode.Standard多實例模式。
在單實例模式下:如果目標頁面的url在頁面棧中已經存在同url頁面,離棧頂最近同url頁面會被移動到棧頂,移動后的頁面為新建頁,原來的頁面仍然存在棧中,頁面棧的元素數量不變;如果目標頁面的url在頁面棧中不存在同url頁面,按多實例模式跳轉,頁面棧的元素數量會加1。
router.pushUrl({url: 'pages/Second',params: {src: 'Index頁面傳來的數據',}
}, router.RouterMode.Single)
注意:
當頁面棧的元素數量較大或者超過32時,可以通過調用router.clear()方法清除頁面棧中的所有歷史頁面,僅保留當前頁面作為棧頂頁面。
- 方式二:API9及以上,router.replaceUrl()方法新增了mode參數,可以將mode參數配置為router.RouterMode.Single單實例模式和router.RouterMode.Standard多實例模式。
在單實例模式下:如果目標頁面的url在頁面棧中已經存在同url頁面,離棧頂最近同url頁面會被移動到棧頂,替換當前頁面,并銷毀被替換的當前頁面,移動后的頁面為新建頁,頁面棧的元素數量會減1;如果目標頁面的url在頁面棧中不存在同url頁面,按多實例模式跳轉,頁面棧的元素數量不變。
router.replaceUrl({url: 'pages/Second',params: {src: 'Index頁面傳來的數據',}
}, router.RouterMode.Single)
已經實現了頁面的跳轉,接下來,在Second頁面中如何進行自定義參數的接收呢?
通過調用router.getParams()方法獲取Index頁面傳遞過來的自定義參數。
import router from '@ohos.router';@Entry
@Component
struct Second {@State src: string = (router.getParams() as Record<string, string>)['src'];// 頁面刷新展示...
}
頁面返回和參數接收
經常還會遇到一個場景,在Second頁面中,完成了一些功能操作之后,希望能返回到Index頁面,那我們要如何實現呢?
在Second頁面中,可以通過調用router.back()方法實現返回到上一個頁面,或者在調用router.back()方法時增加可選的options參數(增加url參數)返回到指定頁面。
- 調用router.back()返回的目標頁面需要在頁面棧中存在才能正常跳轉。
- 例如調用router.pushUrl()方法跳轉到Second頁面,在Second頁面可以通過調用router.back()方法返回到上一個頁面。
- 例如調用router.clear()方法清空了頁面棧中所有歷史頁面,僅保留當前頁面,此時則無法通過調用router.back()方法返回到上一個頁面。
1:返回上一個頁面。
router.back();
2:返回到指定頁面。
router.back({ url: 'pages/Index' });
3:頁面返回詢問對話框。
有的時候,頁面返回根據業務需要增加一個詢問對話框。即在調用router.back()方法之前,可以先調用router.enableBackPageAlert()方法開啟頁面返回詢問對話框功能。
router.enableBackPageAlert({message: 'Message Info'
});router.back();
注意:
1: router.enableBackPageAlert()方法開啟頁面返回詢問對話框功能,只針對當前頁面生效。例如在調用router.pushUrl()或者router.replaceUrl()方法,跳轉后的頁面均為新建頁面,因此在頁面返回之前均需要先調用router.enableBackPageAlert()方法之后,頁面返回詢問對話框功能才會生效。
2:如需關閉頁面返回詢問對話框功能,可以通過調用router.disableAlertBeforeBackPage()方法關閉該功能即可。
4:返回指定頁面
調用router.back()方法返回上一個指定頁面時,通過指定url,返回指定的頁面。如果需要繼續增加自定義參數,例如可以在返回時增加一個自定義參數data。
router.back({url: 'pages/Index',params: {data: 'Second頁面傳來的數據',}
})
從Second頁面返回到Index頁面。在Index頁面通過調用router.getParams()方法,獲取Second頁面傳遞過來的自定義參數。
注意:
調用router.back()方法,不會新建頁面,返回的是原來的頁面,在原來頁面中@State聲明的變量不會重復聲明,以及也不會觸發頁面的aboutToAppear()生命周期回調,因此無法直接在變量聲明以及頁面的aboutToAppear()生命周期回調中接收和解析router.back()傳遞過來的自定義參數。
可以放在業務需要的位置進行參數解析。示例代碼在Index頁面中的onPageShow()生命周期回調中進行參數的解析。
import router from '@ohos.router';
@Entry
@Component
struct Index {@State src: string = '';onPageShow() {this.src = (router.getParams() as Record<string, string>)['src'];}// 頁面刷新展示...
}
UIAbility的生命周期
當用戶瀏覽、切換和返回到對應應用的時候,應用中的UIAbility實例會在其生命周期的不同狀態之間轉換。
UIAbility類提供了很多回調,通過這些回調可以知曉當前UIAbility的某個狀態已經發生改變:例如UIAbility的創建和銷毀,或者UIAbility發生了前后臺的狀態切換。
在UIAbility的使用過程中,會有多種生命周期狀態。掌握UIAbility的生命周期,對于應用的開發非常重要。
為了實現多設備形態上的裁剪和多窗口的可擴展性,系統對組件管理和窗口管理進行了解耦。UIAbility的生命周期包括Create、Foreground、Background、Destroy四個狀態,WindowStageCreate和WindowStageDestroy為窗口管理器(WindowStage)在UIAbility中管理UI界面功能的兩個生命周期回調,從而實現UIAbility與窗口之間的弱耦合。如下圖所示。
- Create狀態,在UIAbility實例創建時觸發,系統會調用onCreate回調。可以在onCreate回調中進行相關初始化操作。
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';export default class EntryAbility extends UIAbility {onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {// 應用初始化...}...
}
例如用戶打開電池管理應用,在應用加載過程中,在UI頁面可見之前,可以在onCreate回調中讀取當前系統的電量情況,用于后續的UI頁面展示。
- UIAbility實例創建完成之后,在進入Foreground之前,系統會創建一個WindowStage。每一個UIAbility實例都對應持有一個WindowStage實例。
WindowStage為本地窗口管理器,用于管理窗口相關的內容,例如與界面相關的獲焦/失焦、可見/不可見。
可以在onWindowStageCreate回調中,設置UI頁面加載、設置WindowStage的事件訂閱。
在onWindowStageCreate(windowStage)中通過loadContent接口設置應用要加載的頁面,Window接口的使用詳見窗口開發指導。
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';export default class EntryAbility extends UIAbility {...onWindowStageCreate(windowStage: window.WindowStage) {// 設置UI頁面加載// 設置WindowStage的事件訂閱(獲焦/失焦、可見/不可見)...windowStage.loadContent('pages/Index', (err, data) => {...});}...
}
例如用戶打開游戲應用,正在打游戲的時候,有一個消息通知,打開消息,消息會以彈窗的形式彈出在游戲應用的上方,此時,游戲應用就從獲焦切換到了失焦狀態,消息應用切換到了獲焦狀態。對于消息應用,在onWindowStageCreate回調中,會觸發獲焦的事件回調,可以進行設置消息應用的背景顏色、高亮等操作。
- Foreground和Background狀態,分別在UIAbility切換至前臺或者切換至后臺時觸發。
分別對應于onForeground回調和onBackground回調。
onForeground回調,在UIAbility的UI頁面可見之前,即UIAbility切換至前臺時觸發。可以在onForeground回調中申請系統需要的資源,或者重新申請在onBackground中釋放的資源。
onBackground回調,在UIAbility的UI頁面完全不可見之后,即UIAbility切換至后臺時候觸發。可以在onBackground回調中釋放UI頁面不可見時無用的資源,或者在此回調中執行較為耗時的操作,例如狀態保存等。
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';export default class EntryAbility extends UIAbility {...onForeground() {// 申請系統需要的資源,或者重新申請在onBackground中釋放的資源...}onBackground() {// 釋放UI頁面不可見時無用的資源,或者在此回調中執行較為耗時的操作// 例如狀態保存等...}
}
例如用戶打開地圖應用查看當前地理位置的時候,假設地圖應用已獲得用戶的定位權限授權。在UI頁面顯示之前,可以在onForeground回調中打開定位功能,從而獲取到當前的位置信息。
當地圖應用切換到后臺狀態,可以在onBackground回調中停止定位功能,以節省系統的資源消耗。
- 前面我們了解了UIAbility實例創建時的onWindowStageCreate回調的相關作用。
對應于onWindowStageCreate回調。在UIAbility實例銷毀之前,則會先進入onWindowStageDestroy回調,我們可以在該回調中釋放UI頁面資源。
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';export default class EntryAbility extends UIAbility {...onWindowStageDestroy() {// 釋放UI頁面資源...}
}
例如在onWindowStageCreate中設置的獲焦/失焦等WindowStage訂閱事件。
- Destroy狀態,在UIAbility銷毀時觸發。可以在onDestroy回調中進行系統資源的釋放、數據的保存等操作。
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';export default class EntryAbility extends UIAbility {...onDestroy() {// 系統資源的釋放、數據的保存等...}
}
例如用戶使用應用的程序退出功能,會調用UIAbilityContext的terminalSelf()方法,從而完成UIAbility銷毀。或者用戶使用最近任務列表關閉該UIAbility實例時,也會完成UIAbility的銷毀。
UIAbility的啟動模式
通常我們打開某一個應用,并瀏覽訪問相關內容后,回到桌面,當再次打開該應用,顯示的仍然是用戶當前訪問的界面。
對于應用的分屏操作,用戶希望使用兩個不同應用(例如備忘錄應用和圖庫應用)之間進行分屏,也希望能使用同一個應用(例如備忘錄應用自身)進行分屏。
對于文檔應用,用戶從文檔應用中打開一個文檔內容,回到文檔應用,繼續打開同一個文檔,希望打開的還是同一個文檔內容。
基于以上場景的考慮,UIAbility當前支持singleton(單實例模式)、multiton(多實例模式)和specified(指定實例模式)3種啟動模式。
3種啟動模式的詳細說明如下:
- singleton(單實例模式)
打開某一個應用,并瀏覽訪問相關內容后,回到桌面,當再次打開該應用,顯示的仍然是用戶當前訪問的界面。
這種情況下可以將UIAbility配置為singleton(單實例模式)。每次調用startAbility()方法時,如果應用進程中該類型的UIAbility實例已經存在,則復用系統中的UIAbility實例,系統中只存在唯一一個該UIAbility實例。
即在最近任務列表中只存在一個該類型的UIAbility實例。
singleton啟動模式,也是默認情況下的啟動模式。singleton啟動模式,每次調用startAbility()啟動UIAbility時,如果應用進程中該類型的UIAbility實例已經存在,則復用系統中的UIAbility實例,系統中只存在唯一一個該UIAbility實例。
singleton啟動模式的開發使用,在module.json5文件中的“launchType”字段配置為“singleton”即可。
{"module": {..."abilities": [{"launchType": "singleton",...}]}
}
- multiton(多實例模式)
用戶在使用分屏功能時,希望使用兩個不同應用(例如備忘錄應用和圖庫應用)之間進行分屏,也希望能使用同一個應用(例如備忘錄應用自身)進行分屏。
這種情況下可以將UIAbility配置為multiton(多實例模式)。每次調用startAbility()方法時,都會在應用進程中創建一個該類型的UIAbility實例。
即在最近任務列表中可以看到有多個該類型的UIAbility實例。
multiton啟動模式,每次調用startAbility()方法時,都會在應用進程中創建一個該類型的UIAbility實例。
multiton啟動模式的開發使用,在module.json5文件中的“launchType”字段配置為“multiton”即可。
{"module": {..."abilities": [{"launchType": "multiton",...}]}
}
- specified(指定實例模式)
用戶打開文檔應用,從文檔應用中打開一個文檔內容,回到文檔應用,繼續打開同一個文檔,希望打開的還是同一個文檔內容;以及在文檔應用中新建一個新的文檔,每次新建文檔,希望打開的都是一個新的空白文檔內容。
這種情況下可以將UIAbility配置為specified(指定實例模式)。在UIAbility實例新創建之前,允許開發者為該實例創建一個字符串Key,新創建的UIAbility實例綁定Key之后,后續每次調用startAbility方法時,都會詢問應用使用哪個Key對應的UIAbility實例來響應startAbility請求。如果匹配有該UIAbility實例的Key,則直接拉起與之綁定的UIAbility實例,否則創建一個新的UIAbility實例。運行時由UIAbility內部業務決定是否創建多實例。
specified啟動模式,根據業務需要是否創建一個新的UIAbility實例。在UIAbility實例創建之前,會先進入AbilityStage的onAcceptWant回調,在onAcceptWant回調中為每一個UIAbility實例創建一個Key,后續每次調用startAbility()方法創建該類型的UIAbility實例都會詢問使用哪個Key對應的UIAbility實例來響應startAbility()請求。
specified啟動模式的開發使用的步驟如下所示。
- 在module.json5文件中的“launchType”字段配置為“specified”。
{"module": {..."abilities": [{"launchType": "specified",...}]}
}
- 在調用startAbility()方法的want參數中,增加一個自定義參數來區別UIAbility實例,例如增加一個“instanceKey”自定義參數。
// 在啟動指定實例模式的UIAbility時,給每一個UIAbility實例配置一個獨立的Key標識
function getInstance() {...
}
let context:common.UIAbilityContext = ...; // context為調用方UIAbility的UIAbilityContext
let want: Want = {deviceId: '', // deviceId為空表示本設備bundleName: 'com.example.myapplication',abilityName: 'SpecifiedAbility',moduleName: 'specified', // moduleName非必選parameters: { // 自定義信息instanceKey: getInstance(),},
}
context.startAbility(want).then(() => {...
}).catch((err: BusinessError) => {...
})
- 在被拉起方UIAbility對應的AbilityStage的onAcceptWant生命周期回調中,解析傳入的want參數,獲取“instanceKey”自定義參數。根據業務需要返回一個該UIAbility實例的字符串Key標識。如果之前啟動過此Key標識的UIAbility,則會將之前的UIAbility拉回前臺并獲焦,而不創建新的實例,否則創建新的實例并啟動。
onAcceptWant(want: want): string {// 在被啟動方的AbilityStage中,針對啟動模式為specified的UIAbility返回一個UIAbility實例對應的一個Key值// 當前示例指的是device Module的EntryAbilityif (want.abilityName === 'MainAbility') {return `DeviceModule_MainAbilityInstance_${want.parameters.instanceKey}`;}return '';
}
例如在文檔應用中,可以對不同的文檔實例內容綁定不同的Key值。當每次新建文檔的時候,可以傳入不同的新Key值(如可以將文件的路徑作為一個Key標識),此時AbilityStage中啟動UIAbility時都會創建一個新的UIAbility實例;當新建的文檔保存之后,回到桌面,或者新打開一個已保存的文檔,回到桌面,此時再次打開該已保存的文檔,此時AbilityStage中再次啟動該UIAbility時,打開的仍然是之前原來已保存的文檔界面。
總結
- UIAbility是一種包含用戶界面的應用組件,用于和用戶進行交互的相關邏輯,他和Android的activity類似,都是屬于系統調度的單元。
- UIAbility之間是可以相互跳轉和傳遞值的,這些都是通過路由來實現的
- UIAbility和和Android的activity一樣也是有生命周期的,了解UIAbility的創建,前后臺切換,銷毀的生命周期狀態,有助于靈活掌握各個生命周期在特定場景可以干什么事。
- 了解UIAbility的創建以及其對應頁面的如何創建。