Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenHarmony
Docs
提交
9a7a2fa9
D
Docs
项目概览
OpenHarmony
/
Docs
大约 1 年 前同步成功
通知
159
Star
292
Fork
28
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
D
Docs
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
9a7a2fa9
编写于
8月 09, 2022
作者:
H
HuangXW
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Call调用开发文档整改以及新增Call前台的说明
Signed-off-by:
N
HuangXW
<
huangxinwei4@huawei.com
>
上级
d429c717
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
176 addition
and
100 deletion
+176
-100
zh-cn/application-dev/ability/figures/stage-call.png
zh-cn/application-dev/ability/figures/stage-call.png
+0
-0
zh-cn/application-dev/ability/stage-call.md
zh-cn/application-dev/ability/stage-call.md
+108
-74
zh-cn/application-dev/reference/apis/js-apis-ability-context.md
...application-dev/reference/apis/js-apis-ability-context.md
+35
-17
zh-cn/application-dev/reference/apis/js-apis-service-extension-context.md
...n-dev/reference/apis/js-apis-service-extension-context.md
+33
-9
未找到文件。
zh-cn/application-dev/ability/figures/stage-call.png
查看替换文件 @
d429c717
浏览文件 @
9a7a2fa9
2.9 KB
|
W:
|
H:
9.8 KB
|
W:
|
H:
2-up
Swipe
Onion skin
zh-cn/application-dev/ability/stage-call.md
浏览文件 @
9a7a2fa9
# Call调用开发指导
## 场景介绍
Ability Call调用是Ability能力的扩展,它为Ability提供一种能够被外部调用的能力,使Ability既能被拉起到前台展示UI,也支持Ability在后台被创建并运行。应用开发者可通过Call调用,使用IPC通信实现不同Ability之间的数据共享。Call调用的场景主要包括:
-
创建Callee被调用端。
-
访问Callee被调用端。
Call调用是Ability能力的扩展,它为Ability提供一种能够被外部调用并与外部进行通信的能力。Call调用支持前台与后台两种启动方式,使Ability既能被拉起到前台展示UI,也可以在后台被创建并运行。Call调用在调用方与被调用方间建立了IPC通信,因此应用开发者可通过Call调用实现不同Ability之间的数据共享。
Call调用的核心接口是startAbilityByCall方法,与startAbility接口的不同之处在于:
-
startAbilityByCall支持前台与后台两种启动方式,而startAbility仅支持前台启动。
-
调用方可使用startAbilityByCall所返回的Caller对象与被调用方进行通信,而startAbilty不具备通信能力。
Call调用的使用场景主要包括:
-
需要与被启动的Ability进行通信
-
希望被启动的Ability在后台运行
本文中的Caller和Callee分别表示调用者和被调用者,IPC表示进程间通信,Call调用流程示意图如下。
**表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
)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> Callee
被调用端所在的Ability,
启动模式需要为单实例。
> 当前仅支持系统应用
及ServiceExtensionAbility使用Call访问Callee
。
> Callee
Ability的
启动模式需要为单实例。
> 当前仅支持系统应用
使用Call调用
。
## 接口说明
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方法。|
|off(method: string): void|通用组件Callee
去
注册method的callback方法。|
|off(method: string): void|通用组件Callee
解
注册method的callback方法。|
|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通信接口。|
|onRelease(callback: OnReleaseCallBack): void|注册通用组件通信断开监听通知。|
## 开发步骤
Call调用的开发步骤:
-
创建Callee被调用端。
-
访问Callee被调用端。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 开发步骤章节中的示例代码片段是开发过程的步骤化展示,部分代码可能无法单独运行,完整工程代码请参考[相关实例](#相关实例)。
### 创建Callee被调用端
Callee被调用端,需要实现指定方法的数据接收回调函数、数据的序列化及反序列化方法。在需要接收数据期间,通过on接口注册监听,无需接收数据时通过off接口解除监听。
1.
配置Ability的启动模式
**1. 配置Ability的启动模式**
配置module.json5,将Callee
被调用端所在的
Ability配置为单实例"singleton"。
配置module.json5,将CalleeAbility配置为单实例"singleton"。
|Json字段|字段说明|
|:------|:------|
...
...
@@ -51,11 +70,11 @@ Ability配置标签示例如下:
"visible"
:
true
}]
```
2.
导入Ability模块。
```
**2. 导入Ability模块**
```
ts
import
Ability
from
'
@ohos.application.Ability
'
```
3.
定义约定的序列化数据。
**3. 定义约定的序列化数据**
调用端及被调用端发送接收的数据格式需协商一致,如下示例约定数据由number和string组成。具体示例代码如下:
```
ts
...
...
@@ -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取消监听,收到序列化数据后作相应处理并返回,应用开发者根据实际需要做相应处理。具体示例代码如下:
```
ts
...
...
@@ -89,12 +108,12 @@ const TAG: string = '[CalleeAbility]'
const
MSG_SEND_METHOD
:
string
=
'
CallSendMsg
'
function
sendMsgCallback
(
data
)
{
Logger
.
log
(
TAG
,
'
CalleeSortFunc called
'
)
console
.
log
(
'
CalleeSortFunc called
'
)
// 获取Caller发送的序列化数据
let
receivedData
=
new
MySequenceable
(
0
,
''
)
data
.
readSequenceable
(
receivedData
)
Logger
.
log
(
TAG
,
`receiveData[
${
receivedData
.
num
}
,
${
receivedData
.
str
}
]`
)
console
.
log
(
`receiveData[
${
receivedData
.
num
}
,
${
receivedData
.
str
}
]`
)
// 作相应处理
// 返回序列化数据result给Caller
...
...
@@ -106,7 +125,7 @@ export default class CalleeAbility extends Ability {
try
{
this
.
callee
.
on
(
MSG_SEND_METHOD
,
sendMsgCallback
)
}
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 {
```
### 访问Callee被调用端
1.
导入Ability模块。
```
**1. 导入Ability模块**
```
ts
import
Ability
from
'
@ohos.application.Ability
'
```
2.
获取Caller通信接口。
**2. 获取Caller通信接口**
Ability的context属性实现了startAbilityByCall方法,用于获取指定通用组件的Caller通信接口。如下示例通过
`this.context`
获取Ability实例的context属性,使用startAbilityByCall拉起Callee被调用端并获取Caller通信接口,注册Caller的onRelease监听。应用开发者根据实际需要做相应处理。具体示例代码如下:
```
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
()
{
try
{
this
.
caller
=
await
context
.
startAbilityByCall
({
...
...
@@ -136,41 +167,40 @@ async onButtonGetCaller() {
abilityName
:
'
CalleeAbility
'
})
if
(
this
.
caller
===
undefined
)
{
Logger
.
error
(
TAG
,
'
get caller failed
'
)
console
.
log
(
'
get caller failed
'
)
return
}
Logger
.
log
(
TAG
,
'
get caller success
'
)
console
.
log
(
'
get caller success
'
)
this
.
regOnRelease
(
this
.
caller
)
}
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。具体示例代码如下:
```
ts
let
TAG
=
'
[MainAbility]
'
var
caller
=
undefined
let
context
=
this
.
context
context
.
startAbilityByCall
({
deviceId
:
getRemoteDeviceId
(),
bundleName
:
'
com.samples.CallApplication
'
,
abilityName
:
'
CalleeAbility
'
}).
then
((
data
)
=>
{
if
(
data
!=
null
)
{
caller
=
data
console
.
log
(
TAG
+
'
get remote caller success
'
)
// 注册caller的release监听
caller
.
onRelease
((
msg
)
=>
{
console
.
log
(
TAG
+
'
remote caller onRelease is called
'
+
msg
)
})
console
.
log
(
TAG
+
'
remote caller register OnRelease succeed
'
)
}
}).
catch
((
error
)
=>
{
console
.
error
(
TAG
+
'
get remote caller failed with
'
+
error
)
})
async
onButtonGetRemoteCaller
()
{
var
caller
=
undefined
var
context
=
this
.
context
context
.
startAbilityByCall
({
deviceId
:
getRemoteDeviceId
(),
bundleName
:
'
com.samples.CallApplication
'
,
abilityName
:
'
CalleeAbility
'
}).
then
((
data
)
=>
{
if
(
data
!=
null
)
{
caller
=
data
console
.
log
(
'
get remote caller success
'
)
// 注册caller的release监听
caller
.
onRelease
((
msg
)
=>
{
console
.
log
(
`remote caller onRelease is called
${
msg
}
`
)
})
console
.
log
(
'
remote caller register OnRelease succeed
'
)
}
}).
catch
((
error
)
=>
{
console
.
error
(
`get remote caller failed with
${
error
}
`
)
})
}
```
从DeviceManager获取指定设备的deviceId,getTrustedDeviceListSync接口仅对系统应用开放。具体示例代码如下:
```
ts
...
...
@@ -178,29 +208,31 @@ import deviceManager from '@ohos.distributedHardware.deviceManager';
var
dmClass
;
function
getRemoteDeviceId
()
{
if
(
typeof
dmClass
===
'
object
'
&&
dmClass
!=
null
)
{
var
list
=
dmClass
.
getTrustedDeviceListSync
()
;
var
list
=
dmClass
.
getTrustedDeviceListSync
()
if
(
typeof
(
list
)
==
'
undefined
'
||
typeof
(
list
.
length
)
==
'
undefined
'
)
{
console
.
log
(
"
MainAbility onButtonClick getRemoteDeviceId err: list is null
"
)
;
return
;
console
.
log
(
"
MainAbility onButtonClick getRemoteDeviceId err: list is null
"
)
return
}
console
.
log
(
"
MainAbility onButtonClick getRemoteDeviceId success:
"
+
list
[
0
].
deviceId
)
;
return
list
[
0
].
deviceId
;
console
.
log
(
"
MainAbility onButtonClick getRemoteDeviceId success:
"
+
list
[
0
].
deviceId
)
return
list
[
0
].
deviceId
}
else
{
console
.
log
(
"
MainAbility onButtonClick getRemoteDeviceId err: dmClass is null
"
)
;
console
.
log
(
"
MainAbility onButtonClick getRemoteDeviceId err: dmClass is null
"
)
}
}
```
在跨设备场景下,需要向用户申请数据同步的权限。具体示例代码如下:
```
ts
let
context
=
this
.
context
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
))
}).
catch
((
error
)
=>
{
console
.
log
(
"
Failed to request permission from user with error:
"
+
JSON
.
stringify
(
error
))
})
requestPermission
()
{
let
context
=
this
.
context
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
))
}).
catch
((
error
)
=>
{
console
.
log
(
"
Failed to request permission from user with error:
"
+
JSON
.
stringify
(
error
))
})
}
```
3.
发送约定序列化数据
**3. 发送约定序列化数据**
向被调用端发送Sequenceable数据有两种方式,一种是不带返回值,一种是获取被调用端返回的数据,method以及序列化数据需要与被调用端协商一致。如下示例调用Call接口,向Callee被调用端发送数据。具体示例代码如下:
```
ts
...
...
@@ -210,7 +242,7 @@ async onButtonCall() {
let
msg
=
new
MySequenceable
(
1
,
'
origin_Msg
'
)
await
this
.
caller
.
call
(
MSG_SEND_METHOD
,
msg
)
}
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) {
try
{
let
msg
=
new
MySequenceable
(
1
,
originMsg
)
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
,
''
)
data
.
readSequenceable
(
result
)
backMsg
(
result
.
str
)
Logger
.
log
(
TAG
,
`caller result is [
${
result
.
num
}
,
${
result
.
str
}
]`
)
console
.
log
(
`caller result is [
${
result
.
num
}
,
${
result
.
str
}
]`
)
}
catch
(
error
)
{
Logger
.
error
(
TAG
,
`caller callWithResult failed with
${
error
}
`
)
console
.
log
(
`caller callWithResult failed with
${
error
}
`
)
}
}
```
4.
释放Caller通信接口
**4. 释放Caller通信接口**
Caller不再使用后,应用开发者可以通过release接口释放Caller。具体示例代码如下:
```
ts
try
{
this
.
caller
.
release
()
this
.
caller
=
undefined
Logger
.
log
(
TAG
,
'
caller release succeed
'
)
}
catch
(
error
)
{
Logger
.
error
(
TAG
,
`caller release failed with
${
error
}
`
)
releaseCall
()
{
try
{
this
.
caller
.
release
()
this
.
caller
=
undefined
console
.
log
(
'
caller release succeed
'
)
}
catch
(
error
)
{
console
.
log
(
`caller release failed with
${
error
}
`
)
}
}
```
...
...
zh-cn/application-dev/reference/apis/js-apis-ability-context.md
浏览文件 @
9a7a2fa9
...
...
@@ -876,7 +876,7 @@ disconnectAbility(connection: number, callback:AsyncCallback\<void>): void;
startAbilityByCall(want: Want): Promise
<
Caller
>
;
获取指定通用组件服务端的caller通信接口, 并且将指定通用组件服务端拉起并切换到后台
。
启动指定Ability至前台或后台,同时获取其Caller通信接口,调用方可使用Caller与被启动的Ability进行通信
。
**系统能力**
:SystemCapability.Ability.AbilityRuntime.Core
...
...
@@ -884,7 +884,7 @@ startAbilityByCall(want: Want): Promise<Caller>;
| 参数名 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- |
| want |
[
Want
](
js-apis-application-Want.md
)
| 是 | 传入需要启动的
ability的信息,包含ability名称、包名、设备ID,设备ID缺省或为空表示启动本地a
bility。 |
| want |
[
Want
](
js-apis-application-Want.md
)
| 是 | 传入需要启动的
Ability的信息,包含abilityName、moduleName、bundleName、deviceId(可选)、parameters(可选),其中deviceId缺省或为空表示启动本地Ability,parameters缺省或为空表示后台启动A
bility。 |
**返回值:**
...
...
@@ -895,22 +895,40 @@ startAbilityByCall(want: Want): Promise<Caller>;
**示例:**
```
js
import
Ability
from
'
@ohos.application.Ability
'
;
var
caller
;
export
default
class
MainAbility
extends
Ability
{
onWindowStageCreate
(
windowStage
)
{
this
.
context
.
startAbilityByCall
({
bundleName
:
"
com.example.myservice
"
,
abilityName
:
"
MainAbility
"
,
deviceId
:
""
}).
then
((
obj
)
=>
{
caller
=
obj
;
console
.
log
(
'
Caller GetCaller Get
'
+
caller
);
}).
catch
((
e
)
=>
{
console
.
log
(
'
Caller GetCaller error
'
+
e
);
});
let
caller
=
undefined
;
// 后台启动Ability,不配置parameters
var
wantBackground
=
{
bundleName
:
"
com.example.myservice
"
,
moduleName
:
"
entry
"
,
abilityName
:
"
MainAbility
"
,
deviceId
:
""
};
this
.
context
.
startAbilityByCall
(
wantBackground
)
.
then
((
obj
)
=>
{
caller
=
obj
;
console
.
log
(
'
GetCaller success
'
);
}).
catch
((
error
)
=>
{
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
...
...
zh-cn/application-dev/reference/apis/js-apis-service-extension-context.md
浏览文件 @
9a7a2fa9
...
...
@@ -694,15 +694,17 @@ disconnectAbility(connection: number): Promise<void>;
startAbilityByCall(want: Want): Promise
<
Caller
>
;
将指定Ability拉起到后台并获取其Caller通信接口,拉起方可使用Caller与被拉起
的Ability进行通信。
启动指定Ability至前台或后台,同时获取其Caller通信接口,调用方可使用Caller与被启动
的Ability进行通信。
**系统能力**
:SystemCapability.Ability.AbilityRuntime.Core
**系统API**
:此接口为系统接口,三方应用不支持调用。
**参数:**
| 参数名 | 类型 | 必填 | 说明 |
| -------- | -------- | -------- | -------- |
| want |
[
Want
](
js-apis-application-Want.md
)
| 是 | 传入需要启动的
ability的信息,包含ability名称、模块名、包名、设备ID,设备ID缺省或为空表示启动本地a
bility。 |
| want |
[
Want
](
js-apis-application-Want.md
)
| 是 | 传入需要启动的
Ability的信息,包含abilityName、moduleName、bundleName、deviceId(可选)、parameters(可选),其中deviceId缺省或为空表示启动本地Ability,parameters缺省或为空表示后台启动A
bility。 |
**返回值:**
...
...
@@ -714,16 +716,38 @@ startAbilityByCall(want: Want): Promise<Caller>;
```
js
let
caller
=
undefined
;
this
.
context
.
startAbilityByCall
({
// 后台启动Ability,不配置parameters
var
wantBackground
=
{
bundleName
:
"
com.example.myservice
"
,
moduleName
:
"
entry
"
,
abilityName
:
"
MainAbility
"
,
deviceId
:
""
}).
then
((
obj
)
=>
{
caller
=
obj
;
console
.
log
(
'
Caller GetCaller Get
'
+
caller
);
}).
catch
((
e
)
=>
{
console
.
log
(
'
Caller GetCaller error
'
+
e
);
});
};
this
.
context
.
startAbilityByCall
(
wantBackground
)
.
then
((
obj
)
=>
{
caller
=
obj
;
console
.
log
(
'
GetCaller Success
'
);
}).
catch
((
error
)
=>
{
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.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录