提交 9a7a2fa9 编写于 作者: H HuangXW

Call调用开发文档整改以及新增Call前台的说明

Signed-off-by: NHuangXW <huangxinwei4@huawei.com>
上级 d429c717
# Call调用开发指导 # Call调用开发指导
## 场景介绍 ## 场景介绍
Ability Call调用是Ability能力的扩展,它为Ability提供一种能够被外部调用的能力,使Ability既能被拉起到前台展示UI,也支持Ability在后台被创建并运行。应用开发者可通过Call调用,使用IPC通信实现不同Ability之间的数据共享。Call调用的场景主要包括: Call调用是Ability能力的扩展,它为Ability提供一种能够被外部调用并与外部进行通信的能力。Call调用支持前台与后台两种启动方式,使Ability既能被拉起到前台展示UI,也可以在后台被创建并运行。Call调用在调用方与被调用方间建立了IPC通信,因此应用开发者可通过Call调用实现不同Ability之间的数据共享。
- 创建Callee被调用端。
- 访问Callee被调用端。
本文中的Caller和Callee分别表示调用者和被调用者,IPC表示进程间通信,Call调用流程示意图如下。 Call调用的核心接口是startAbilityByCall方法,与startAbility接口的不同之处在于:
- startAbilityByCall支持前台与后台两种启动方式,而startAbility仅支持前台启动。
- 调用方可使用startAbilityByCall所返回的Caller对象与被调用方进行通信,而startAbilty不具备通信能力。
Call调用的使用场景主要包括:
- 需要与被启动的Ability进行通信
- 希望被启动的Ability在后台运行
**表1** Call调用相关名词解释
|名词|描述|
|:------|:------|
|CallerAbility|指代进行Call调用的Ability(调用方)|
|CalleeAbility|指代被Call调用的Ability(被调用方)|
|Caller |实际对象,由startAbilityByCall接口所返回,CallerAbility可使用Caller与CalleeAbility进行通信,具体接口见表2|
|Callee |实际对象,被Ability对象所持有,可与Caller进行通信|
|IPC |指代进程间通信|
Call调用流程示意图如下:
- CallerAbility调用startAbilityByCall接口获取Caller,并使用Caller对象的call方法向CalleeAbility发送数据
- CalleeAbility持有一个Callee对象,通过Callee的on方法注册回调函数,当接收到Caller发送的数据时将会调用对应的回调函数
![stage-call](figures/stage-call.png) ![stage-call](figures/stage-call.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> Callee被调用端所在的Ability,启动模式需要为单实例。 > CalleeAbility的启动模式需要为单实例。
> 当前仅支持系统应用及ServiceExtensionAbility使用Call访问Callee > 当前仅支持系统应用使用Call调用
## 接口说明 ## 接口说明
Caller及Callee功能如下:具体的API详见[接口文档](../reference/apis/js-apis-application-ability.md#caller) Caller及Callee功能如下:具体的API详见[接口文档](../reference/apis/js-apis-application-ability.md#caller)
**表1** Call API接口功能介绍 **表2** Call API接口功能介绍
|接口名|描述| |接口名|描述|
|:------|:------| |:------|:------|
|startAbilityByCall(want: Want): Promise\<Caller>|获取指定通用组件的Caller通信接口,拉起指定通用组件并将其切换到后台。| |startAbilityByCall(want: Want): Promise\<Caller>|启动指定Ability并获取其Caller通信接口,默认为后台启动,通过配置want可实现前台启动,详见[接口文档](../reference/apis/js-apis-ability-context.md#abilitycontextstartabilitybycall)。AbilityContext与ServiceExtensionContext均支持该接口。|
|on(method: string, callback: CalleeCallBack): void|通用组件Callee注册method对应的callback方法。| |on(method: string, callback: CalleeCallBack): void|通用组件Callee注册method对应的callback方法。|
|off(method: string): void|通用组件Callee注册method的callback方法。| |off(method: string): void|通用组件Callee注册method的callback方法。|
|call(method: string, data: rpc.Sequenceable): Promise\<void>|向通用组件Callee发送约定序列化数据。| |call(method: string, data: rpc.Sequenceable): Promise\<void>|向通用组件Callee发送约定序列化数据。|
|callWithResult(method: string, data: rpc.Sequenceable): Promise\<rpc.MessageParcel>|向通用组件Callee发送约定序列化数据, 并将返回的约定序列化数据带回。| |callWithResult(method: string, data: rpc.Sequenceable): Promise\<rpc.MessageParcel>|向通用组件Callee发送约定序列化数据, 并将Callee返回的约定序列化数据带回。|
|release(): void|释放通用组件的Caller通信接口。| |release(): void|释放通用组件的Caller通信接口。|
|onRelease(callback: OnReleaseCallBack): void|注册通用组件通信断开监听通知。| |onRelease(callback: OnReleaseCallBack): void|注册通用组件通信断开监听通知。|
## 开发步骤 ## 开发步骤
Call调用的开发步骤:
- 创建Callee被调用端。
- 访问Callee被调用端。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 开发步骤章节中的示例代码片段是开发过程的步骤化展示,部分代码可能无法单独运行,完整工程代码请参考[相关实例](#相关实例)。 > 开发步骤章节中的示例代码片段是开发过程的步骤化展示,部分代码可能无法单独运行,完整工程代码请参考[相关实例](#相关实例)。
### 创建Callee被调用端 ### 创建Callee被调用端
Callee被调用端,需要实现指定方法的数据接收回调函数、数据的序列化及反序列化方法。在需要接收数据期间,通过on接口注册监听,无需接收数据时通过off接口解除监听。 Callee被调用端,需要实现指定方法的数据接收回调函数、数据的序列化及反序列化方法。在需要接收数据期间,通过on接口注册监听,无需接收数据时通过off接口解除监听。
1. 配置Ability的启动模式 **1. 配置Ability的启动模式**
配置module.json5,将Callee被调用端所在的Ability配置为单实例"singleton"。 配置module.json5,将CalleeAbility配置为单实例"singleton"。
|Json字段|字段说明| |Json字段|字段说明|
|:------|:------| |:------|:------|
...@@ -51,11 +70,11 @@ Ability配置标签示例如下: ...@@ -51,11 +70,11 @@ Ability配置标签示例如下:
"visible": true "visible": true
}] }]
``` ```
2. 导入Ability模块。 **2. 导入Ability模块**
``` ```ts
import Ability from '@ohos.application.Ability' import Ability from '@ohos.application.Ability'
``` ```
3. 定义约定的序列化数据。 **3. 定义约定的序列化数据**
调用端及被调用端发送接收的数据格式需协商一致,如下示例约定数据由number和string组成。具体示例代码如下: 调用端及被调用端发送接收的数据格式需协商一致,如下示例约定数据由number和string组成。具体示例代码如下:
```ts ```ts
...@@ -81,7 +100,7 @@ export default class MySequenceable { ...@@ -81,7 +100,7 @@ export default class MySequenceable {
} }
} }
``` ```
4. 实现Callee.on监听及Callee.off解除监听。 **4. 实现Callee.on监听及Callee.off解除监听**
被调用端Callee的监听函数注册时机, 取决于应用开发者。注册监听之前的数据不会被处理,取消监听之后的数据不会被处理。如下示例在Ability的onCreate注册'MSG_SEND_METHOD'监听,在onDestroy取消监听,收到序列化数据后作相应处理并返回,应用开发者根据实际需要做相应处理。具体示例代码如下: 被调用端Callee的监听函数注册时机, 取决于应用开发者。注册监听之前的数据不会被处理,取消监听之后的数据不会被处理。如下示例在Ability的onCreate注册'MSG_SEND_METHOD'监听,在onDestroy取消监听,收到序列化数据后作相应处理并返回,应用开发者根据实际需要做相应处理。具体示例代码如下:
```ts ```ts
...@@ -89,12 +108,12 @@ const TAG: string = '[CalleeAbility]' ...@@ -89,12 +108,12 @@ const TAG: string = '[CalleeAbility]'
const MSG_SEND_METHOD: string = 'CallSendMsg' const MSG_SEND_METHOD: string = 'CallSendMsg'
function sendMsgCallback(data) { function sendMsgCallback(data) {
Logger.log(TAG, 'CalleeSortFunc called') console.log('CalleeSortFunc called')
// 获取Caller发送的序列化数据 // 获取Caller发送的序列化数据
let receivedData = new MySequenceable(0, '') let receivedData = new MySequenceable(0, '')
data.readSequenceable(receivedData) data.readSequenceable(receivedData)
Logger.log(TAG, `receiveData[${receivedData.num}, ${receivedData.str}]`) console.log(`receiveData[${receivedData.num}, ${receivedData.str}]`)
// 作相应处理 // 作相应处理
// 返回序列化数据result给Caller // 返回序列化数据result给Caller
...@@ -106,7 +125,7 @@ export default class CalleeAbility extends Ability { ...@@ -106,7 +125,7 @@ export default class CalleeAbility extends Ability {
try { try {
this.callee.on(MSG_SEND_METHOD, sendMsgCallback) this.callee.on(MSG_SEND_METHOD, sendMsgCallback)
} catch (error) { } catch (error) {
Logger.error(TAG, `${MSG_SEND_METHOD} register failed with error ${JSON.stringify(error)}`) console.log(`${MSG_SEND_METHOD} register failed with error ${JSON.stringify(error)}`)
} }
} }
...@@ -121,14 +140,26 @@ export default class CalleeAbility extends Ability { ...@@ -121,14 +140,26 @@ export default class CalleeAbility extends Ability {
``` ```
### 访问Callee被调用端 ### 访问Callee被调用端
1. 导入Ability模块。 **1. 导入Ability模块**
``` ```ts
import Ability from '@ohos.application.Ability' import Ability from '@ohos.application.Ability'
``` ```
2. 获取Caller通信接口。 **2. 获取Caller通信接口**
Ability的context属性实现了startAbilityByCall方法,用于获取指定通用组件的Caller通信接口。如下示例通过`this.context`获取Ability实例的context属性,使用startAbilityByCall拉起Callee被调用端并获取Caller通信接口,注册Caller的onRelease监听。应用开发者根据实际需要做相应处理。具体示例代码如下: Ability的context属性实现了startAbilityByCall方法,用于获取指定通用组件的Caller通信接口。如下示例通过`this.context`获取Ability实例的context属性,使用startAbilityByCall拉起Callee被调用端并获取Caller通信接口,注册Caller的onRelease监听。应用开发者根据实际需要做相应处理。具体示例代码如下:
```ts ```ts
// 注册caller的release监听
private regOnRelease(caller) {
try {
caller.onRelease((msg) => {
console.log(`caller onRelease is called ${msg}`)
})
console.log('caller register OnRelease succeed')
} catch (error) {
console.log(`caller register OnRelease failed with ${error}`)
}
}
async onButtonGetCaller() { async onButtonGetCaller() {
try { try {
this.caller = await context.startAbilityByCall({ this.caller = await context.startAbilityByCall({
...@@ -136,41 +167,40 @@ async onButtonGetCaller() { ...@@ -136,41 +167,40 @@ async onButtonGetCaller() {
abilityName: 'CalleeAbility' abilityName: 'CalleeAbility'
}) })
if (this.caller === undefined) { if (this.caller === undefined) {
Logger.error(TAG, 'get caller failed') console.log('get caller failed')
return return
} }
Logger.log(TAG, 'get caller success') console.log('get caller success')
this.regOnRelease(this.caller) this.regOnRelease(this.caller)
} catch (error) { } catch (error) {
Logger.error(TAG, `get caller failed with ${error}`) console.log(`get caller failed with ${error}`)
} }
}.catch((error) => { }
console.error(TAG + 'get caller failed with ' + error)
})
``` ```
在跨设备场景下,需指定对端设备deviceId。具体示例代码如下: 在跨设备场景下,需指定对端设备deviceId。具体示例代码如下:
```ts ```ts
let TAG = '[MainAbility] ' async onButtonGetRemoteCaller() {
var caller = undefined var caller = undefined
let context = this.context var context = this.context
context.startAbilityByCall({ context.startAbilityByCall({
deviceId: getRemoteDeviceId(), deviceId: getRemoteDeviceId(),
bundleName: 'com.samples.CallApplication', bundleName: 'com.samples.CallApplication',
abilityName: 'CalleeAbility' abilityName: 'CalleeAbility'
}).then((data) => { }).then((data) => {
if (data != null) { if (data != null) {
caller = data caller = data
console.log(TAG + 'get remote caller success') console.log('get remote caller success')
// 注册caller的release监听 // 注册caller的release监听
caller.onRelease((msg) => { caller.onRelease((msg) => {
console.log(TAG + 'remote caller onRelease is called ' + msg) console.log(`remote caller onRelease is called ${msg}`)
}) })
console.log(TAG + 'remote caller register OnRelease succeed') console.log('remote caller register OnRelease succeed')
} }
}).catch((error) => { }).catch((error) => {
console.error(TAG + 'get remote caller failed with ' + error) console.error(`get remote caller failed with ${error}`)
}) })
}
``` ```
从DeviceManager获取指定设备的deviceId,getTrustedDeviceListSync接口仅对系统应用开放。具体示例代码如下: 从DeviceManager获取指定设备的deviceId,getTrustedDeviceListSync接口仅对系统应用开放。具体示例代码如下:
```ts ```ts
...@@ -178,29 +208,31 @@ import deviceManager from '@ohos.distributedHardware.deviceManager'; ...@@ -178,29 +208,31 @@ import deviceManager from '@ohos.distributedHardware.deviceManager';
var dmClass; var dmClass;
function getRemoteDeviceId() { function getRemoteDeviceId() {
if (typeof dmClass === 'object' && dmClass != null) { if (typeof dmClass === 'object' && dmClass != null) {
var list = dmClass.getTrustedDeviceListSync(); var list = dmClass.getTrustedDeviceListSync()
if (typeof (list) == 'undefined' || typeof (list.length) == 'undefined') { if (typeof (list) == 'undefined' || typeof (list.length) == 'undefined') {
console.log("MainAbility onButtonClick getRemoteDeviceId err: list is null"); console.log("MainAbility onButtonClick getRemoteDeviceId err: list is null")
return; return
} }
console.log("MainAbility onButtonClick getRemoteDeviceId success:" + list[0].deviceId); console.log("MainAbility onButtonClick getRemoteDeviceId success:" + list[0].deviceId)
return list[0].deviceId; return list[0].deviceId
} else { } else {
console.log("MainAbility onButtonClick getRemoteDeviceId err: dmClass is null"); console.log("MainAbility onButtonClick getRemoteDeviceId err: dmClass is null")
} }
} }
``` ```
在跨设备场景下,需要向用户申请数据同步的权限。具体示例代码如下: 在跨设备场景下,需要向用户申请数据同步的权限。具体示例代码如下:
```ts ```ts
let context = this.context requestPermission() {
let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC'] let context = this.context
context.requestPermissionsFromUser(permissions).then((data) => { let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC']
context.requestPermissionsFromUser(permissions).then((data) => {
console.log("Succeed to request permission from user with data: "+ JSON.stringify(data)) console.log("Succeed to request permission from user with data: "+ JSON.stringify(data))
}).catch((error) => { }).catch((error) => {
console.log("Failed to request permission from user with error: "+ JSON.stringify(error)) console.log("Failed to request permission from user with error: "+ JSON.stringify(error))
}) })
}
``` ```
3. 发送约定序列化数据 **3. 发送约定序列化数据**
向被调用端发送Sequenceable数据有两种方式,一种是不带返回值,一种是获取被调用端返回的数据,method以及序列化数据需要与被调用端协商一致。如下示例调用Call接口,向Callee被调用端发送数据。具体示例代码如下: 向被调用端发送Sequenceable数据有两种方式,一种是不带返回值,一种是获取被调用端返回的数据,method以及序列化数据需要与被调用端协商一致。如下示例调用Call接口,向Callee被调用端发送数据。具体示例代码如下:
```ts ```ts
...@@ -210,7 +242,7 @@ async onButtonCall() { ...@@ -210,7 +242,7 @@ async onButtonCall() {
let msg = new MySequenceable(1, 'origin_Msg') let msg = new MySequenceable(1, 'origin_Msg')
await this.caller.call(MSG_SEND_METHOD, msg) await this.caller.call(MSG_SEND_METHOD, msg)
} catch (error) { } catch (error) {
Logger.error(TAG, `caller call failed with ${error}`) console.log(`caller call failed with ${error}`)
} }
} }
``` ```
...@@ -224,27 +256,29 @@ async onButtonCallWithResult(originMsg, backMsg) { ...@@ -224,27 +256,29 @@ async onButtonCallWithResult(originMsg, backMsg) {
try { try {
let msg = new MySequenceable(1, originMsg) let msg = new MySequenceable(1, originMsg)
const data = await this.caller.callWithResult(MSG_SEND_METHOD, msg) const data = await this.caller.callWithResult(MSG_SEND_METHOD, msg)
Logger.log(TAG, 'caller callWithResult succeed') console.log('caller callWithResult succeed')
let result = new MySequenceable(0, '') let result = new MySequenceable(0, '')
data.readSequenceable(result) data.readSequenceable(result)
backMsg(result.str) backMsg(result.str)
Logger.log(TAG, `caller result is [${result.num}, ${result.str}]`) console.log(`caller result is [${result.num}, ${result.str}]`)
} catch (error) { } catch (error) {
Logger.error(TAG, `caller callWithResult failed with ${error}`) console.log(`caller callWithResult failed with ${error}`)
} }
} }
``` ```
4. 释放Caller通信接口 **4. 释放Caller通信接口**
Caller不再使用后,应用开发者可以通过release接口释放Caller。具体示例代码如下: Caller不再使用后,应用开发者可以通过release接口释放Caller。具体示例代码如下:
```ts ```ts
try { releaseCall() {
try {
this.caller.release() this.caller.release()
this.caller = undefined this.caller = undefined
Logger.log(TAG, 'caller release succeed') console.log('caller release succeed')
} catch (error) { } catch (error) {
Logger.error(TAG, `caller release failed with ${error}`) console.log(`caller release failed with ${error}`)
}
} }
``` ```
......
...@@ -876,7 +876,7 @@ disconnectAbility(connection: number, callback:AsyncCallback\<void>): void; ...@@ -876,7 +876,7 @@ disconnectAbility(connection: number, callback:AsyncCallback\<void>): void;
startAbilityByCall(want: Want): Promise&lt;Caller&gt;; startAbilityByCall(want: Want): Promise&lt;Caller&gt;;
获取指定通用组件服务端的caller通信接口, 并且将指定通用组件服务端拉起并切换到后台 启动指定Ability至前台或后台,同时获取其Caller通信接口,调用方可使用Caller与被启动的Ability进行通信
**系统能力**:SystemCapability.Ability.AbilityRuntime.Core **系统能力**:SystemCapability.Ability.AbilityRuntime.Core
...@@ -884,7 +884,7 @@ startAbilityByCall(want: Want): Promise&lt;Caller&gt;; ...@@ -884,7 +884,7 @@ startAbilityByCall(want: Want): Promise&lt;Caller&gt;;
| 参数名 | 类型 | 必填 | 说明 | | 参数名 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- | | -------- | -------- | -------- | -------- |
| want | [Want](js-apis-application-Want.md) | 是 | 传入需要启动的ability的信息,包含ability名称、包名、设备ID,设备ID缺省或为空表示启动本地ability。 | | want | [Want](js-apis-application-Want.md) | 是 | 传入需要启动的Ability的信息,包含abilityName、moduleName、bundleName、deviceId(可选)、parameters(可选),其中deviceId缺省或为空表示启动本地Ability,parameters缺省或为空表示后台启动Ability。 |
**返回值:** **返回值:**
...@@ -895,22 +895,40 @@ startAbilityByCall(want: Want): Promise&lt;Caller&gt;; ...@@ -895,22 +895,40 @@ startAbilityByCall(want: Want): Promise&lt;Caller&gt;;
**示例:** **示例:**
```js ```js
import Ability from '@ohos.application.Ability'; let caller = undefined;
var caller;
export default class MainAbility extends Ability { // 后台启动Ability,不配置parameters
onWindowStageCreate(windowStage) { var wantBackground = {
this.context.startAbilityByCall({
bundleName: "com.example.myservice", bundleName: "com.example.myservice",
moduleName: "entry",
abilityName: "MainAbility", abilityName: "MainAbility",
deviceId: "" deviceId: ""
}).then((obj) => { };
this.context.startAbilityByCall(wantBackground)
.then((obj) => {
caller = obj; caller = obj;
console.log('Caller GetCaller Get ' + caller); console.log('GetCaller success');
}).catch((e) => { }).catch((error) => {
console.log('Caller GetCaller error ' + e); console.log(`GetCaller failed with ${error}`);
}); });
// 前台启动Ability,将parameters中的"ohos.aafwk.param.callAbilityToForeground"配置为true
var wantForeground = {
bundleName: "com.example.myservice",
moduleName: "entry",
abilityName: "MainAbility",
deviceId: "",
parameters: {
"ohos.aafwk.param.callAbilityToForeground": true
} }
} };
this.context.startAbilityByCall(wantForeground)
.then((obj) => {
caller = obj;
console.log('GetCaller success');
}).catch((error) => {
console.log(`GetCaller failed with ${error}`);
});
``` ```
## AbilityContext.startAbilityWithAccount ## AbilityContext.startAbilityWithAccount
......
...@@ -694,15 +694,17 @@ disconnectAbility(connection: number): Promise&lt;void&gt;; ...@@ -694,15 +694,17 @@ disconnectAbility(connection: number): Promise&lt;void&gt;;
startAbilityByCall(want: Want): Promise&lt;Caller&gt;; startAbilityByCall(want: Want): Promise&lt;Caller&gt;;
将指定Ability拉起到后台并获取其Caller通信接口,拉起方可使用Caller与被拉起的Ability进行通信。 启动指定Ability至前台或后台,同时获取其Caller通信接口,调用方可使用Caller与被启动的Ability进行通信。
**系统能力**:SystemCapability.Ability.AbilityRuntime.Core **系统能力**:SystemCapability.Ability.AbilityRuntime.Core
**系统API**:此接口为系统接口,三方应用不支持调用。
**参数:** **参数:**
| 参数名 | 类型 | 必填 | 说明 | | 参数名 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- | | -------- | -------- | -------- | -------- |
| want | [Want](js-apis-application-Want.md) | 是 | 传入需要启动的ability的信息,包含ability名称、模块名、包名、设备ID,设备ID缺省或为空表示启动本地ability。 | | want | [Want](js-apis-application-Want.md) | 是 | 传入需要启动的Ability的信息,包含abilityName、moduleName、bundleName、deviceId(可选)、parameters(可选),其中deviceId缺省或为空表示启动本地Ability,parameters缺省或为空表示后台启动Ability。 |
**返回值:** **返回值:**
...@@ -714,16 +716,38 @@ startAbilityByCall(want: Want): Promise&lt;Caller&gt;; ...@@ -714,16 +716,38 @@ startAbilityByCall(want: Want): Promise&lt;Caller&gt;;
```js ```js
let caller = undefined; let caller = undefined;
this.context.startAbilityByCall({
// 后台启动Ability,不配置parameters
var wantBackground = {
bundleName: "com.example.myservice", bundleName: "com.example.myservice",
moduleName: "entry", moduleName: "entry",
abilityName: "MainAbility", abilityName: "MainAbility",
deviceId: "" deviceId: ""
}).then((obj) => { };
this.context.startAbilityByCall(wantBackground)
.then((obj) => {
caller = obj; caller = obj;
console.log('Caller GetCaller Get ' + caller); console.log('GetCaller Success');
}).catch((e) => { }).catch((error) => {
console.log('Caller GetCaller error ' + e); console.log(`GetCaller failed with ${error}`);
});
// 前台启动Ability,将parameters中的"ohos.aafwk.param.callAbilityToForeground"配置为true
var wantForeground = {
bundleName: "com.example.myservice",
moduleName: "entry",
abilityName: "MainAbility",
deviceId: "",
parameters: {
"ohos.aafwk.param.callAbilityToForeground": true
}
};
this.context.startAbilityByCall(wantForeground)
.then((obj) => {
caller = obj;
console.log('GetCaller success');
}).catch((error) => {
console.log(`GetCaller failed with ${error}`);
}); });
``` ```
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册