• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# InputMethodExtensionAbility
2
3## 使用场景
4[InputMethodExtensionAbility](../reference/apis/js-apis-inputmethod-extension-ability.md)基于[ExtensionAbility](extensionability-overview.md)框架,用于开发输入法应用。
5
6[InputMethodExtensionAbility](../reference/apis/js-apis-inputmethod-extension-ability.md)实例及其所在的ExtensionAbility进程的整个生命周期,都是由输入法框架进行调度管理。输入法框架提供了[InputMethodExtensionAbility](../reference/apis/js-apis-inputmethod-extension-ability.md)基类,开发者需要派生此基类,以实现输入法应用生命周期开始和销毁时的相关初始化操作和资源清理工作等。
7
8[InputMethodExtensionAbility](../reference/apis/js-apis-inputmethod-extension-ability.md)通过[InputMethodExtensionContext](../reference/apis/js-apis-inputmethod-extension-context.md)提供相关能力。
9
10
11## 实现一个输入法应用
12
13[InputMethodExtensionAbility](../reference/apis/js-apis-inputmethod-extension-ability.md)提供了onCreate()和onDestroy()生命周期回调,根据需要重写对应的回调方法。InputMethodExtensionAbility的生命周期如下:
14
15- **onCreate()**
16
17  服务被首次创建时触发该回调,开发者可以在此进行一些初始化的操作,例如注册公共事件监听等。
18
19  > **说明:**
20  >
21  > 如果服务已创建,再次启动该InputMethodExtensionAbility不会触发onCreate()回调。
22
23- **onDestroy()**
24
25  当不再使用服务且准备将该实例销毁时,触发该回调。开发者可以在该回调中清理资源,如注销监听等。
26
27
28## 开发步骤
29
30开发者在实现一个输入法应用时,需要在DevEco Studio工程中新建一个InputMethodExtensionAbility,具体步骤如下:
31
321. 在工程Module对应的ets目录下,右键选择“New > Directory”,新建一个目录,并命名为InputMethodExtensionAbility。
33
342. 在InputMethodExtensionAbility目录下,右键选择“New > File”,新建四个文件,分别为KeyboardController.tsInputMethodService.tsIndex.ets以及KeyboardKeyData.ts。目录如下:
35
36```
37/src/main/
38├── ets/inputmethodextability
39│   └──model/KeyboardController.ts			# 显示键盘
40│   └──InputMethodService.ts				# 自定义类继承InputMethodExtensionAbility并加上需要的生命周期回调
41│   └──pages
42│      └── Index.ets						# 绘制键盘,添加输入删除功能
43│      └── KeyboardKeyData.ts			    # 键盘属性定义
44├── resources/base/profile/main_pages.json
45```
46
47## 文件介绍
48
491. InputMethodService.ts文件。
50
51InputMethodService.ts文件中,增加导入InputMethodExtensionAbility的依赖包,自定义类继承InputMethodExtensionAbility并加上需要的生命周期回调。
52
53   ```ts
54   import Want from '@ohos.app.ability.Want';
55   import InputMethodExtensionAbility from '@ohos.InputMethodExtensionAbility';
56   import keyboardController from './model/KeyboardController'
57
58   export default class InputDemoService extends InputMethodExtensionAbility {
59
60     onCreate(want: Want): void {
61       keyboardController.onCreate(this.context); // 初始化窗口并注册对输入法框架的事件监听
62     }
63
64     onDestroy(): void {
65       console.log("onDestroy.");
66       keyboardController.onDestroy(); // 销毁窗口并去注册事件监听
67     }
68   }
69   ```
70
712. KeyboardController.ts文件。
72
73   ```ts
74   import common from '@ohos.app.ability.common';
75   import display from '@ohos.display';
76   import inputMethodEngine from '@ohos.inputMethodEngine';
77   import InputMethodExtensionContext from '@ohos.InputMethodExtensionContext';
78
79   // 调用输入法框架的getInputMethodAbility方法获取实例,并由此实例调用输入法框架功能接口
80   const inputMethodAbility: inputMethodEngine.InputMethodAbility = inputMethodEngine.getInputMethodAbility();
81
82   export class KeyboardController {
83     private mContext: InputMethodExtensionContext | undefined = undefined; // 保存InputMethodExtensionAbility中的context属性
84     private panel: inputMethodEngine.Panel | undefined = undefined;
85     private textInputClient: inputMethodEngine.InputClient | undefined = undefined;
86     private keyboardController: inputMethodEngine.KeyboardController | undefined = undefined;
87
88     constructor() {
89     }
90
91     public onCreate(context: InputMethodExtensionContext): void
92     {
93       this.mContext = context;
94       this.initWindow(); // 初始化窗口
95       this.registerListener(); // 注册对输入法框架的事件监听
96     }
97
98     public onDestroy(): void // 应用生命周期销毁
99     {
100       this.unRegisterListener(); // 去注册事件监听
101       if(this.panel) { // 销毁窗口
102         this.panel.hide();
103         inputMethodAbility.destroyPanel(this.panel);
104       }
105       if(this.mContext) {
106         this.mContext.destroy();
107       }
108     }
109
110     public insertText(text: string): void {
111       if(this.textInputClient) {
112         this.textInputClient.insertText(text);
113       }
114     }
115
116     public deleteForward(length: number): void {
117       if(this.textInputClient) {
118         this.textInputClient.deleteForward(length);
119       }
120     }
121
122     private initWindow(): void // 初始化窗口
123     {
124       if(this.mContext === undefined) {
125         return;
126       }
127       let dis = display.getDefaultDisplaySync();
128       let dWidth = dis.width;
129       let dHeight = dis.height;
130       let keyHeightRate = 0.47;
131       let keyHeight = dHeight * keyHeightRate;
132       let nonBarPosition = dHeight - keyHeight;
133       let panelInfo: inputMethodEngine.PanelInfo = {
134         type: inputMethodEngine.PanelType.SOFT_KEYBOARD,
135         flag: inputMethodEngine.PanelFlag.FLG_FIXED
136       };
137       inputMethodAbility.createPanel(this.mContext, panelInfo).then(async (inputPanel: inputMethodEngine.Panel) => {
138         this.panel = inputPanel;
139         if(this.panel) {
140           await this.panel.resize(dWidth, keyHeight);
141           await this.panel.moveTo(0, nonBarPosition);
142           await this.panel.setUiContent('inputmethodextability/pages/Index');
143         }
144       });
145     }
146
147     private registerListener(): void
148     {
149       this.registerInputListener(); // 注册对输入法框架服务的监听
150       ...
151       // 注册隐藏键盘事件监听等
152     }
153
154     private registerInputListener(): void { // 注册对输入法框架服务的开启及停止事件监听
155       inputMethodAbility.on('inputStart', (kbController, textInputClient) => {
156         this.textInputClient = textInputClient; // 此为输入法客户端实例,由此调用输入法框架提供给输入法应用的功能接口
157         this.keyboardController = kbController;
158       })
159       inputMethodAbility.on('inputStop', () => {
160         this.onDestroy(); // 销毁KeyboardController
161       });
162     }
163
164     private unRegisterListener(): void
165     {
166       inputMethodAbility.off('inputStart');
167       inputMethodAbility.off('inputStop', () => {});
168     }
169   }
170
171   const keyboardController = new KeyboardController();
172
173   export default keyboardController;
174   ```
175
1763. KeyboardKeyData.ts文件。
177
178   定义软键盘的按键显示内容。
179
180   ```ts
181   export interface sourceListType {
182     content: string,
183   }
184
185   export let numberSourceListData: sourceListType[] = [
186     {
187       content: '1'
188     },
189     {
190       content: '2'
191     },
192     {
193       content: '3'
194     },
195     {
196       content: '4'
197     },
198     {
199       content: '5'
200     },
201     {
202       content: '6'
203     },
204     {
205       content: '7'
206     },
207     {
208       content: '8'
209     },
210     {
211       content: '9'
212     },
213     {
214       content: '0'
215     }
216   ]
217   ```
218
2194. Index.ets文件。
220
221   主要描绘了具体按键功能。如按下数字键,就会将数字内容在输入框中打印出来,按下删除键,就会将内容删除。
222
223   同时在resources/base/profile/main_pages.json文件的src字段中添加此文件路径。
224
225   ```ets
226   import { numberSourceListData, sourceListType } from './keyboardKeyData';
227   import keyboardController from '../model/KeyboardController';
228
229   @Component
230   struct keyItem {
231     private keyValue: sourceListType = numberSourceListData[0];
232     @State keyBgc: string = "#fff"
233     @State keyFontColor: string = "#000"
234
235     build() {
236       Column() {
237         Flex({ direction: FlexDirection.Column,
238           alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
239           Text(this.keyValue.content).fontSize(20).fontColor(this.keyFontColor)
240         }
241       }
242       .backgroundColor(this.keyBgc)
243       .borderRadius(6)
244       .width("8%")
245       .height("65%")
246       .onClick(() => {
247         keyboardController.insertText(this.keyValue.content);
248       })
249     }
250   }
251
252   // 删除组件
253   @Component
254   export struct deleteItem {
255     @State keyBgc: string = "#fff"
256     @State keyFontColor: string = "#000"
257
258     build() {
259       Column() {
260         Flex({ direction: FlexDirection.Column,
261           alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
262           Text("删除").fontSize(20).fontColor(this.keyFontColor)
263         }
264       }
265       .backgroundColor(this.keyBgc)
266       .width("13%")
267       .borderRadius(6)
268       .onClick(() => {
269         keyboardController.deleteForward(1);
270       })
271     }
272   }
273
274   // 数字键盘
275   @Component
276   struct numberMenu {
277     private numberList: sourceListType[] = numberSourceListData;
278
279     build() {
280       Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceEvenly }) {
281         Flex({ justifyContent: FlexAlign.SpaceBetween }) {
282           ForEach(this.numberList, (item: sourceListType) => { // 数字键盘第一行
283             keyItem({ keyValue: item })
284           }, (item: sourceListType) => item.content);
285         }
286         .padding({ top: "2%" })
287         .width("96%")
288         .height("25%")
289
290         Flex({ justifyContent: FlexAlign.SpaceBetween }) {
291           deleteItem()
292         }
293         .width("96%")
294         .height("25%")
295       }
296     }
297   }
298
299   @Entry
300   @Component
301   struct Index {
302     private numberList: sourceListType[] = numberSourceListData
303
304     build() {
305       Stack() {
306         Flex({
307           direction: FlexDirection.Column,
308           alignItems: ItemAlign.Center,
309           justifyContent: FlexAlign.End
310         }) {
311               Flex({
312                 direction: FlexDirection.Column,
313                 alignItems: ItemAlign.Center,
314                 justifyContent: FlexAlign.SpaceBetween
315               }) {
316                 numberMenu({
317                   numberList: this.numberList
318                 })
319               }
320               .align(Alignment.End)
321               .width("100%")
322               .height("75%")
323             }
324         .height("100%").align(Alignment.End).backgroundColor("#cdd0d7")
325       }
326       .position({ x: 0, y: 0 }).zIndex(99999)
327     }
328   }
329   ```
330
3315. 在工程Module对应的[module.json5配置文件](../quick-start/module-configuration-file.md)中注册InputMethodExtensionAbility,type标签需要设置为“inputMethod”,srcEntry标签表示当前InputMethodExtensionAbility组件所对应的代码路径。
332
333   ```json
334   {
335     "module": {
336       ...
337       "extensionAbilities": [
338         {
339           "description": "inputMethod",
340           "icon": "$media:icon",
341           "name": "InputMethodExtAbility",
342           "srcEntry": "./ets/inputmethodextability/InputMethodService.ts",
343           "type": "inputMethod",
344           "exported": true,
345         }
346       ]
347     }
348   }
349   ```
350
351## 验证方法
352
3531. 使用hdc命令,拉起选择输入法弹窗应用:`hdc shell aa start ability -a InputMethod -b cn.openharmonyinputmethodchoosedialog`
354
3552. 在弹窗上显示的输入法应用列表中,选择并点击demo应用,将demo应用切换为当前输入法。
356
3573. 点击任意编辑框,即可拉起输入法demo。
358
359## 约束与限制
360
361为了降低InputMethodExtensionAbility能力被三方应用滥用的风险,在InputMethodExtensionAbility中限制调用以下模块中的接口。
362
363> **说明:**
364>
365> - 若导入被限制的模块,在编译时不报错,在运行时会返回错误的值,即undefined,导致不生效。
366> - 当前未禁止对音频管理模块[@ohos.multimedia.audio (音频管理)](../reference/apis/js-apis-audio.md)的访问,但要求开发者应遵循以下约定:
367>   - 不得因用户未授予录音权限而禁止用户使用输入法应用的非语音输入法功能;
368>   - 仅允许InputMethodExtensionAbility处于前台时开展与录音相关的业务。如仅允许软键盘在前台且用户主动操作语音输入法时,才进行录音;应用切换到后台时,应主动停止录音;
369>   - 系统会逐步增加对违反以上约定的行为进行管控和识别,因此未遵守此约定可能会造成业务功能异常。
370
371**模块列表:**
372
373- [@ohos.ability.featureAbility (FeatureAbility模块)](../reference/apis/js-apis-ability-featureAbility.md)
374- [@ohos.ability.particleAbility (ParticleAbility模块)](../reference/apis/js-apis-ability-particleAbility.md)
375- [@ohos.account.distributedAccount (分布式帐号管理)](../reference/apis/js-apis-distributed-account.md)
376- [@ohos.backgroundTaskManager (后台任务管理)](../reference/apis/js-apis-backgroundTaskManager.md)
377- [@ohos.bluetooth (蓝牙)](../reference/apis/js-apis-bluetooth.md)
378- [@ohos.bluetoothManager (蓝牙)](../reference/apis/js-apis-bluetoothManager.md)
379- [@ohos.connectedTag (有源标签)](../reference/apis/js-apis-connectedTag.md)
380- [@ohos.geolocation (位置服务)](../reference/apis/js-apis-geolocation.md)
381- [@ohos.geoLocationManager (位置服务)](../reference/apis/js-apis-geoLocationManager.md)
382- [@ohos.nfc.cardEmulation (标准NFC-cardEmulation)](../reference/apis/js-apis-cardEmulation.md)
383- [@ohos.nfc.controller (标准NFC)](../reference/apis/js-apis-nfcController.md)
384- [@ohos.nfc.tag (标准NFC-Tag)](../reference/apis/js-apis-nfcTag.md)
385- [@ohos.reminderAgent (后台代理提醒)](../reference/apis/js-apis-reminderAgent.md)
386- [@ohos.reminderAgentManager (后台代理提醒)](../reference/apis/js-apis-reminderAgentManager.md)
387- [@ohos.sensor (传感器)](../reference/apis/js-apis-sensor.md)
388- [@ohos.telephony.call (拨打电话)](../reference/apis/js-apis-call.md)
389- [@ohos.telephony.data (蜂窝数据)](../reference/apis/js-apis-telephony-data.md)
390- [@ohos.telephony.observer (observer)](../reference/apis/js-apis-observer.md)
391- [@ohos.telephony.radio (网络搜索)](../reference/apis/js-apis-radio.md)
392- [@ohos.telephony.sim (SIM卡管理)](../reference/apis/js-apis-sim.md)
393- [@ohos.telephony.sms (短信服务)](../reference/apis/js-apis-sms.md)
394- [@ohos.wallpaper (壁纸)](../reference/apis/js-apis-wallpaper.md)
395- [@ohos.wifiext (WLAN扩展接口)](../reference/apis/js-apis-wifiext.md)
396- [@ohos.wifiManager (WLAN)](../reference/apis/js-apis-wifiManager.md)
397- [@ohos.wifiManagerExt (WLAN扩展接口)](../reference/apis/js-apis-wifiManagerExt.md)
398- [@system.geolocation (地理位置)](../reference/apis/js-apis-system-location.md)
399- [nfctech (标准NFC-Tag Nfc 技术)](../reference/apis/js-apis-nfctech.md)
400- [tagSession (标准NFC-Tag TagSession)](../reference/apis/js-apis-tagSession.md)
401
402
403
404## 相关实例
405
406针对InputMethodExtensionAbility开发,有以下相关实例可供参考:
407
408- [Kika输入法](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-4.0-Release/code/Solutions/InputMethod/KikaInput)