现在的位置: 首页 > 综合 > 正文

Android输入法框的梳理

2013年10月15日 ⁄ 综合 ⁄ 共 9735字 ⁄ 字号 评论关闭

http://blog.csdn.net/a345017062/article/details/6121147

/frameworks/base/services/java/InputMethodManagerService.java

这是整个系统当中,一切与输入法有关的地方的总控制中心。它通过管理下面三个模块来实现系统的输入法框架。

1、/frameworks/base/services/java/WindowManagerService

负责显示输入法,接收用户事件。

2、/frameworks/base/core/java/android.inputmethodservice/InputMethodService

输入法内部逻辑,键盘布局,选词等,最终把选出的字符通过commitText提交出来。要做一个像搜狗输入法这样的东西的话,主要就是在这里做文章。

3、InputManager

由UI控件(View,TextView,EditText等)调用,用来操作输入法。比如,打开,关闭,切换输入法等。

 

 

下面说一下InputMethodManagerService这个控制中心是怎么样与三个模块交互的。

 

1、与WindowManagerSerivce的交互。

首先,InputMethodManagerService在初始化时,会调用IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)),得到IWindowManager这个代理,然后通过IWindowManager与WindowManagerService交互。比如下面这些操作:

调用mIWindowManager.addWindowToken(mCurToken, WindowManager.LayoutParams.TYPE_INPUT_METHOD),让WindowManagerService显示输入法界面。

调用mIWindowManager.removeWindowToken(mCurToken)让输入法界面关闭。

调用mIWindowManager.inputMethodClientHasFocus(client)判断输入法是否聚焦。

 

2、与InputMethodService的交互。

InputMethodManagerService在内部维护着一个ArrayList<InputMethodInfo> mMethodList。这个列表会在服务启动时通过PackageManager查询当前系统中的输入法程序来得到。与之对应的,每一个输入法程序的AndroidManifest.xml中都会有一个Service,而每个Service中都会有标记来告诉系统,自己是个输入法程序。下面这个是我从系统自带的例子Samples/SoftKeyboard/AndroidManifest.xml中的取出来的:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" 

        package="com.example.android.softkeyboard">

    <application android:label="@string/ime_name">

        <service android:name="SoftKeyboard"

                android:permission="android.permission.BIND_INPUT_METHOD">

            <intent-filter>

                <action android:name="android.view.InputMethod" />

            </intent-filter>

            <meta-data android:name="android.view.im" android:resource="@xml/method" />

        </service>

    </application>

</manifest>

另外,InputMethodManagerService内部还有一个PackageReceiver,当系统中有程序的安装、删除、重启等事件发生时,会更新mMethodList。InputMethodManagerService打开,关闭,切换输入法时,其实就是在操作mMethodList中某个InputMethodInfo。把InputMethodInfo中的代表某个输入法的InputMethodService启动或者销毁,就实现了输入法的打开和关闭。

 

3、与InputMethodManager的交互

InputMethodManager中会包含一个IInputMethodManager,这个东西就是InputMethodManagerService的代理,打开关闭输入法这些操作就是由InputMethodManager中的某些方法调用IInputMethodManager中相应的方法来实现的。比如:

mService.getInputMethodList()获取输入法列表。

mService.updateStatusIcon(imeToken, packageName, iconId)更新输入法图标,即屏幕上方状态栏中的输入法图标。

mService.finishInput(mClient)隐藏当前输入法。这所以不说关闭输入法,是因为输入法服务启动起来以后,只有在系统关闭或者切换输入法时才会关闭。

mService.showSoftInput(mClient, flags, resultReceiver)打开当前输入法。

...

 

 

分别介绍完三大模块之后,再介绍两个东西,输入法的实现和怎么样调用输入法。

 

1、以系统的SoftKeyboard为例,实现一个输入法至少需要Keyboard,KeyboardView,CandidateView,SoftKeyboard这四个东西。

CandidateView负责显示软键盘上面的那个候选区域。

