目的
建立服務,支援兩種方式啟動:startAbility、connectAbility,本次隻實作啟動本地服務,後續再介紹啟動遠端服務的操作。
前置條件
環境
裝置:DAYU200 開發闆
系統:OpenHarmony 3.1 release
IDE:DevEco Studio 3.0 Beta3
項目實踐
以下内容屬于個人實踐總結,在不同的系統版本、不同的SDK版本存在着一些差異,如果有描述錯誤的地方請留意修改,謝謝。
建立一個項目
建立項目
項目初始狀态的目錄
建立service
建立service後,IDE會自動建立service的回調函數
這裡簡單的說明下服務的生命周期功能介紹
接口名 | 描述 |
---|---|
onStart | 該方法在建立Service的時候調用,用于Service的初始化。在Service的整個生命周期隻會調用一次,調用時傳入的Want應為空。 |
onCommand | 在Service建立完成之後調用,該方法在用戶端每次啟動該Service時都會調用,開發者可以在該方法中做一些調用統計、初始化類的操作。 |
onConnect | 在Ability和Service連接配接時調用。 |
onDisconnect | 在Ability與綁定的Service斷開連接配接時調用。 |
onStop | 在Service銷毀時調用。Service應通過實作此方法來清理任何資源,如關閉線程、注冊的偵聽器等。 |
從這裡可以看出,IDE為我們自動建立的service生命周期函數中,還有兩個重要的函數onConnect(want)、onDisconnect(want) 沒有實作,你可以通過手動方式添加函數。這裡順便提一下,如果是使用 startAbility()的方式啟動本地服務,則可以不實作onConnect(want)函數。
注冊服務
Service也需要在應用配置檔案config.json中進行注冊,注冊類型type需要設定為service。
{
"module": {
"abilities": [
{
"visible": true,
"srcPath": "StartServiceAbility",
"name": ".StartServiceAbility",
"srcLanguage": "ets",
"icon": "$media:icon",
"description": "$string:StartServiceAbility_desc",
"type": "service"
},
]
...
}
...
}
服務開發
基于Service模闆的Ability(以下簡稱“Service”)主要用于背景運作任務(如執行音樂播放、檔案下載下傳等),但不提供使用者互動界面。Service可由其他應用或Ability啟動,即使使用者切換到其他應用,Service仍将在背景繼續運作。
啟動本地服務 startAbility()
官方文檔
demo視訊
啟動本地服務業務流程
1、點選“綁定事件”,用于将服務端處理的結果通過公共事件通知頁面顯示;
2、點選“開啟本地服務”,将本地服務啟動;
3、服務啟動後,執行累加操作,初始值為1每間隔1秒累加一次,每次增加1;
4、通過公共事件,将服務端處理的累加結果通知頁面顯示;
5、點選“斷開本地服務”,通過公共事件通知服務端停止,累加計算也停止;
6、點選“解綁事件”,取消公共事件的觀察者。
相關代碼
index.ets
說明:頁面,主要用于操作服務功能使用。
import { TitleBar } from '../component/TitleBar'
import commonEvent from '@ohos.commonEvent'
import { StartServiceModel } from '../model/StartServiceModel'
import { ConnectServiceModel } from '../model/ConnectServiceModel'
import { OperateView } from '../component/OperateView'
import prompt from '@ohos.prompt';
import rpc from '@ohos.rpc';
const TAG = "[StartService.index]";
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
private btnBindCommonEvent: Array<String> = ['綁定事件', '解綁事件'];
private btnFResources: Array<String> = ['開啟本地服務', '斷開本地服務'];
private mSubscriber = null;
@State calculateResult: string = '';
private startServiceModel = new StartServiceModel();
private connectServiceModel = new ConnectServiceModel();
@State startServiceTitle: string = 'Start Service Test';
createSubscriber() {
if (this.mSubscriber != null) {
return;
}
// Subscriber information
var subscribeInfo = {
events: ["calculateResult", // 計算結果
],
}
// Create subscriber callback
commonEvent.createSubscriber(subscribeInfo, (err, subscriber) => {
if (err.code) {
console.error(`${TAG} [CommonEvent]CreateSubscriberCallBack err = ${JSON.stringify(err)}`)
prompt.showToast({
message: '綁定失敗',
duration: 2000
})
} else {
console.log(`${TAG} [CommonEvent]CreateSubscriber`)
this.mSubscriber = subscriber
this.subscribe();
}
})
}
subscribe() {
// Subscribe
if (this.mSubscriber != null) {
commonEvent.subscribe(this.mSubscriber, (err, data) => {
if (err.code) {
console.error(`[CommonEvent]SubscribeCallBack err= = ${JSON.stringify(err)}`)
} else {
console.info(`[CommonEvent]SubscribeCallBack data= = ${JSON.stringify(data)}`)
let code: number = data.code;
switch (code) {
case (101):
{
// 計算傳回
this.calculateResult = data.data;
break;
}
default:
{
break;
}
}
}
})
console.log(`${TAG} [CommonEvent] Subscribe succeed`);
prompt.showToast({
message: '綁定成功',
duration: 2000
})
} else {
console.error(`${TAG} [CommonEvent] Need create subscriber`)
prompt.showToast({
message: '請先綁定事件',
duration: 2000
});
}
}
unsubscribe() {
// Unsubscribe CommonEvent
if (this.mSubscriber != null) {
commonEvent.unsubscribe(this.mSubscriber, (err) => {
if (err.code) {
console.error(`${TAG}[CommonEvent]UnsubscribeCallBack err= = ${JSON.stringify(err)}`);
prompt.showToast({
message: '事件解綁失敗',
duration: 2000
});
} else {
console.info(`${TAG} [CommonEvent]Unsubscribe success`)
prompt.showToast({
message: '事件解綁成功',
duration: 2000
});
this.mSubscriber = null
}
})
}
}
build() {
Column() {
TitleBar({ title: $startServiceTitle })
Text(this.calculateResult)
.fontSize(22)
.width('94%')
.margin({ top: 10, bottom: 10 })
.constraintSize({ minHeight: 50 })
.padding(10)
.border({ width: 1, color: Color.Gray, radius: 20 })
Row() {
ForEach(this.btnBindCommonEvent, (item, index) => {
Button() {
Text(item)
.fontColor(Color.White)
.fontSize(20)
}
.type(ButtonType.Capsule)
.backgroundColor('#0D9FFB')
.width('40%')
.height(60)
.margin(10)
.onClick(() => {
console.log(`${TAG} button clicked,index=${index}`);
switch (index) {
case 0:
{
// 綁定事件監聽器
this.createSubscriber();
break;
}
case 1:
{
// 解綁事件監聽器
this.unsubscribe();
break;
}
default:
{
break;
}
}
})
}, item => JSON.stringify(item))
}
ForEach(this.btnFResources, (item, index) => {
Button() {
Text(item)
.fontColor(Color.White)
.fontSize(20)
}
.type(ButtonType.Capsule)
.backgroundColor('#0D9FFB')
.width('90%')
.height(60)
.margin(10)
.onClick(() => {
console.log(`${TAG} button clicked,index=${index}`);
switch (index) {
case 0:
{
// 點選啟動服務
this.startServiceModel.startService("");
prompt.showToast({
message: '服務已啟動',
duration: 2000
})
break;
}
case 1:
{
// 點選斷開服務
this.startServiceModel.stopService();
prompt.showToast({
message: '服務已關閉',
duration: 2000
});
break;
}
default:
{
break;
}
}
})
}, item => JSON.stringify(item))
}
.width('100%')
.height('100%')
}
}
StartServiceAbility
service.ts
說明:服務,啟動後在背景進行業務處理
import commonEvent from '@ohos.commonEvent'
import featureAbility from '@ohos.ability.featureAbility';
const TAG: string = '[StartServiceAbility]'
class ServiceSubscriber {
subscriber = null;
intervalID = -1;
isCalculating = false;
constructor() {
}
/**
* 開始累加
* @param data
*/
calculate(data: number) {
if (this.isCalculating) {
return;
}
this.isCalculating = true;
let self = this;
this.intervalID = setInterval(() => {
data++;
self.sendResult(data.toString());
}, 1000);
}
/**
* 停止累加
*/
stopCalculate() {
console.log(`${TAG} ServiceAbility stop Calculate`);
this.isCalculating = false;
if (this.intervalID != -1) {
clearInterval(this.intervalID);
this.intervalID = -1;
}
}
sendResult(result) {
var options = {
code: 101, // 傳回消息結果
data: result
}
commonEvent.publish("calculateResult", options, (err) => {
if (err.code) {
console.error(`${TAG} [CommonEvent]PublishCallBack err = ${JSON.stringify(err)}`)
} else {
console.log(`${TAG} [CommonEvent] PublishCall success`)
}
})
}
createSubscriber() {
// Subscriber information
var subscribeInfo = {
events: ["closeService"],
}
// Create subscriber callback
commonEvent.createSubscriber(subscribeInfo, (err, subscriber) => {
if (err.code) {
console.error(`${TAG} [CommonEvent]CreateSubscriberCallBack err = ${JSON.stringify(err)}`)
} else {
console.log(`${TAG} [CommonEvent]CreateSubscriber`)
this.subscriber = subscriber
this.subscribe();
}
})
}
subscribe() {
// Subscribe
if (this.subscriber != null) {
commonEvent.subscribe(this.subscriber, (err, data) => {
if (err.code) {
console.error(`[CommonEvent]SubscribeCallBack err= = ${JSON.stringify(err)}`)
} else {
console.info(`[CommonEvent]SubscribeCallBack data= = ${JSON.stringify(data)}`)
// 關閉服務
featureAbility.terminateSelf(() => {
console.log(`${TAG} [CommonEvent] featureAbility terminateSelf`)
});
}
})
console.log(`${TAG} [CommonEvent] Subscribe succeed`)
} else {
console.error(`${TAG} [CommonEvent] Need create subscriber`)
}
}
unsubscribe() {
// Unsubscribe CommonEvent
if (this.subscriber != null) {
commonEvent.unsubscribe(this.subscriber, (err) => {
if (err.code) {
console.error(`${TAG}[CommonEvent]UnsubscribeCallBack err= = ${JSON.stringify(err)}`)
} else {
console.info(`${TAG} [CommonEvent]Unsubscribe success`)
this.subscriber = null
}
})
}
}
}
let serviceSubscriber: ServiceSubscriber = null;
export default {
onStart() {
console.log(`${TAG} ServiceAbility onStart`);
serviceSubscriber = new ServiceSubscriber();
serviceSubscriber.createSubscriber();
},
onConnect(want) {
console.log(`${TAG} ServiceAbility OnConnect`);
return null;
},
onCommand(want, startId) {
console.log(`${TAG} ServiceAbility onCommand`);
// 開始累加數值
if (serviceSubscriber != null) {
serviceSubscriber.calculate(0);
} else {
console.error(`${TAG} ServiceAbility onCommand serviceSubscriber is null`);
}
},
onDisconnect(want) {
console.log(`${TAG} ServiceAbility OnDisConnect`);
},
onStop() {
console.log(`${TAG} ServiceAbility onStop`);
// 關閉監聽器
if (serviceSubscriber != null) {
serviceSubscriber.unsubscribe();
serviceSubscriber.stopCalculate();
serviceSubscriber = null;
}
},
onReconnect(want) {
console.log(`${TAG} ServiceAbility onReconnect`);
},
};
StartServiceModel
說明:啟動本地服務處理類,用于控制啟動、停止服務,關閉服務使用到了通過公共事件通知服務調用featureAbility.terminateSelf()實作停止服務。
import featureAbility from '@ohos.ability.featureAbility';
import commonEvent from '@ohos.commonEvent'
let TAG: string = '[StartService.StartServiceModel]'
/**
* 用戶端啟動服務
*/
export class StartServiceModel{
/**
* 啟動服務
*/
startService(deviceId) {
console.log(`${TAG} startService begin`);
featureAbility.startAbility({
want: {
deviceId: deviceId,
bundleName: 'com.nlas.etsservice',
abilityName: 'com.example.entry.StartServiceAbility'
}
});
}
/**
* 斷開服務
*/
stopService() {
var options = {
code: 103 // 傳回消息結果
}
commonEvent.publish("closeService", options, (err) => {
if (err.code) {
console.error(`${TAG} [CommonEvent]PublishCallBack stopService err = ${JSON.stringify(err)}`)
} else {
console.log(`${TAG} [CommonEvent] PublishCall stopService success`)
}
})
}
}
至此,startAbility()的方式啟動服務操作就完成了。
接下去我們來說說通過 connectAbility() 連接配接服務
連接配接本地服務 connectAbility
官方指導
demo視訊
啟動本地服務業務流程
1、點選 “連接配接本地服務”,執行服務連接配接,根據連接配接的結果進行toast提示,連接配接成功則提示:"服務已連接配接";
2、服務連接配接成功狀态下,在輸入框中輸入需要排序的字元串;
3、點選“排序”,服務端處理字元排序,并把排序的結果通過服務代理對象傳回給用戶端,用戶端顯示排序結果;
4、點選“斷開本地服務”,如果服務斷開成功,則提示:“服務已斷開”;
5、服務未連接配接或者已斷開狀态下,點選排序,提示:“請連接配接服務”。
相關代碼
index.ets
說明:頁面,用于操作服務和顯示服務端處理的結果。
import { TitleBar } from '../component/TitleBar'
import { ConnectServiceModel } from '../model/ConnectServiceModel'
import { OperateView } from '../component/OperateView'
import prompt from '@ohos.prompt';
import rpc from '@ohos.rpc';
const TAG = "[StartService.index]";
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
private btnSortResources: Array<String> = ['連接配接本地服務', '斷開本地服務', '排序'];
private mSubscriber = null;
private connectServiceModel = new ConnectServiceModel();
@State beforeSortString: string = '';
@State afterSortString: string = '';
@State sourceString: string = '';
@State connectServiceTitle: string = 'Connect Service Test';
async sortString() {
console.log(`${TAG} sortString begin`)
let mRemote = this.connectServiceModel.getRemoteObject()
if (mRemote === null) {
prompt.showToast({
message: '請連接配接服務'
})
return;
}
if (this.beforeSortString.length === 0) {
prompt.showToast({
message: 'please input a string'
})
return;
}
let option: rpc.MessageOption = new rpc.MessageOption()
let data: rpc.MessageParcel = rpc.MessageParcel.create()
let reply: rpc.MessageParcel = rpc.MessageParcel.create()
data.writeString(this.beforeSortString)
await mRemote.sendRequest(1, data, reply, option)
let msg = reply.readString()
this.afterSortString = msg
}
build() {
Column() {
TitleBar({ title: $connectServiceTitle })
OperateView({ before: $beforeSortString, after: $afterSortString, source: $sourceString })
ForEach(this.btnSortResources, (item, index) => {
Button() {
Text(item)
.fontColor(Color.White)
.fontSize(20)
}
.type(ButtonType.Capsule)
.backgroundColor('#0D9FFB')
.width('94%')
.height(50)
.margin(10)
.onClick(() => {
console.info(`${TAG} button clicked,index=${index}`)
switch (index) {
case 0:
this.connectServiceModel.connectService("");
break;
case 1:
this.connectServiceModel.disconnectService();
break;
case 2:
this.sortString();
break;
default:
break;
}
})
}, item => JSON.stringify(item))
}
.width('100%')
.height('100%')
}
}
ConnectServiceAbility
service.ts
說明:服務,服務連接配接後傳回服務代理對象,用戶端通過服務代理對象向服務端發起請求(sendRequest()),服務端根據用戶端的請求進行處理,将結果通服務代理對象寫給用戶端。
import rpc from "@ohos.rpc"
const TAG: string = '[StartServiceAbility]'
class ServiceSubscriber extends rpc.RemoteObject{
constructor(des: any) {
if (typeof des === 'string') {
super(des)
} else {
return
}
}
onRemoteRequest(code: number, data: any, reply: any, option: any) {
console.log(`${TAG}onRemoteRequest called`)
if (code === 1) {
let string = data.readString();
console.log(`${TAG} string=${string}`)
let result = Array.from(string).sort().join('')
console.log(`${TAG} result=${result}`)
reply.writeString(result);
} else {
console.log(`${TAG} unknown request code`)
}
return true;
}
}
export default {
onStart() {
console.log(`${TAG} ServiceAbility onStart`);
},
onStop() {
console.log(`${TAG} ServiceAbility onStop`);
},
onCommand(want, startId) {
console.log(`${TAG} ServiceAbility onCommand`);
},
onConnect(want) {
console.log(`${TAG} ServiceAbility OnConnect`);
return new ServiceSubscriber("sort service");
},
onDisconnect(want) {
console.log(`${TAG} ServiceAbility OnDisConnect`);
}
};
ConnectServiceModel.ts
說明:用于連接配接服務、斷開服務的操作處理類。
import prompt from '@ohos.prompt'
import featureAbility from '@ohos.ability.featureAbility'
import rpc from "@ohos.rpc"
let mRemote: rpc.IRemoteObject = null
let connection: number = -1
let TAG: string = '[ConnectServiceModel.ServiceModel]'
export class ConnectServiceModel {
onConnectCallback(element, remote) {
console.log(`${TAG}onConnectLocalService onConnectDone element:${element}`)
console.log(`${TAG}onConnectLocalService onConnectDone remote:${remote}`)
mRemote = remote
if (mRemote === null) {
prompt.showToast({
message: '服務未連接配接'
})
return
}
prompt.showToast({
message: '服務已連接配接',
})
}
onDisconnectCallback(element) {
console.log(`${TAG}onConnectLocalService onDisconnectDone element:${element}`)
}
onFailedCallback(code) {
console.log(`${TAG}onConnectLocalService onFailed errCode:${code}`)
prompt.showToast({
message: `服務連接配接失敗 errCode:${code}`
})
}
connectService(deviceId) {
console.log(`${TAG} onConnectService begin`)
connection = featureAbility.connectAbility(
{
deviceId: deviceId,
bundleName: 'com.nlas.etsservice',
abilityName: 'com.example.entry.ConnectServiceAbility'
},
{
onConnect: this.onConnectCallback,
onDisconnect: this.onDisconnectCallback,
onFailed: this.onFailedCallback,
},
)
}
disconnectService() {
console.log(`${TAG} onDisconnectService begin`)
mRemote = null
if (connection === -1) {
prompt.showToast({
message: '服務未連接配接'
})
return
}
featureAbility.disconnectAbility(connection)
connection = -1
prompt.showToast({
message: '服務已斷開'
})
}
getRemoteObject() {
return mRemote
}
}
至此,通過connectAbility()連接配接本地服務的操作就完成了,主要注意兩點:
1、連接配接服務時需要傳回服務代理對象;
2、用戶端向服務端發起請求時,服務端通過代理對象讀取用戶端資料的順序必須與用戶端寫入的資料順序一緻,當然,服務端向用戶端傳回資料時代理對象寫入的順序與用戶端讀取的順序必須相同。
錯誤示範
用戶端代碼
async sortString() {
console.log(`${TAG} sortString begin`)
let mRemote = this.connectServiceModel.getRemoteObject()
if (mRemote === null) {
prompt.showToast({
message: '請連接配接服務'
})
return;
}
if (this.beforeSortString.length === 0) {
prompt.showToast({
message: 'please input a string'
})
return;
}
let option: rpc.MessageOption = new rpc.MessageOption()
let data: rpc.MessageParcel = rpc.MessageParcel.create()
let reply: rpc.MessageParcel = rpc.MessageParcel.create()
data.writeInt(11);// 用戶端寫入1
data.writeString("aaaaaaaaa");// 用戶端寫入2
data.writeString(this.beforeSortString);// 用戶端寫入3
data.writeString("eeeeeeeee");// 用戶端寫入4
data.writeInt(22);// 用戶端寫入5
await mRemote.sendRequest(1, data, reply, option);
let intS = reply.readInt();// 服務端傳回資料 讀取4
let intE = reply.readInt();//服務端傳回資料 讀取 5
let startStr = reply.readString();// 服務端傳回資料 讀取1
let msg = reply.readString();// 服務端傳回資料 讀取2
let startEnd = reply.readString();// 服務端傳回資料 讀取3
console.log(`[StartServiceAbility] callback intS=${intS} startStr=${startStr} startEnd=${startEnd} msg=${msg} intE=${intE}`)
this.afterSortString = intS.toString() + startStr + msg + startEnd + intE.toString();
}
服務端代碼
onRemoteRequest(code: number, data: any, reply: any, option: any) {
console.log(`${TAG}onRemoteRequest called`)
if (code === 1) {
let intS = data.readInt();// 讀取用戶端請求資料1
let intE = data.readInt();// 讀取用戶端請求資料5
let startStr = data.readString();// 讀取用戶端請求資料2
let string = data.readString();// 讀取用戶端請求資料3
let endStr = data.readString();// 讀取用戶端請求資料4
this.initialPublish(string);
console.log(`${TAG} intS=${intS} intE=${intE}`);
console.log(`${TAG} startStr=${startStr} string=${string} endStr=${endStr}`);
let result = Array.from(string).sort().join('');
console.log(`${TAG} result=${result}`);
reply.writeString(startStr);// 傳回用戶端 寫入資料1
reply.writeString(result);// 傳回用戶端 寫入資料2
reply.writeString(endStr);// 傳回用戶端 寫入資料3
reply.writeInt(intS);// 傳回用戶端 寫入資料4
reply.writeInt(intE);// 傳回用戶端 寫入資料5
} else {
console.log(`${TAG} unknown request code`)
}
return true;
}
傳回結果:
08-05 17:39:50.629 11002-11013/com.nlas.etsservice D 03b00/JSApp: app Log: [StartServiceAbility] callback intS=0 startStr= startEnd= msg= intE=0
正确示範
用戶端代碼
async sortString() {
console.log(`${TAG} sortString begin`)
let mRemote = this.connectServiceModel.getRemoteObject()
if (mRemote === null) {
prompt.showToast({
message: '請連接配接服務'
})
return;
}
if (this.beforeSortString.length === 0) {
prompt.showToast({
message: 'please input a string'
})
return;
}
let option: rpc.MessageOption = new rpc.MessageOption()
let data: rpc.MessageParcel = rpc.MessageParcel.create()
let reply: rpc.MessageParcel = rpc.MessageParcel.create()
data.writeInt(11);// 用戶端寫入1
data.writeString("aaaaaaaaa");// 用戶端寫入2
data.writeString(this.beforeSortString);// 用戶端寫入3
data.writeString("eeeeeeeee");// 用戶端寫入4
data.writeInt(22);// 用戶端寫入5
await mRemote.sendRequest(1, data, reply, option);
let startStr = reply.readString();// 服務端傳回資料 讀取1
let msg = reply.readString();// 服務端傳回資料 讀取2
let startEnd = reply.readString();// 服務端傳回資料 讀取3
let intS = reply.readInt();// 服務端傳回資料 讀取4
let intE = reply.readInt();//服務端傳回資料 讀取 5
console.log(`[StartServiceAbility] callback intS=${intS} startStr=${startStr} startEnd=${startEnd} msg=${msg} intE=${intE}`)
this.afterSortString = intS.toString() + startStr + msg + startEnd + intE.toString();
}
服務端代碼
onRemoteRequest(code: number, data: any, reply: any, option: any) {
console.log(`${TAG}onRemoteRequest called`)
if (code === 1) {
let intS = data.readInt();// 讀取用戶端請求資料1
let startStr = data.readString();// 讀取用戶端請求資料2
let string = data.readString();// 讀取用戶端請求資料3
let endStr = data.readString();// 讀取用戶端請求資料4
let intE = data.readInt();// 讀取用戶端請求資料5
this.initialPublish(string);
console.log(`${TAG} intS=${intS} intE=${intE}`);
console.log(`${TAG} startStr=${startStr} string=${string} endStr=${endStr}`);
let result = Array.from(string).sort().join('');
console.log(`${TAG} result=${result}`);
reply.writeString(startStr);// 傳回用戶端 寫入資料1
reply.writeString(result);// 傳回用戶端 寫入資料2
reply.writeString(endStr);// 傳回用戶端 寫入資料3
reply.writeInt(intS);// 傳回用戶端 寫入資料4
reply.writeInt(intE);// 傳回用戶端 寫入資料5
} else {
console.log(`${TAG} unknown request code`)
}
return true;
}
傳回結果
08-05 17:26:43.706 10605-10615/com.nlas.etsservice D 03b00/JSApp: app Log: [StartServiceAbility] callback intS=11 startStr=aaaaaaaaa startEnd=eeeeeeeee msg=gggiiiuuuvv intE=22
感謝
如果您能看到最後,還希望您能動動手指點個贊,一個人能走多遠關鍵在于與誰同行,我用跨越山海的一路相伴,希望得到您的點贊。