做企业云网站的企业邮箱,深圳市宝安区松岗街道邮政编码,专业建站推广企业,灵武住房和城乡建设厅网站阅读Android AOSP 12版本代码#xff0c;对输入法IME整体框架模块进行学习梳理#xff0c;内容包含输入法框架三部分IMM、IMMS、IMS的启动流程、点击弹出流程、显示/隐藏流程#xff0c;以及常见问题和调试技巧。 1. IME整体框架 IME整体分为三个部分#xf… 阅读Android AOSP 12版本代码对输入法IME整体框架模块进行学习梳理内容包含输入法框架三部分IMM、IMMS、IMS的启动流程、点击弹出流程、显示/隐藏流程以及常见问题和调试技巧。 1. IME整体框架 IME整体分为三个部分 1.1. 输入法客户端IMM 代码路径frameworks/base/core/java/android/view/inputmethod/ 主要指输入法框架的InputMethodManager, 每个app都一个实例, 用来和输入法控制端交互。运行在需要使用输入法的进程 1.2. 输入法管理端IMMS 代码路径frameworks/base/services/core/java/com/android/server/inputmethod/ 主要指输入法框架的InputMethodManagerService, 运行在system_server进程工作内容包含以下
输入法服务端的绑定输入法服务端与输入法客户端的绑定输入法启用/关闭输入法显示/隐藏切换输入法 1.3. 输入法服务端IMS 代码路径frameworks/base/core/java/android/inputmethodservice/ 主要指输入法框架的InputMethodService, 这是一个输入法服务, 真正实现输入法界面, 控制字符输入的地方。运行在输入法进程, 例如某狗输入法进程 1.4. 子模块交互流程图
输入法的整体交互过程如下:
IMM利用IInputMethodManager请求IMMSIMMS绑定输入法服务InputMethodService, 得到IInputMethodIMMS请求IInputMethod创建交互IInputMethodSessionIMMS通过IInputMethodClient告知IMM IInputMethodSessionIMM和IMS通过IInputMethodSession和IInputContext交互 2. IME初始化启动流程
2.1. IME客户端IMM初始化流程 涉及代码文件路径 frameworks/base/core/java/android/view/ViewRootImpl.java frameworks/base/core/java/android/view/WindowManagerGlobal.java frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java frameworks/base/core/java/com/android/internal/view/IInputMethodClient.aidl frameworks/base/core/java/com/android/internal/view/IInputContext.aidl frameworks/base/core/java/com/android/internal/view/IInputMethodManager.aidl frameworks/base/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java 2.1.1. 函数流程梳理 # 每次新增窗口window时都会实例化ViewRootImpl而ViewRootImpl在获取IWindowSession时会检查输入法是否已经初始化
ViewRootImpl.java -- 初始化构造函数调用WindowManagerGlobal.getWindowSession()--- WindowManagerGlobal.java -- getWindowSession()调用InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary() 实例化全局调用InputMethodManager即初始化IMM--- InputMethodManager.java -- ensureDefaultInstanceForDefaultDisplayIfNecessary()调用forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper())入参默认displayID和looper# 此处也说明对于APP层IMM有且只有一个实例每次创建ViewRootImpl都会检查IMM是否实例化完成---》 调用forContextInternal函数先从缓存Map中查询是否有IMM实例如果没有则创建IMM实例并添加到Map中---》 调用createInstance创建实例然后在三目运算中默认固定调用createRealInstance(displayId, looper)---》 调用createRealInstance函数 1获取输入法服务service即Context.INPUT_METHOD_SERVICEservice是AIDL接口文件IInputMethodManager.aidl2new InputMethodManager(service, displayId, looper)创建实例---》 InputMethodManager构造函数---》 new IInputConnectionWrapper 创建虚拟的输入法上下文主要用于监听输入法服务的激活状态接受输入事件# 添加IMM实例到输入法service服务中# 此处两个入参都是AIDL接口类型的对象# 1IInputMethodClient.aidl输入法客户端, 主要用于报告输入法当前的状态, 让APP应用端的IMM做出相应的处理# 2IInputContext.aidl输入法上下文, 主要用于操作字符输入操作, 让当前接收字符的view进行处理3调用service.addClient(imm.mClient //[AIDL对象即IInputMethodClient], imm.mIInputContext//[AIDL对象IInputContext], displayId)--- IInputMethodManager.aidl -- 调用addClient跨进程通信到IMMS--- 服务端InputMethodManagerService.java extends IInputMethodManager.Stub -- 调用addClient函数创建ClientState对象---》 调用内部静态类ClientState的构造函数保存client相关状态属性综上代码流程梳理可以看出
对于每个APP应用IMM有且只有一个实例并且每次创建ViewRootImpl时都会检查IMM是否已经实例化成功实例化IMM对象时会涉及到两个AIDL接口文件一个用于应用端IMM处理输入法当前状态一个用于输入法上下文创建一个虚拟的InputContext代表输入空间用于监听输入法激活状态实例化过程中会有个displayid用于多屏幕显示通常情况下默认是default display0实例化最后会通过AIDL的addClient接口函数将IMM添加到IMMS中如此IMM实例化完成 2.1.2. 代码详细说明 //ViewRootImpl.javapublic ViewRootImpl(Context context, Display display) {this(context, display, WindowManagerGlobal.getWindowSession(),false /* useSfChoreographer */);}//WindowManagerGlobal.javapublic static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {if (sWindowSession null) {try {//调用该函数初始化IMMInputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();......} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowSession;}}//InputMethodManager.javapublic static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {//默认default displayforContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());}private static InputMethodManager forContextInternal(int displayId, Looper looper) {final boolean isDefaultDisplay displayId Display.DEFAULT_DISPLAY;synchronized (sLock) {//从缓存Map中查找是否由default display的IMM实例InputMethodManager instance sInstanceMap.get(displayId);//如果存在实例则直接返回if (instance ! null) {return instance;}//初始化创建实例instance createInstance(displayId, looper);//如果是用于default display使用则存储到sInstance中作为全局单例实例if (sInstance null isDefaultDisplay) {sInstance instance;}//将IMM实例保存到Map中sInstanceMap.put(displayId, instance);return instance;}}private static InputMethodManager createInstance(int displayId, Looper looper) {//isInEditMode固定返回false直接调用createRealInstancereturn isInEditMode() ? createStubInstance(displayId, looper): createRealInstance(displayId, looper);}private static InputMethodManager createRealInstance(int displayId, Looper looper) {//IInputMethodManager是AIDL接口文件用于跨进程通信到IMMSInputMethodManagerServicefinal IInputMethodManager service;try {//获取serviceservice IInputMethodManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));} catch (ServiceNotFoundException e) {throw new IllegalStateException(e);}//创建IMM实例final InputMethodManager imm new InputMethodManager(service, displayId, looper);//将PID/UID和每个IME客户端关联然后作为跨进程服务端IPC使用梳理//如果作为同进程内调用梳理则需要确保Binder.getCalling{Pid, Uid}()返回Process.my{Pid, Uid}()//无论哪种情况都要调用Binder的{clear, restore}CallingIdentity()函数对跨进程没有影响对同进程可以满足需求实现final long identity Binder.clearCallingIdentity();try {// 添加 IMM 实例到输入法服务// imm.mClient 是一个aidl对象, mClient即new IInputMethodClient.Stub()AIDL接口// imm.mIInputContext 是一个aidl对象, IInputContextAIDL接口service.addClient(imm.mClient, imm.mIInputContext, displayId);} catch (RemoteException e) {e.rethrowFromSystemServer();} finally {Binder.restoreCallingIdentity(identity);}return imm;}//InputMethodManagerService.java//由每个APP应用进程调用作为输入法开始与交互的准备Overridepublic void addClient(IInputMethodClient client, IInputContext inputContext,int selfReportedDisplayId) {//获取调用的uid和pid即InputMethodManager实际运行所在的UID/PID//两种情况下调用此方法//1.IMM正在另一个进程中实例化//2.IMM正在同一个进程中实例化final int callerUid Binder.getCallingUid();final int callerPid Binder.getCallingPid();synchronized (mMethodMap) {// TODO: Optimize this linear search.final int numClients mClients.size();for (int i 0; i numClients; i) {final ClientState state mClients.valueAt(i);if (state.uid callerUid state.pid callerPid state.selfReportedDisplayId selfReportedDisplayId) {throw new SecurityException(uid callerUid /pid callerPid /displayId selfReportedDisplayId is already registered.);}}//利用IBinder.deathRecipient监听client存活状态//如果client的Binder死亡则将Client从缓存Map中移除final ClientDeathRecipient deathRecipient new ClientDeathRecipient(this, client);try {client.asBinder().linkToDeath(deathRecipient, 0);} catch (RemoteException e) {throw new IllegalStateException(e);}//此处不验证displayID后续每当客户端需要与指定的交互时就需要检查displayID//此处创建ClientState对象将client和inputContext缓存进去然后将该对象保存到缓存Map mClients中mClients.put(client.asBinder(), new ClientState(client, inputContext, callerUid,callerPid, selfReportedDisplayId, deathRecipient));}}2.1.3. IMM初始化序列图
2.2. IME管理端IMMS初始化流程 IMMS运行在system server进程中属于系统服务的一部分用于控制输入法的显示/隐藏、切换、绑定等操作。 涉及代码文件路径 frameworks/base/services/java/com/android/server/SystemServer.java frameworks/base/services/core/java/com/android/server/SystemServiceManager.java frameworks/base/core/java/android/os/SystemService.java frameworks/base/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java frameworks/base/packages/SettingsProvider/res/values/defaults.xml 2.2.1. 初始化函数流程梳理 # 我们从systemserver的startOtherServices函数开始梳理
# 此处需要注意因为我梳理的是IMMS而Google还提供了一个MultiClientInputMethodManagerService多客户端输入法服务进程此处不梳理
# PS从InputMethodManagerService代码文件中可以看到Lifecycle是里面的一个内部类继承systemservice
SystemServer.java -- startOtherServices然后通过SystemServiceManager的startService启动IMMS传入class nameInputMethodManagerService.Lifecycle.class--- SystemServiceManager.java -- startService有好几个重载的方法说明下1第一个startService方法入参className即InputMethodManagerService.Lifecycle.class将其作为入参调用loadClassFromLoader2loadClassFromLoader会通过反射方法得到具体的Class类返回ClassSystemService类型的服务类即继承SystemService的Lifecycle3调用第二个startService方法入参即serviceClass4先通过SystemService.class.isAssignableFrom(serviceClass)判断该类是否是SysteService的子类5然后通过反射构造类的实例serviceconstructor.newInstance(mContext)即实例化Lifecycle类重点6调用第三个startService方法入参该Lifecycle对象2先将该service添加到mServices列表中然后调用SystemService.java的onStart函数--- InputMethodManagerService.java -- 通过上面的流程看到此处会先调用Lifecycle类的构造函数然后调用onStart函数1构造函数会创建IMMS实例即InputMethodManagerService mServicenew InputMethodManagerService(context)2onStart函数会将该mService通过publishBinderService方法发布到系统服务中以便其他进行可以进行Binder获取到即添加到dev/binder域管理# 主要讲述IMMS对象被创建从构造函数梳理---》 调用构造函数主要用于注册一些监听事件, 获取必须的系统服务, UI相关的组件等PS
SystemService启动输入法服务时会有个判断启动IMMS还是MCIMMS。MULTI_CLIENT_IME_ENABLED即persist.debug.multi_client_ime或ro.sys.multi_client_ime开启启动MultiClientInputMethodManagerService服务否则启动InputMethodManagerService服务关于MultiClientInputMethodManagerService就是多会话输入法支持每屏幕焦点是启用此功能的前提。如果不支持则无法启用此功能。由于安全限制每屏幕焦点限制规定只有一小部分设备支持此功能。详细参考Google官方文档和源码 2.2.2. systemRunning函数流程梳理 # 我们从systemserver的startOtherServices函数开始梳理
# startBootPhase在服务startservice后执行该函数将service分段处理
# 例如此处IMMS在SystemService.PHASE_WAIT_FOR_SENSOR_SERVICE200和SystemService.PHASE_LOCK_SETTINGS_READY480之间
SystemServer.java -- startOtherServices然后通过SystemServiceManager的startBootPhase--- SystemServiceManager.java -- startBootPhase遍历两个分段之间的服务然后调用对应service的onBootPhase--- InputMethodManagerService.java -- 调用Lifecycle类的onBootPhase函数然后调用InputMethodManagerService的systemRunning函数主要内容1MyPackageMonitor内部类register注册监听安装包的变化包含安装卸载更新等2SettingsObserver注册监听当前用户的各种输入法相关的settingprovider变化例如默认输入法输入法列表输入法语言等3getSelectedInputMethod获取用户设置的输入法default_input_method此处是查询settings数据库的默认输入法frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java4buildInputMethodListLocked如果没有默认输入法则入参false该函数内容如下---》 查询输入法服务信息然后将信息储存到mMethodListmMethodMapmMyPackageMonitor中---》 调用chooseNewDefaultIMELocked选择一个新的输入法---》 updateInputMethodsFromSettingsLocked遍历所有输入法如果输入法存在被禁用的组件则重新启用调用setInputMethodLocked方法完成对输入法设置和输入法发生变化的广播ACTION_INPUT_METHOD_CHANGED的发送该函数中调用setInputMethodLockedPS:一般我们修改默认输入法packages/SettingsProvider/res/values/defaults.xml 数据库配置添加def_input_method和def_enable_input_methods然后frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java对应添加loadStringSetting加载引用DEFAULT_INPUT_METHOD和ENABLED_INPUT_METHODS 2.2.3. 代码详细说明 //SystemServer.java
private void startOtherServices(NonNull TimingsTraceAndSlog t) {......// Bring up services needed for UI.if (mFactoryTestMode ! FactoryTest.FACTORY_TEST_LOW_LEVEL) {t.traceBegin(StartInputMethodManagerLifecycle);if (InputMethodSystemProperty.MULTI_CLIENT_IME_ENABLED) {mSystemServiceManager.startService(MultiClientInputMethodManagerService.Lifecycle.class);} else {//启动IMMS服务mSystemServiceManager.startService(InputMethodManagerService.Lifecycle.class);}t.traceEnd();......}...
}//SystemServiceManager.java//第一个startService函数public SystemService startService(String className) {//调用loadClassFromLoaderfinal ClassSystemService serviceClass loadClassFromLoader(className,this.getClass().getClassLoader());return startService(serviceClass);}private static ClassSystemService loadClassFromLoader(String className,ClassLoader classLoader) {try {//通过反射方法得到具体的Class类返回ClassSystemService类型的服务类即继承SystemService的Lifecyclereturn (ClassSystemService) Class.forName(className, true, classLoader);} catch (ClassNotFoundException ex) {.......}}//第二个startService函数public T extends SystemService T startService(ClassT serviceClass) {try {final String name serviceClass.getName();Slog.i(TAG, Starting name);Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, StartService name);// 判断该class该类是否是SysteService的子类if (!SystemService.class.isAssignableFrom(serviceClass)) {throw new RuntimeException(Failed to create name : service must extend SystemService.class.getName());}final T service;try {//通过反射构造类的实例即实例化Lifecycle类ConstructorT constructor serviceClass.getConstructor(Context.class);//newInstance实例化service constructor.newInstance(mContext);} catch (InstantiationException ex) {throw new RuntimeException(Failed to create service name : service could not be instantiated, ex);} ............//调用第三个startServicestartService(service);return service;} finally {Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);}}//第三个startService函数public void startService(NonNull final SystemService service) {// Register it.将service注册到mServices列表中mServices.add(service);// Start it.long time SystemClock.elapsedRealtime();try {//调用该service的onStart函数service.onStart();} catch (RuntimeException ex) {throw new RuntimeException(Failed to start service service.getClass().getName() : onStart threw an exception, ex);}warnIfTooLong(SystemClock.elapsedRealtime() - time, service, onStart);}//InputMethodManagerService.javapublic static final class Lifecycle extends SystemService {private InputMethodManagerService mService;//实例化时调用构造函数public Lifecycle(Context context) {super(context);//创建InputMethodManagerService IMMS对象然后调用IMMS构造函数mService new InputMethodManagerService(context);}//在startService中调用到此处Overridepublic void onStart() {//将IMMS service添加到LocalServicesLocalServices.addService(InputMethodManagerInternal.class,new LocalServiceImpl(mService));//发布到系统服务中以便其他进行可以进行Binder获取到即添加到dev/binder域管理publishBinderService(Context.INPUT_METHOD_SERVICE, mService, false /*allowIsolated*/,DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);}.......}public class InputMethodManagerService extends IInputMethodManager.Stubimplements ServiceConnection, Handler.Callback {....//IMMS构造函数public InputMethodManagerService(Context context) {mIPackageManager AppGlobals.getPackageManager();mContext context;mRes context.getResources();mHandler new Handler(this);// Note: SettingsObserver doesnt register observers in its constructor.// SettingsObserver类型用于监听来自设置的输入法配置, 比如默认输入法, 启用的输入法, 选择的输入法等mSettingsObserver new SettingsObserver(mHandler);mIWindowManager IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));mWindowManagerInternal LocalServices.getService(WindowManagerInternal.class);mPackageManagerInternal LocalServices.getService(PackageManagerInternal.class);mInputManagerInternal LocalServices.getService(InputManagerInternal.class);mImeDisplayValidator displayId - mWindowManagerInternal.getDisplayImePolicy(displayId);.....// 状态栏输入法图标名称, 会根据这个名称设置输入法的图标显示mSlotIme mContext.getString(com.android.internal.R.string.status_bar_ime);mIsLowRam ActivityManager.isLowRamDeviceStatic();// 切换输入法时的通知Bundle extras new Bundle();extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);.....//获取UIDint userId 0;try {userId ActivityManager.getService().getCurrentUser().id;} catch (RemoteException e) {Slog.w(TAG, Couldnt get current user ID; guessing its 0, e);}// 最近切换的UIDmLastSwitchUserId userId;//应在buildInputMethodListLocked之前创建mSettings//类型InputMethodSettings输入法设置对象mSettings new InputMethodSettings(mRes, context.getContentResolver(), mMethodMap, userId, !mSystemReady);updateCurrentProfileIds();AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);mSwitchingController InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);mMenuController new InputMethodMenuController(this);}......}IMMS.java中几个重要的变量
String mCurMethodId系统当前默认的输入法id, 可能为空, 与Settings.Secure#DEFAULT_INPUT_METHOD值保持一致在setInputMethodLocked中赋值String mCurId当前已经绑定的输入法id, 如果没有输入法绑定上的话, 值为nullClientState mCurClient用于当前激活的IME, 只有持有这个令牌的IME才被系统认可IInputMethod mCurMethod当前已经绑定的输入法接口, 如果为null, 说明没有任何输入法连接上 2.2.4. 序列图
2.3. IME服务端IMS初始化流程 IMS运行在输入法进程, 是一种特殊的输入法后台服务继承结构为:InputMethodService extends AbstractInputMethodServiceService 输入法服务本质上是一个服务, 使用时需要IMMS通过bindService的方式绑定。 初始化过程在Service的onCreate方法中, 绑定方法在onBind方法中。 涉及代码文件路径 frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java frameworks/base/core/java/com/android/internal/view/IInputMethodManager.aidl frameworks/base/core/java/android/inputmethodservice/InputMethodService.java packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java 2.3.1. 函数流程梳理 # 我们从InputMethodManager.java开始梳理
InputMethodManager.java -- startInput---》 startInputInner--- IInputMethodManager.aidl -- startInputOrWindowGainedFocus--- InputMethodManagerService.java -- startInputOrWindowGainedFocus---》 startInputOrWindowGainedFocusInternal---》 startInputOrWindowGainedFocusInternalLocked---》 startInputUncheckedLocked---》 调用bindCurrentInputMethodServiceLocked启动当前默认的输入法的服务启动在设置的输入法进程中---》 调用bindServiceAsUser例如AOSP提供的packages/inputmethods/LatinIME输入法可以在AndroidManifest.xml看到输入法service即此处的绑定的服务# 参考packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java---》 输入法APP中会调用InputMethodManager的setAdditionalInputMethodSubtypes然后调用到InputMethodManagerService.java对应函数---》 调用buildInputMethodListLocked检查当前默认的输入法(LatinIME)服务是否存在---》 调用setInputMethodEnabledLocked检查默认的LatinIME是否是可用的可用的输入法如果不可用则设置为可用即检查settingprovider数据库的enabled_input_methods信息# 输入法应用会继承InputMethodService比如packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/LatinIME.java
# 因此会实现InputMethodService的onCreate函数# 从onTouchEvent开始viewClicked流程
1 viewClicked
2 checkFocus
3 startInputInner
4 startInputOrWindowGainedFocus
5 startInputLocked
6 startInputUncheckedLocked
7 attachNewInputLocked3. 输入法弹出流程
3.1. 点击弹出输入法流程 # 点击界面输入框应用然后弹出输入法在点击onTouchEvent事件后
# 一般应用会继承TextView
frameworks/base/core/java/android/widget/TextView.java -- onTouchEvent---》 viewClicked(imm) 入参InputMethodManager对象如果IMM对象不为空则会调用IMM的同名函数# 此处会判断服务的view是否相同当两次点击在同一个输入框时两者相同否则不同
# 此处以首次点击某个输入框为例
---》 InputMethodManager.java -- 调用viewClicked(View view)---》 调用checkFocus()检查焦点然后调用ImeFocusController的同名函数 controller.checkFocus(false /* forceNewFocus */, true /* startInput */)# 具体调用代码immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */, 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */)# 入参说明# -startInputReasonSTART_INPUT_REASON_CHECK_FOCUS标明本次调用的目的# -windowGainingFocus:会影响IMMS中startInputOrWindowGainedFocus的调用逻辑# -controlFlagssoftInputModewindowFlags 0---》 ImeFocusController.java -- checkFocus创建获取接口类InputMethodManagerDelegate对象其实就是IMM.java的内部类DelegateImpl实现该接口类对象然后调用该类的startInput函数---》 InputMethodManager.java -- 调用类DelegateImpl的startInput函数---》 调用startInputInner函数1---》 创建new IInputConnectionWrapper对象2---》 调用IInputMethodManager.aidl的startInputOrWindowGainedFocus函数---》 InputMethodManagerService.java -- 跨进程调用startInputOrWindowGainedFocus---》 调用startInputOrWindowGainedFocusInternal---》 调用startInputOrWindowGainedFocusInternalLocked---》 调用startInputUncheckedLocked# NOTE此处返回结果给上层InputMethodManager.java的startInputOrWindowGainedFocus函数---》 调用attachNewInputLocked函数发送MSG_START_INPUT消息触发handleMessage调用startInput ---该函数返回对象InputBindResult输入法数据结果信息---代码说明
# session是SessionState静态类method是IInputMethod对象
session.method.startInput(startInputToken, inputContext, missingMethods, editorInfo, restarting);---》 IInputMethod.aidl -- startInput
---》 IInputMethodWrapper.java实现IInputMethod -- startInput发送DO_START_INPUT消息然后在executeMessage中调用InputMethod的dispatchStartInputWithToken# 抽象类AbstractInputMethodImpl实现了InputMethod接口类
# 静态类InputMethodSessionCallbackWrapper实现了InputMethod.SessionCallback类主要实现sessionCreated建立session连接
---》 InputMethod.java 接口类InputMethod方法dispatchStartInputWithToken然后调用startInput如果是重启则调用restartInput# InputMethodService.java中的类InputMethodImpl继承了抽象类AbstractInputMethodImpl
---》 InputMethodService.java -- 调用类InputMethodImpl的startInput方法---》 调用doStartInput# 调用以通知输入方法文本输入已在编辑器中开始。您应该使用此回调来初始化输入的状态以匹配给定给它的编辑器的状态# 此处由具体输入法APP继承InputMethodService类然后来实现# NOTE比如packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/LatinIME.java# 入参attibute开始输入的编辑器的属性# 入参restarting如果输入在同一编辑器中重新启动例如因为应用程序更改了编辑器中的文本则设置为true。否则将为false表示这是一个带有编辑的新会话---》 调用onStartInput(EditorInfo attribute, boolean restarting)3.1.1. 序列图
3.2. 输入法显示流程隐藏 梳理WMS部分流程。 可参考Android输入法弹出流程 # 点击界面输入框应用然后弹出输入法在点击onTouchEvent事件后
# 一般应用会继承TextView
frameworks/base/core/java/android/widget/TextView.java -- onTouchEvent---》 创建InputMethodManager对象调用showSoftInput先执行上面点击弹出输入法viewClicked的流程紧接着执行此处流程----》 InputMethodManager.java --- showSoftInputhideSoftInput隐藏输入法----》 IInputMethodManager.aidl 跨进程showSoftInput----》IMMS.java extends IInputMethodManager.Stub 调用showSoftInput---》 showCurrentInputLocked 发送MSG_SHOW_SOFT_INPUT消息然后在handleMessage调用IInputMethod的showSoftInput---》 IInputMethod.aidl 跨进程showSoftInput---》 IInputMethodWrapper extends IInputMethod.Stub 调用showSoftInput发送DO_SHOW_SOFT_INPUT消息然后在executeMessage调用InputMethod的showSoftInputWithToken函数# 抽象类AbstractInputMethodImpl实现了InputMethod接口类
# 静态类InputMethodSessionCallbackWrapper实现了InputMethod.SessionCallback类主要实现sessionCreated建立session连接
---》 InputMethod.java 接口类InputMethod方法showSoftInputWithToke调用showSoftInput# InputMethodService.java中的类InputMethodImpl继承了抽象类AbstractInputMethodImpl
---》 InputMethodService.java -- 调用类InputMethodImpl的方法showSoftInput显示输入法hideSoftInput隐藏输入法1----》 dispatchOnShowInputRequested 调用onShowInputRequested2----》 showWindow()---》 调用startViews(prepareWindow(showInput))# prepareWindow函数调用1---》 initialize()2---》 updateFullscreenMode()3---》 updateInputViewShown(() 先调用onCreateInputView然后调用setInputView4---》 如果mViewsCreated未创建即为false调用initialize()然后调用onCreateCandidatesView()再调用setCandidatesView(v)# startViews函数1---》 onStartInputView2---》 onStartCandidatesView3---》 startExtractingText3----》 applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible) 如果条件满足申请显示输入法-----》 InputMethodManagerService.java --- applyImeVisibilityAsync通过Binder机制调用IMMS类的applyImeVisibility函数使申请IME可见# 隐藏输入法函数是hideIme
-----》 WMS.java --- showImePostLayout(IBinder imeTargetWindowToken)
# NOTE该问题代码修改的地方在Android 13上已修复
(1)-----》 windowState.java --- InsetsControlTarget controlTarget getImeControlTarget 此处会获取displaycontent还有他的parentWindow() 》 调用 DisplayContent.java --- InsetsControlTarget getImeHostOrFallback(WindowState target)
(2)-----》 ImeInsetsSourceProvider.java --- scheduleShowImePostLayout(controlTarget)该函数主要通过Target的值处理输入法显示逻辑如果显示则调用setImeShowing将mImeShowing全局变量设置为true然后外部通过isImeShowing()调用获取该值-----》 checkShowImePostLayout() 判断是否显示输入法有多个逻辑判断包含(1) mWin ! null 即windowtoken - imeTargetWindowToken(2) mWin.isDrawn()(3) !mWin.mGivenInsetsPending(4) mIsImeLayoutDrawn true(5) 调用isReadyToShowIme()函数判断两个target的内容目标窗口的target和请求的target----》 setImeShowing(true) 设置输入法可显示 和isImeShowing()成对用于外部获取3.2.1. 序列图
4. 输入法常见问题小结
4.1. 配置默认输入法
4.1.1. 查看已安装的输入法 adb shell ime list -a# 结果
com.android.inputmethod.latin/.CarLatinIME:# mId就是配置项def_input_methodmIdcom.android.inputmethod.latin/.CarLatinIME mSettingsActivityNamenull mIsVrOnlyfalse mSupportsSwitchingToNextInputMethodfalse mInlineSuggestionsEnabledfalse mSuppressesSpellCheckerfalse mShowInInputMethodPickertruemIsDefaultResId0x0Service:priority0 preferredOrder0 match0x108000 specificIndex-1 isDefaultfalseServiceInfo:# 输入法服务名namecom.android.inputmethod.latin.CarLatinIME# 输入法包名packageNamecom.android.inputmethod.latinlabelRes0x7f08000c nonLocalizedLabelnull icon0x0 banner0x0enabledtrue exportedtrue directBootAwaretrue# 权限permissionandroid.permission.BIND_INPUT_METHODflags0x0ApplicationInfo:packageNamecom.android.inputmethod.latinlabelRes0x7f08000c nonLocalizedLabelnull icon0x7f04000c banner0x0processNamecom.android.inputmethod.latintaskAffinitycom.android.inputmethod.latinuid10058 flags0x38c8be45 privateFlags0xa4400040 theme0x103013erequiresSmallestWidthDp0 compatibleWidthLimitDp0 largestWidthLimitDp0# APK目录sourceDir/system/app/CarLatinIME/CarLatinIME.apkseinfodefault:targetSdkVersion23seinfoUser:complete# data数据目录dataDir/data/user/0/com.android.inputmethod.latindeviceProtectedDataDir/data/user_de/0/com.android.inputmethod.latincredentialProtectedDataDir/data/user/0/com.android.inputmethod.latinsharedLibraryFiles[/system/framework/android.test.base.jar, /system/framework/android.hidl.manager-V1.0-java.jar, /system/framework/android.hidl.base-V1.0-java.jar, /system/framework/org.apache.http.legacy.jar]enabledtrue minSdkVersion32 targetSdkVersion23 versionCode32 targetSandboxVersion1supportsRtltruefullBackupContenttruecrossProfilefalseHiddenApiEnforcementPolicy0usesNonSdkApitrueallowsPlaybackCapturefalsenativeHeapZeroInitialized04.1.2. 切换输入法 adb shell ime set com.android.inputmethod.latin/.CarLatinIME4.1.3. Android代码配置新默认输入法
Android默认输入法的配置和两个关键词相关都保存在SettingsProvider或者settings_secure.xml里面
enabled_input_methods:表示配置的系统允许使用的输入法的id字符串中间以冒号分隔比如com.android.inputmethod.latin/.LatinIME:com.xxxx.inputmethod.remote/.RemoteIMEdefault_input_method:表示配置的系统默认使用的输入法
在InputMethodService启动时一方面会从packagemanager那边获取InputMethodService的应用信息另一方面也会settings数据库里面读取enabled_input_methods和default_input_method对应的输入法信息。
如果后者是空的则会把前者保存起来并enable并通过InputMethodUtils.getMostApplicableDefaultIME方法来获取最适合当前系统的输入法并设置为默认的输入法如果后者不是空的则会读取settings数据库的default_input_method信息如果是有效的输入法就会把它设置为默认的输入法如果不是有效的是空的则还是会通过getMostApplicableDefaultIME方法来获取最适合当前系统的输入法 //frameworks/base/core/java/android/provider/Settings.java/*** List of input methods that are currently enabled. This is a string* containing the IDs of all enabled input methods, each ID separated* by :.** Format like ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0* where imeId is ComponentName and subtype is int32.*/Readablepublic static final String ENABLED_INPUT_METHODS enabled_input_methods;/*** Setting to record the input method used by default, holding the ID* of the desired method.*/Readablepublic static final String DEFAULT_INPUT_METHOD default_input_method;配置修改方法 //frameworks/base/packages/SettingsProvider/res/values/defaults.xml
//包名输入法 ID服务名 可通过ime list -s查看!-- set default input method--string namedef_input_method translatablefalsecom.test.inputmethod.pinyin/.PinyinIME/stringstring nameenabled_input_methods translatablefalsecom.test.inputmethod.pinyin/.PinyinIME:com.android.inputmethod.pinyin/.PinyinIME/string//frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java loadStringSetting(stmt, Settings.Secure.DEFAULT_INPUT_METHOD,R.string.def_input_method);loadStringSetting(stmt, Settings.Secure.ENABLED_INPUT_METHODS,R.string.enabled_input_methods);其中配置项的字符串获取方式是
安装上apk在settings界面选中该输入法或者命令ime set然后命令行执行settings get secure def_input_method和settings get secure enabled_input_method通过ime list -s 查看输入法ID的信息然后修改此处配置项 4.2. Android 12输入法无法在虚拟屏显示
Android 12移除了Android 11上portalToDisplayId的部分代码导致Android 12上创建Virtual Display后无法在虚拟屏弹出输入法。回退代码如下
备注android-12.0.0_r3仍未移除可在AOSP源码上查看到
core/jni/android_hardware_input_InputWindowHandle.cpp添加portalToDisplayId通过JNI获取JVM层portalToDisplayId的值和FIELD_ID jfieldID portalToDisplayId; mInfo.portalToDisplayId env-GetIntField(obj,gInputWindowHandleClassInfo.portalToDisplayId); GET_FIELD_ID(gInputWindowHandleClassInfo.portalToDisplayId, clazz,portalToDisplayId, I);services/surfaceflinger/Layer.cpp在fillInputInfo函数中由于当前虚拟屏Touch实现的方案getLayerStack会返回0会覆盖掉WMS传递给SF的DisplayID的值。由于这里会更新InputHandleInfo传递给InputManager如果DisplayID的值不正确会影响InputManager内部关于Focus的计算逻辑 WindowInfo Layer::fillInputInfo(const spDisplayDevice display) {if (!hasInputInfo()) {mDrawingState.inputInfo.name getName();mDrawingState.inputInfo.ownerUid mOwnerUid;mDrawingState.inputInfo.ownerPid mOwnerPid;mDrawingState.inputInfo.inputFeatures WindowInfo::Feature::NO_INPUT_CHANNEL;mDrawingState.inputInfo.flags WindowInfo::Flag::NOT_TOUCH_MODAL;mDrawingState.inputInfo.displayId getLayerStack();}WindowInfo info mDrawingState.inputInfo;info.id sequence; if (info.displayId ADISPLAY_ID_NONE) {info.displayId getLayerStack();}......
}流程如下从SurfaceFlinger开始合成流程开始 frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp -- onMessageReceived开始触发合成--- onMessageInvalidate--- updateInputFlinger()更新InputFlinger--- updateInputWindowInfo()更新input窗口信息--- frameworks/native/services/surfaceflinger/Layer.cpp -- fillInputInfo(DisplayDevice display) 入参display信息// 代码内容
InputWindowInfo Layer::fillInputInfo(const spDisplayDevice display) {//绘制的inputinfo不为空mDrawingState.inputInfo.tokenif (!hasInputInfo()) {mDrawingState.inputInfo.name getName();mDrawingState.inputInfo.ownerUid mOwnerUid;mDrawingState.inputInfo.ownerPid mOwnerPid;mDrawingState.inputInfo.inputFeatures InputWindowInfo::Feature::NO_INPUT_CHANNEL;mDrawingState.inputInfo.flags InputWindowInfo::Flag::NOT_TOUCH_MODAL;mDrawingState.inputInfo.displayId getLayerStack();}InputWindowInfo info mDrawingState.inputInfo;info.id sequence;//此处注意输入窗口的display idif (info.displayId ADISPLAY_ID_NONE) {info.displayId getLayerStack();}// Transform that goes from logical(rotated) display to physical/unrotated display.// This is for when inputflinger operates in physical display-space.ui::Transform toPhysicalDisplay;if (display) {toPhysicalDisplay display-getTransform();info.displayWidth display-getWidth();info.displayHeight display-getHeight();}fillInputFrameInfo(info, toPhysicalDisplay);.....
}--- fillInputFrameInfo(info, toPhysicalDisplay)计算input窗口显示合成区域4.3. Android 12输入法无法显示在虚拟屏内部
修改方法 packages/SettingsProvider/res/values/defaults.xml和packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java开启强制桌面模式可以解决输入法显示在虚拟屏内部
通过settings get global force_desktop_mode_on_external_displays获取该值默认是null一般是通过开发者选项中进行开启/关闭手动命令设置设置桌面模式settings put global force_desktop_mode_on_external_displays 1 //defaults.xml!-- Initial value for the Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS --integer namedef_force_desktop_mode_on_external_displays1/integer//DatabaseHelper.java// set global value of DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYSloadIntegerSetting(stmt, Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,4.3.1. 开发者选项桌面模式 开发者选项中桌面模式的开关主要就是DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS值的控制 //packages/apps/Settings/src/com/android/settings/development/DesktopModePreferenceController.java
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
public class DesktopModePreferenceController extends DeveloperOptionsPreferenceControllerimplements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {.......Overridepublic boolean onPreferenceChange(Preference preference, Object newValue) {final boolean isEnabled (Boolean) newValue;Settings.Global.putInt(mContext.getContentResolver(),DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);return true;}Overridepublic void updateState(Preference preference) {final int mode Settings.Global.getInt(mContext.getContentResolver(),DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, SETTING_VALUE_OFF);((SwitchPreference) mPreference).setChecked(mode ! SETTING_VALUE_OFF);}Overrideprotected void onDeveloperOptionsSwitchDisabled() {super.onDeveloperOptionsSwitchDisabled();Settings.Global.putInt(mContext.getContentResolver(),DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, SETTING_VALUE_OFF);((SwitchPreference) mPreference).setChecked(false);}
}4.3.2. 桌面模式属性流程
4.3.2.1. 修改方法
在WMS.java中获取值并监听变化赋值给变量mForceDesktopModeOnExternalDisplays //frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;public class WindowManagerService extends IWindowManager.Stubimplements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {......//强制桌面模式标志//通过监听updateForceDesktopModeOnExternalDisplays()boolean mForceDesktopModeOnExternalDisplays;//构造函数时获取该值mForceDesktopModeOnExternalDisplays Settings.Global.getInt(resolver,DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) ! 0;//通过监听设置应用上开关的变化然后在onChange调用updateForceDesktopModeOnExternalDisplays()函数更新mForceDesktopModeOnExternalDisplays值private final Uri mForceDesktopModeOnExternalDisplaysUri Settings.Global.getUriFor(Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS);4.3.2.2. mForceDesktopModeOnExternalDisplays值流程
第一处 frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp -- setDisplayViewports输入管理服务的函数用来设置输入系统需要的显示器的显示视图信息
--- JNI函数getPointerDisplayId()调用InputManagerService的对应函数frameworks/base/services/core/java/com/android/server/input/InputManagerService.java -- getPointerDisplayId()--- frameworks/base/services/core/java/com/android/server/wm/InputManagerCallback.java -- getPointerDisplayId()如果桌面模式打开则displayidDEFAULT_DISPLAY否则从WMS对象的displaycontent中获取displayId第二处 DisplayContent.java -- 此处三个地方会调用到updateImeControlTarget函数
(1)setImeLayeringTargetInner(Nullable WindowState target)由computeImeTarget(boolean updateImeTarget)计算IME Target的函数调用
(2)updateImeInputAndControlTarget(WindowState target)由WindowState.java或者WMS.java中调用
(3)setInputMethodWindowLocked(WindowState win)由RootWindowContainer.java或者WMS.java调用--- updateImeControlTarget()
--- computeImeControlTarget()
--- getImeHostOrFallback(WindowState target)
--- getImePolicy()
--- forceDesktopMode()判断是否开启桌面模式 非defalut display 非private的Flag返回布尔值4.4. Android 12输入法无法在多个虚拟屏切换显示
4.4.1. 修改方法 参考Google官方文档-屏幕支持 overlay/packages/services/Car/car_product/overlay/frameworks/base/core/res/res/values/config.xml关闭多屏焦点为了解决输入法无法在多个虚拟屏切换显示的问题
该配置项阅读官方文档意思就是为了同时支持多个以单个屏幕为目标的输入源可以将Android配置为支持多个聚焦窗口每个屏幕最多支持一个。
由Android 10引入在Android 9即更低版本中系统中一次最多只有一个窗口具有焦点。
然而在多个Virtual Display虚拟屏中开启该配置项输入法只会将焦点聚焦在一个虚拟屏中无法切换虚拟屏点击弹出输入法所以关闭该配置项。 resources xmlns:xliffurn:oasis:names:tc:xliff:document:1.2 !-- disable multi display focus --!-- Whether the system enables per-display focus. If the system has the input method for eachdisplay, this value should be true. --bool nameconfig_perDisplayFocusEnabledfalse/bool
/resources4.4.2. 多屏焦点流程
InputDispatcher现在可以有多个聚焦窗口每个屏幕一个。如果某个输入事件特定于屏幕则该事件会被分派到相应屏幕中的聚焦窗口。否则它会被分派到聚焦屏幕即用户最近与之交互的屏幕中的聚焦窗口。参阅 InputDispatcher::setFocusedDisplay()。聚焦应用也会通过NativeInputManager::setFocusedApplication()在InputManagerService中分别更新在WindowManager中系统还会单独跟踪聚焦窗口。参阅DisplayContent#mCurrentFocus和DisplayContent#mFocusedApp以及各自的用途。相关的焦点跟踪和更新方法已从WindowManagerService移至DisplayContent
以下是WMS焦点更新时多屏焦点的流程 WindowManagerService.java构造函数中获取config_perDisplayFocusEnabled属性值将其赋值给全局变量mPerDisplayFocusEnabled用于控制此功能的可用性InsetsSourceProvider.java -- setWindow更新当前支持此源的窗口此处只关注IME输入法
--- updateControlForTarget更新Target
--- setClientVisible(boolean clientVisible)设置Client端可见发送LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED消息给WMS--- WMS.java -- handleMessage处理LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED消息调用layoutAndAssignWindowLayersIfNeeded函数--- DisplayContent.java -- layoutAndAssignWindowLayersIfNeeded() 每当对层次结构进行视觉更改如移动容器或调整容器大小时都可能调用此方法
--- updateFocusedWindowLocked更新焦点窗口此处会通过返回的newFocus和mCurrentFocus比较
--- findFocusedWindowIfNeeded(int topFocusedDisplayId) 判断多屏焦点属性mPerDisplayFocusEnabled的值如果开启则在该显示器上查询聚焦窗口否则返回NULL4.4.3. PS多屏异显
PS多屏异显物理屏的修改是将下面的属性改成false !-- When true, local displays that do not contain any of their own content will automaticallymirror the content of the default display. --bool nameconfig_localDisplaysMirrorContentfalse/bool4.5. Android 12输入法未显示在对应DisplayID上面
4.5.1. 修改方法
services/core/java/com/android/server/wm/WindowState.java修改getImeControlTarget的返回值Android 13上此处已修改。修复IME不显示在对应的display id上。
通过dumpsys window windows查看显示的display id是正常实际点击到的虚拟屏display id(未修改时会一直指向display id0:
imeLayeringTarget in display# 3 Window{d8d67ca u0 com.example.test/com.example.test.MainActivity} InsetsControlTarget getImeControlTarget() {final DisplayContent dc getDisplayContent();final WindowState parentWindow dc.getParentWindow(); // If targets display has a parent, IME is displayed in the parent display.// return dc.getImeHostOrFallback(parentWindow ! null ? parentWindow : this);// fix ime show on current virtual display// use dump window to see imeLayeringTarget in correct displayreturn dc.getImeHostOrFallback(this);}5. 调试技巧
5.1. adb shell ime # 列出所有输入法服务
adb shell ime list -a -s
# 设置输入法
adb shell ime set com.sohu.inputmethod.sogouoem/.SogouIME
# 启用输入法
adb shell ime enable com.sohu.inputmethod.sogouoem/.SogouIME
# 不启用输入法
adb shell ime disable com.sohu.inputmethod.sogouoem/.SogouIME
# 重置为默认输入法
adb shell ime reset
# 从设置获取默认输入法
adb shell settings get secure default_input_method5.2. Dump获取信息
5.2.1. 获取到输入法的各种信息 adb shell dumpsys input_method# 可以获取到输入法的各种信息, 并且可以过滤window信息
adb shell dumpsys input_method | grep -i window5.2.2. 获取输入法的窗口状态信息 # 获取输入法的窗口状态信息
adb shell dumpsys window | grep -i input5.2.3. 获取输入法的窗口层级信息 # 获取输入法的窗口层级信息
adb shell dumpsys SurfaceFlinger5.2.4. 获取当前存在几个isplay
通过dumpsys display查看当前有几个display adb shell dumpsys displayDisplay States: size3Display Id0Display StateONDisplay Brightness0.05Display SdrBrightness0.05Display Id2Display StateONDisplay Brightness0.0Display SdrBrightness0.0Display Id3Display StateONDisplay Brightness0.0Display SdrBrightness0.05.2.5. 通过adb命令将app启动在指定的屏幕 # 命令参考
adb shell am start -n com.android.demo/com.android.demo.MainActivity --display 1
adb shell am start -n com.android.demo/com.android.demo.MainActivity --user 0 --display 1# Android 12原生setting
adb shell
# 原生设置界面
am start -n com.android.car.settings/.Settings_Launcher_Homepage --display 2参数–display指定屏幕 display 0表示第一块屏幕 display 1表示第2块屏幕参数–user可以启动指定的用户在多用户下有效系统默认是–user 0 5.3. 应用使用输入法
5.3.1. 获取输入法打开的状态 //获取输入法打开的状态
public static boolean isShowing(Context context) {InputMethodManager imm (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);return imm.isActive();//isOpen若返回true则表示输入法打开
}5.3.2. 调用显示系统默认的输入法
方法一 InputMethodManager imm (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(m_receiverView /*接受软键盘输入的视图(View)*/, InputMethodManager.SHOW_FORCED /*提供当前操作的标记SHOW_FORCED表示强制显示*/);//view为接受软键盘输入的视图SHOW_FORCED表示强制显示
public static void showOrHide(Context context, View view) {InputMethodManager imm (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);//imm.showSoftInput(view, InputMethodManager.SHOW_FORCED); //SHOW_FORCED表示强制显示//imm.hideSoftInputFromWindow(view.getWindowToken(), 0); //强制隐藏键盘
}方法二: //如果输入法在窗口上已经显示则隐藏反之则显示
public static void showOrHide(Context context) {InputMethodManager imm (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);//这个方法可以实现输入法在窗口上切换显示如果输入法在窗口上已经显示则隐藏如果隐藏则显示输入法到窗口上//该方法在IMM中最终会调用hideSoftInputFromWindow或者showSoftInputimm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
}5.3.3. 调用隐藏系统默认的输入法 //调用隐藏系统默认的输入法
public static void showOrHide(Context context, Activity activity) {//activity为当前activity((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);
}