Keyboard负责解析并保存键盘布局,并提供选词算法,供程序运行当中使用。其中键盘布局是以XML文件存放在资源当中的。比如我们在汉字输入法下,按下b、a两个字母。Keyboard就负责把这两个字母变成爸、把、巴等显示在CandidateView上。

KeyboardView负责显示,就是我们看到的按键。

上面这两人东西合起来,组成了InputView,就是我们看到的软键盘。

SoftKeyboard继承了InputMethodService,启动一个输入法,其实就是启动一个InputMethodService,当SoftKeyboard输入法被使用时,启动就会启动SoftKeyboard这个Service。InputMethodService中管理着一个继承自Dialog的SoftInputWindow,而SoftInputWindow里面就包括了InputView和CandidateView这两个东西。

 

2、怎么样调用输入法呢?

说起这个东西,很自然地想起EditText来,我们团队跟踪过这个Widget,EditText本身很简单,主要的代码在TextView和View当中。这两个Widget本身又很复杂,杂在一起说不清楚。这里我就把我们团队以前做过的一个小例子拿进来做参考,说明一下如何从一个View上调用输入法和如何接收输入法传过来的字符串。

小例子的起源来自于我们要做一个浏览器,需要在SurfaceView来在Canvas上面绘制自己需要的东西,开启自己的主控制循环线程,事件处理等。比如我要在SurfaceView上绘制输入浏览器中的按钮、文本、图片、输入框等,当然这些和ImageView,TextView没有关系,都是用自己的UI引擎来做的。最后所有问题都解决了,却在输入框上卡壳了。因为要实现输入,得调用EditText,否则就必须自己去和EditText一样连接输入法。以前找过相关资料,看网上也有人碰到过这个问题,但都没有答案。最后,还是团队中一个很牛的娃给解决了。代码很简单,不出二十行,但没资料,View的源码又太庞大,费的劲却是只有我们团队的人才能体会得到的。。。这里佩服张老二同学一下,没有他的努力,就没有下面这二十多行很重要很重要的源码的诞生。

首先,定义一个继承自BaseInputConnection的类。

public class MyBaseInputConnection extends BaseInputConnection{

public MyBaseInputConnection(View targetView, boolean fullEditor) {

super(targetView, fullEditor);

}

public static String tx="";

@Override

public boolean commitText(CharSequence text, int newCursorPosition) {//输入法程序就是通过调用这个方法把最终结果输出来的。

tx = text.toString();

return true;

}

}

BaseInputConnection相当于一个InputMethodService和View之间的一个通道。每当InputMethodService产生一个结果时,都会调用BaseInputConnection的commitText方法,把结果传递出来。

public class MyView extends SurfaceView ...{

InputMethodManager input = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);//得到InputMethodManager。

ResultReceiver receiver = new ResultReceiver(new Handler() {//定义事件处理器。

public void handleMessage(Message msg) {

 

}

});

... ...

input.showSoftInput(this, 0, mRR);//在你想呼出输入法的时候,调用这一句。

... ...

@Override

public InputConnection onCreateInputConnection(EditorInfo outAttrs) {//这个方法继承自View。把自定义的BaseInputConnection通道传递给InputMethodService。

return new MyBaseInputConnection(this, false);

}

}

InputMethodManager:

整个输入法框架(IMF)结构的核心API,处理应用程序和当前输入法的交互。可以通过Context.getSystemService()来获取一个InputMethodManager的实例。


结构概览:

一个IMF结构中包含三个主要的部分:

input method manager:管理各部分的交互。它是一个客户端API,存在于各个应用程序的context中,用来沟通管理所有进程间交互的全局系统服务。

input method(IME):实现一个允许用户生成文本的独立交互模块。系统绑定一个当前的输入法。使其创建和生成,决定输入法何时隐藏或者显示它的UI。同一时间只能有一个IME运行。

client application:通过输入法管理器控制输入焦点和IME的状态。一次只能有一个客户端使用IME。


applications:

大多数情况下,使用标准的TextView或者它的子类的应用很少能很好的使用soft input methods。你需要主要的主要事情如下:

当你的可编辑区域显示的时候,正确设置inputType,以便于input method能找到足够的context,这样input method才能更好的输入文本。

正确处理输入法显示的时候覆盖的区域。理想情况下,应用应该处理窗口缩小带来的影响。但是这依赖系统在需要的情况下对窗口重置的操作。你应该在activity中设置windowSoftInputMode属性或者在创建的窗口设置适当的值,这能帮助系统判断是否重置大小。系统自动尝试判断是否重置大小,但是这可能带来错误。

你也可以通过windowSoftInputMode属性对你的窗体设置最喜欢的soft input状态。


你可以通过InputMethodManager这个API来实现更精细的控制。


当你自己谢自己的文本编辑域的时候,你必须实现onCreateInputConnection(EditInfo)来返回一个InputConnection的实例,用来允许IME和你的文本编辑域来交互。


input methods:

一个IME实现为一个Service,典型的是继承InputMethodService。IME提供核心的InputMethod接口,尽管提供InputMethod通常是由InputMethodService来处理,而IME的实现只需要处理更高层的API。

更多信息参考InputMethodService


Security:

由于输入法必须有自由去完全掌控UI,和监听所有用户输入,所以这会导致很多安全问题。由于Android输入法框架允许任何第三方软件,所以注意限制IME的选中和交互。


以下是IMF背后的安全架构的关键点:

只允许系统通过BIND_INPUT_METHOD权限直接访问IME的InputMethod接口。通过绑定到要求这个权限的服务来强制实现这一点。所以系统可以保证没有不被信任的客户端在它的控制之外访问到当前的输入法。

IMF框架中有很多客户端进程,但仅有一个是活动的。不活动的客户端不能和IMF的键部分交互。这是通过下述机制实现的。

输入法的客户端只被授予访问输入法的InputMethodSession接口的权限。每一个客户端实现一个InputMethodSession接口的实例。当前IMF只处理和活动activity关联的那个InputMethodSession。普通IME(继承自InputMethodService)是通过AbstractInputMethodService强制实现的。但是自定义InputMethodSession实现的IME必须显示处理这一点。

只有活动的客户端的InputConnection可以采取操作。IMF判断每一个客户端是否活动,强制要求不活动的客户端调用InputConnection会被忽略。这确保了IME传递事件和文本输入到当前有焦点的UI。

屏幕关闭状态下IME永远不能和InputConnection交互。这一点通过使屏幕关闭状态下所有的客户端不活动来实现。这一点阻止了有问题的IME在用户无法看到它的行为情况下操作用户UI。

客户端应用程序可以请求选择一个新的IME,但是不能自己编程切换。这防止了恶意程序切换到自带的IME,当用户运行到另一个程序时,这个IME会保持运行。另一方面,IME程序可以编程实现切换到系统的另一个IME,这是因为IME本身已经拥有了用户输入的全部控制。

用户使用新的IME之前,必须显式地在设置里面允许一个新的IME。这就是对系统说,我(用户)已经知道了这个IME,可以用。出了问题用户负责怪不了系统。


调用下面代码:(第一次调用显示,再次调用则隐藏,如此反复),this指activity

[java:firstline[1]] view
plain
copy

  1. InputMethodManager imm = (InputMethodManager)this.getSystemService(Context.INPUT_METHOD_SERVICE);  
  2. imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);  
  3. imm.showSoftInput(myview, InputMethodManager.SHOW_IMPLICIT);  

 

单独显示隐藏软键盘

显示:

[java:firstline[1]] view
plain
copy

  1. InputMethodManager imm = (InputMethodManager)this.getSystemService(Context.INPUT_METHOD_SERVICE);  
  2. imm.showSoftInput(myview, 0);  

隐藏:

[java:firstline[1]] view
plain
copy

  1. imm.hideSoftInputFromWindow(view.getWindowToken(), 0);  

程序启动后,自动弹出软键盘,可以通过设置一个时间函数来实现,不能再onCreate里写:

[java:firstline[1]] view
plain
copy

  1. Timer timer = new Timer();  
  2.   
  3. timer.schedule(new TimerTask() {   
  4. @Override public void run() {   
  5. InputMethodManager imm = (InputMethodManager)this.getSystemService(INPUT_METHOD_SERVICE); imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);  
  6.   
  7. Toast.makeText(chick.this"show", Toast.LENGTH_SHORT).show();   
  8. }   
  9. }, 1000);  

Android 1.5 新特色之一就是输入法框架(Input Method Framework,IMF),正是它的出现,才为诞生不带实体键盘的设备提供了可能。IMF设计用来支持不同的IME,包括了soft keyboard,hand-writing recognizes和hard keyboard translators。这里,我们把焦点锁定在soft keyboard上。

新特色对普通应用开发者而言,应该确保让应用能够和IMF很好的配合,提供优秀的用户体验。应用要做的最重要事情,就是对每个EditText使用 新的属性android:inputType,这个属性实际代替了很多已经存在的属性,包括android:password, android:singleLine, android:numeric, android:phoneNumber, android:capitalize, android:autoText, android:editable。如果你两个都声明了,Cupcake设备就会使用新的android:inputType属性,而忽视其他的。

主要的API就是android.view.inputmethod.InputMethodManager,你可以通过Context.getSystemService方法来获取。它允许你和全局输入法状态进行交互,例如显式隐藏或者显示IME的输入法区域。

新特色对于系统开发人员而言,其提供了开发各种各样语言输入法的实现入口,有了它,其他语言输入法才能进驻Android。以下内容即关注如何实现一个输入法。

要创建一个输入法,需要继承android.inputmethodservice.InputMethodService。这个类提供了输入法的 基本实现,包括状态管理、控制输入法可见,还有和当前activity通讯。Android提供了两个输入法和一个示例,这两个输入法分别是 PinyinIME和LatinIME输入法,你可以在源码packages/inputmethods下找到,示例即SoftKeyboard,可以在 SDK 1.5下的platforms/android-1.5/samples下找到。这三个输入法提供了目前实现Android平台输入法实现的最佳参考。

输入法的打包方式和其他应用是相同的,在AndroidManifest.xml中,要把输入法声明为service,并附带上合适的intent filter和相关的meta data。如下所示:

<service android:name=".DemoIme"
android:label="@string/ime_name"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method" />
</service>

如果输入法允许用户调整设置,那么你还应该提供一个setting activity。这个时候不要忘记在input-method的XML文件中,加入相关的属性,如以下所示(这个文件就是刚才meta-data中的@xml/method):

<input-method xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.demo.SettingsActivity" 
android:isDefault="@bool/im_is_default" />

输入法在UI展现上,有两个主要的可见元素,input view和candiate view。但这不是必需的,可以根据实际需要选择你需要的元素。Input View,是用户从键盘、手写或者其他方式输入文本的地方。当输入法第一次展现的时 候,InputMethodService.onCreateInputView()就会被调用。Candidates View,是候选词汇集合出现的地方。其可有可无,可以在调用InputMethodService.onCreateCandidatesView时返 回null,–这是它的默认行为。

应用的文本区可以有不同的输入类型,包括文本、数值、URL、邮箱地址和搜索,因此在你实现新的输入法时,需要当心不同的输入类型。输入法并不会自 动切换不同的输入类型,所以你要在IME中支持所有的类型。不过可以轻松一点的是, IME并不负责输入内容的校验,—因为这是应用的职责。

InputMethodService.onStartInputView()被调用时,会传入一个EditorInfo对象,这个对象包含了关于 输入类型和text field的其他属性的细节。EditorInfo.inputType和EditorInfo.TYPE_CLASS_MASK可以是很多值,包括 TYPE_CLASS_NUMBER,TYPE_CLASS_DATETIME,TYPE_CLASS_PHONE,TYPE_CLASS_TEXT。可 以通过android.text.InputType了解更多信息。

要把文本发送给应用有两种方式,你可以发送单一的按键事件,也可以在应用的文本框内编辑光标附近的文本。发送按键事件,可以简单构造 KeyEvent对象,并且调用InputConnection.sendKeyEvent(),或者更加方便一点,使用

【上篇】
【下篇】

抱歉!评论已关闭.