输入法框架 即 Input Method Framework 简称IMF

几个简写

  • IMMS:InputMethodManagerService
  • IMS : InputMethodService
  • IMM: InputMethodManager
  • WMS: WindowManagerService
  • IIMM: IInputMethodManager
  • AbsIMS: AbstracInputMethodService
  • IC: InputConnection

包括三个部分

  1. IMM (input method manager) 输入法服务,管理各部分的交互,是一个客户端的 API ,存在于各个应用程序的 context 中,用来沟通管理所有进程间交互的全局系统服务,可以通过Context.getSystemService(Context.INPUT_METHOD_SERVICE)来获取,
  2. IME (input method) 输入法应用,实现一个允许用户生成文本的独立交互模块。系统绑定一个当前的输入法。使其创建和生成,决定输入法何时隐藏或者显示它的 UI 。同一时间只能有一个 IME 运行。

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

IMM

每个程序有一个 IMM 实例,这个是程序和 IMMS 通信的接口,它里面包含一个 IIMM 对象 mService ,这个 mService 就是 IMMS 的代理,打开关闭输入法这些操作就是由 IMM 中的某些方法调用 IIMM 中相应的方法来实现的.比如:

  • mService.getInputMethodList() 获取输入法列表。
  • mService.updateStatusIcon(imeToken, packageName , iconId) 更新输入法图标,即屏幕上方状态栏中的输入法图标
  • mService.finishInput(mClient) 隐藏当前输入法。而不说关闭输入法,是因为输入法服务启动起来以后,只有在系统关闭或者切换输入法时才会关闭。
  • mService.showSoftInput(mClient, flags , resultReceiver)打开当前输入法。

IMMS

是一个系统服务。整个系统当中,一切与输入法有关的地方的总控制中心,负责管理系统的所有输入法,包括输入法Service(即IMS)加载以及切换,它通过管理下面三个模块来实现系统的输入法框架

  • WMS 负责显示输入法,接收用户事件
  • IMS 输入法内部逻辑,键盘布局,选词等,最终把选出的字符通过 commitTetxt 提交出来
  • InputManager 由 UI 控件(View, TextView , EditText 等)调用,用来操作输入法,比如打开,关闭,切换输入法等

IME

IME 是一个包含特殊 IME 服务的应用程序。该程序有以下特点

  1. 必须有一个服务是继承IMS
  2. 应用的清单文件必须声明输入法服务,
    • 服务中必须的添加权限BIND_INPUT_METHOD
    • 提供一个 intent filter 来匹配<action android:name="android.view.InputMethod"/>
    • 提供定义了 IME 特征的元数据(metadata)。
    • 定义一个设置页面来提供用户修改 IME 配置的接口,它可以被系统设置所启动。
<!--所有输入法 Service 必须添加权限 android.permission.BIND_INPUT_METHOD-->
<service
    android:name=".LatinIME"
    android:label="@string/ime_name"
    android:permission="android.permission.BIND_INPUT_METHOD">
    <!--匹配android.view.InputMethod 的intent filter -->
    <intent-filter>
        <action android:name="android.view.InputMethod"/>
    </intent-filter>
    <!--定义 IME 特征的元数据,也就是在“语言与键盘”设置界面可以看到我们编写的输入法,
    其中android:resource属性制定了一个输入法资源 ID 。
    这个资源文件(method.xml)在res\xml目录中-->
    <meta-data
        android:name="android.view.im"
        android:resource="@xml/method"/>
</service>
<!--定义一个设置页面来提供用户修改 IME 配置的接口-->
<activity
    android:name="com.android.inputmethod.pinyin.SettingsActivity"
    android:label="@string/ime_settings_activity_name">
</activity>

<!--res\xml\method.xml-->
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
      android:settingsActivity="com.android.inputmethod.pinyin.SettingsActivity"
      android:isDefault="@bool/im_is_default"/>
<!--<input-method>标签的android:settingActivity属性可以指定输入法设置窗口。-->

IMS

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

IMS 主要由一下几个组件构成,其中包括输入法的相关 UI 和文字输出

  1. 软键盘视图(SoftInput View) 这是软键盘的输入法区域,主要完成在触摸屏下和用户的交互输入,
    • onCreatInputView() 用来进行软键盘实例化
    • onEvaluateInputViewShown() 决定是否显示 7K 键盘视图
    • updateInputViewShown() 当前状态改变的时候,重新决策是否显示软键盘视图
  2. 候选字视图(Candidates View) Candidates View 也是输入法中一个相当重要的组件,当用户输入法字符的时候,显示相关的列表,停止输入的时候,就会自动消失
    • onCreatCandidatesView() 实例化自己的输入法,和软键盘视图不同的是,候选词视图对整个 UI 布局不会产生影响
    • setCandidatesViewShow()用来设置是否显示候选词视图
  3. 输出字符 字符的输出是 IMS 最核心的功能,输入法通过 InputConncetion 从 IMF 中获得字符的输出,并且通过不同的编辑器类型来获取相应的支持,通过 onFinishInput() 和 onStartInput() 方法进行输入目标的切换

另外:

  • onInitializeInterface()用于 InputMethodService 在执行的过程中配置的改变
  • onBindInput() 切换一个新的输入通道
  • onStartInput() 处理一个新的输入

输入法是以 Service 的方式运行的 输入法同一时间只能服务一个程序,只有最顶层的可见的程序才能接收到输入法的输入数据

输入法的流程

程序获得焦点时,就会通过 IMM 向 IMMS 通知自己获得焦点并且请求将绑定自己到当前输入法上,同时当程序某个需要输入法的View(比如EditText)获得焦点时就会通过 IMM 向 IMMS 请求显示输入法,而这时 IMMS 收到请求后,会将请求的View(比如 EditText)的数据通信 IC(这个是由具体 View 自己创建,比如 EditText 创建的 IC 是EditableInputConnection) 发送给当前的输入法,并且请求显示输入法,输入法收到请求后,就会显示自己的 UI dialog ,同时保存目标 view 的数据结构,当用户实现输入后,直接通过 view 的数据通信接口将字符传递到对应的 View 中(IC#commitText())

IMM 创建

我们知道,在每一个程序中,都可以通过下面的代码获取到一个 IMM 对象 InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE) 这个 IMM 对象,就是和 IMMS 通信的接口。

//Activity.java
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
  if (getBaseContext() == null) {
    throw new IllegalStateException("System services not available to Activities before onCreate()");
  }

  if (WINDOW_SERVICE.equals(name)) {
    return mWindowManager;
  } else if (SEARCH_SERVICE.equals(name)) {
    ensureSearchManager();
      return mSearchManager;
  }
  return super.getSystemService(name);
}

因为 Context.INPUT_METHOD_SERVICE = “input_method”,所以执行到了 super.getSystemService()中,即 Context 的 getSystemService() 中,而 Context 是一个抽象类,真正的实现是 ContextImpl 。

//ContextImpl.java
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP = new HashMap<String, ServiceFetcher>();

@Override
public Object getSystemService(String name) {
    ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
    return fetcher == null ? null : fetcher.getService(this);
}

SYSTEM_SERVICE_MAP 是一个 HashMap ,那么我们就看看 INPUT_METHOD_SERVICE 这个 key 什么时候 put 进去吧,然后我们就发现了,在 ContextImpl 的静态代码块中,有这样一段代码

static {
  ...
  registerService(INPUT_METHOD_SERVICE, new StaticServiceFetcher() {
          public Object createStaticService() {
              return InputMethodManager.getInstance();
          }
  });
  ...
}  

在 registerService() 中,把 key 和 value 添加到 SYSTEM_SERVICE_MAP 中了,

private static void registerService(String serviceName, ServiceFetcher fetcher) {
      if (!(fetcher instanceof StaticServiceFetcher)) {
          fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
      }
      SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}

但是还有一个问题,就是 StaticServiceFetcher 是干嘛的啊

StaticServiceFetcher

Fetcher 是取样器,获取器的意思,那么整体翻译,就是静态服务获取器,有点像是工厂模式的意思,是不是源码就知道了,

abstract static class StaticServiceFetcher extends ServiceFetcher {
    private Object mCachedInstance;

    @Override
    public final Object getService(ContextImpl unused) {
        synchronized (StaticServiceFetcher.this) {
            Object service = mCachedInstance;
            if (service != null) {
                return service;
            }
            return mCachedInstance = createStaticService();
        }
    }

    public abstract Object createStaticService();
}

好像是,我看着像是工厂模式,但是不确定,不管了,看代码也能看明白是干嘛的,就是生成一个 ServiceFetcher 对象, 接下来再看看这段代码就明白了

registerService(INPUT_METHOD_SERVICE, new StaticServiceFetcher() {
        public Object createStaticService() {
          return InputMethodManager.getInstance();
       }
  });

根据 InputMethodManager.getInstance()得到一个 StaticServiceFetcher 对象,然后添加到 SYSTEM_SERVICE_MAP 集合中。 接下来看看 InputMethodManager.getInstance()代码

//InputMethodManager
public static InputMethodManager getInstance() {
  synchronized (InputMethodManager.class) {
    if (sInstance == null) {
      // b实际上是一个 IMMS 对象
      IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
      //service 就是一个 IMMS 的代理
      IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
      sInstance = new InputMethodManager(service, Looper.getMainLooper());
    }
    return sInstance;
  }
}

ServiceManager.getService(Context.INPUT_METHOD_SERVICE) 得到的是一个 IMMS 对象,这个为啥呢,在哪里设置呢,起始这个要从另外一个入口说起,这就是 SystemServer.java ,这可是一个 NB 的类,简单来说,在开机启动的时候,就会这个类就会执行, 一些系统的服务就会被开启。这其中就包括输入法管理的服务,即IMMS

启动IMMS

那输入法服务是什么时候被绑定上去的呢 然后我们就看到了在SystemServer.java 中

//SystemServer.java
public static void main(String[] args) {
  new SystemServer().run();
}

private void run() {
  ...
// Start services.
  try {
    startBootstrapServices();
    startCoreServices();
    startOtherServices();
  } catch (Throwable ex) {
    throw ex;
  }
  ...
}

private void startOtherServices() {
  ...
  InputMethodManagerService imm = null;

  try {
      imm = new InputMethodManagerService(context, wm);
      ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
    } catch (Throwable e) {
      reportWtf("starting Input Manager Service", e);
    }
    ...
  final InputMethodManagerService immF = imm;
      ...
      try {
          if (immF != null) immF.systemRunning(statusBarF);
        } catch (Throwable e) {
          ...
        }
        ...
}

因为 SystemServer 在开机的时候就执行了,所以 ServiceManager 中就添加了一个 key 是Context.INPUT_METHOD_SERVICE,值是 IMMS 对象的服务,其实 addService() 的同时就已经把该服务启动起来了,具体逻辑参照 Android系统服务的注册方式

咱们接着看的 InputMethodManager.getInstance()代码

//InputMethodManager
public static InputMethodManager getInstance() {
  synchronized (InputMethodManager.class) {
    if (sInstance == null) {
      // b实际上是一个 IMMS 对象
      IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
      //service 就是一个 IMMS 的代理
      IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
      sInstance = new InputMethodManager(service, Looper.getMainLooper());
    }
    return sInstance;
  }
}

刚才我们说了, b 是一个 IMMS 对象,那么这个 service 呢,它就是 IMMS 的代理,看这个就明白了 ,IInputMethodManager.Stub.asInterface(b), AIDL 技术,不再展开讲。 所以说应用程序一创建,就会存在一个 IMM 对象,和一个 IMMS 的代理对象service

然后我们看看 InputMethodManager 的构造函数,

InputMethodManager(IInputMethodManager service, Looper looper) {
     mService = service;
     mMainLooper = looper;
     mH = new H(looper);
     mIInputContext = new ControlledInputConnectionWrapper(looper, mDummyInputConnection , this);
}

这就没啥解释了, InputMethodManager 中的 mService 其实就是一个 IMMS 的代理。
总结一句话就是 (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)得到的 IMM 对象中持有有一个 IMMS 的代理对象 mService 打开关闭输入法这些操作就是由 IMM 中的某些方法调用 IIMM 中相应的方法来实现的.
比如:

  • mService.getInputMethodList() 获取输入法列表。
  • mService.updateStatusIcon(imeToken, packageName , iconId) 更新输入法图标,即屏幕上方状态栏中的输入法图标
  • mService.finishInput(mClient) 隐藏当前输入法。而不说关闭输入法,是因为输入法服务启动起来以后,只有在系统关闭或者切换输入法时才会关闭。
  • mService.showSoftInput(mClient, flags , resultReceiver)打开当前输入法。

虽然每一个 Window 对应一个 View 和一个 ViewRootImpl ,但是由于 IMM 的创建是一个单例模式,所以一个程序中尽管有多个 ViewRootImpl ,但是却只有一个 IMM 对象,就是通过InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);得到 IMM 对象, 注意:尽管这个和上面在SystemServer.java 的 startOtherServices() 中的看起来一样,但是这个得到的是 IMM 对象,而上面那个是 IMMS 对象,这是不一样的,千万别搞混了。

IMMS 就已经启动,(ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm); addService()里面直接启动了该IMMS),那么我们看看 IMS 是怎么启动的吧

IMS启动

既然说到了 IMS ,那么我们就看看 IMS 是怎么启动的吧,还是在SystemServer.java中的 startOtherServices() 方法中,

private void startOtherServices() {
  ...
  InputMethodManagerService imm = null;

  try {
      imm = new InputMethodManagerService(context, wm);
      ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
    } catch (Throwable e) {
      reportWtf("starting Input Manager Service", e);
    }
    ...
  final InputMethodManagerService immF = imm;
      ...
      try {
          if (immF != null) immF.systemRunning(statusBarF);
        } catch (Throwable e) {
          ...
        }
        ...
}

生成 IMMS 对象 imm 之后,我们就发现了 immF , imm 把值赋值给 immF ,然后执行了systemRunning()方法,这个就是启动 IMMS 服务的

//IMMS
public void systemRunning(StatusBarManagerService statusBar) {
  synchronized (mMethodMap) {
    if (!mSystemReady) {//系统启动,执行该方法, mSystemReady 默认为 false ,所以会执行,
      mSystemReady = true;//标志设置为 true ,所以该方法只执行一次
      ...
      try {
        startInputInnerLocked();
      } catch (RuntimeException e) {
        Slog.w(TAG, "Unexpected exception", e);
      }
    }
  }
}

mSystemReady 默认是 false ,系统启动后,这个方法执行后,设置为 true ,所以该方法只执行一次。然后看具体启动的方法startInputInnerLocked();

InputBindResult startInputInnerLocked() {
  。。。
  InputMethodInfo info = mMethodMap.get(mCurMethodId);
  //启动输入法service
 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
 mCurIntent.setComponent(info.getComponent());
 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.input_method_binding_label);
 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(mContext, 0 , new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));

 if (bindCurrentInputMethodService(mCurIntent, this , Context.BIND_AUTO_CREATE | Context.BIND_NOT_VISIBLE | Context.BIND_NOT_FOREGROUND | Context.BIND_SHOWING_UI)) {
    mLastBindTime = SystemClock.uptimeMillis();
    mHaveConnection = true;
    mCurId = info.getId();
  //这个 token 是给输入法 service 用来绑定输入法的 window 的,通过这个token
  //InputMethodManagerService可以很方便的直接管理输入法的window
   mCurToken = new Binder();
  try {
    mIWindowManager.addWindowToken(mCurToken, WindowManager.LayoutParams.TYPE_INPUT_METHOD);
  } catch (RemoteException e) {
  }
  return new InputBindResult(null, null , mCurId , mCurSeq , mCurUserActionNotificationSequenceNumber);
} else {
  mCurIntent = null;
}
return null;
}

mMethodMap 里面存的就是你设备上所有的输入法, key 就是你输入法的一个 ID ,这个可以通过 adb 命令得到: adb shell ime list -a
如下图列出来的就是我设备上安装的所有输入法,其实就一个, id 就是com.android.inputmethod.latin/.LatinIME
如果想要切换输入法,直接通过 adb 命令,先得到输入法的 ID ,然后使用下面的命令就可以了 adb shell ime set com.android.inputmethod.latin/.LatinIME

接着继续看代码 ,先看看绑定的是啥服务把 public static final String SERVICE_INTERFACE = "android.view.InputMethod" 这个相信很多输入法开发者都很熟悉了,所有的输入法开发,都会继承 IMS ,然后在AndroidManifest.xml中,注册该服务,并且需要添加一个action 而这个 action 就是 android.view.InputMethod。 例如,

<service android:name="LatinIME"
        android:label="@string/english_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>

根据 mCurMethodId 得到该输入法的信息 InputMethodInfo 对象,info.getComponent() 里面得到的就是该输入法的包名和该 IMS 的名字,然后就可以设置进去,mCurIntent.setComponent(info.getComponent()),这样就可以绑定输入法了,

public ComponentName getComponent() {
     return new ComponentName(mService.serviceInfo.packageName,mService.serviceInfo.name);
}

看绑定 IMS 的过程吧 bindCurrentInputMethodService()

private boolean bindCurrentInputMethodService(Intent service, ServiceConnection conn , int flags) {
  if (service == null || conn == null) {
    return false;
  }
  return mContext.bindServiceAsUser(service, conn , flags , newUserHandle(mSettings.getCurrentUserId()));
}

这就没啥好讲的了,就是调用了 Context 的绑定服务的方法, 因为 IMMS 实现了 ServiceConnection 接口, 如果bindCurrentInputMethodService()返回 true ,那么 mCurToken 就会被赋值,这个 token 是给输入法 service 用来绑定输入法的 window 的,通过这个 token IMMS 可以很方便的直接管理输入法的window bindCurrentInputMethodService()返回 true ,才会绑定成功,绑定成功后,会执行onServiceConnected()方法,

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
  synchronized (mMethodMap) {
    if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
      //保存输入法 service 传递过来的通信接口IInputMethod
      mCurMethod = IInputMethod.Stub.asInterface(service);
      if (mCurToken == null) {
        unbindCurrentMethodLocked(false, false);
        return;
      }
      //将刚刚创建的 window token 传递给输入法 service ,然后输入用这个 token 创建 window ,
      // 这样 IMMS 可以用根据这个 token 找到输入法在 IMMS 里的数据及输入法 window 在 WMS 里的数据
      executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(MSG_ATTACH_TOKEN, mCurMethod , mCurToken));
      if (mCurClient != null) {
        clearClientSessionLocked(mCurClient);
        //请求为程序和输入法建立一个连接会话,这样 client 就可以直接和输入法通信了
        requestClientSessionLocked(mCurClient);
      }
    }
  }
}

绑定成功后的输入法服务的代理 赋值给 mCurMethod ,为啥还是代理呢,还是因为mCurMethod = IInputMethod.Stub.asInterface(service); AIDL, 绑定成功, mCurMethod 不为 null ,那么会发送 MSG_SHOW_SOFT_INPUT 消息,最后就执行到了HandlerCaller.java 的 handleMessage() 中的 case MSG_SHOW_SOFT_INPUT: 中

executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(MSG_SHOW_SOFT_INPUT,
 getImeShowFlags() , mCurMethod , resultReceiver));

//HandlerCaller.java
public Message obtainMessageIOO(int what, int arg1 , Object arg2 , Object arg3) {
    SomeArgs args = SomeArgs.obtain();
    args.arg1 = arg2;
    args.arg2 = arg3;
    return mH.obtainMessage(what, arg1 , 0 , args);
}

case MSG_SHOW_SOFT_INPUT:
    args = (SomeArgs) msg.obj;
    try {
      ((IInputMethod) args.arg1).showSoftInput(msg.arg1, (ResultReceiver) args.arg2);
    } catch (RemoteException e) {
    }
    args.recycle();
    return true;

所以, args.arg1 就是 mCurMethod , 它实际类型是 IInputMethodWrapper ,继续往下看,showSoftInput()

//IInputMethodWrapper.java
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
    mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT, flags , resultReceiver));
}
..................................
case DO_SHOW_SOFT_INPUT:
  inputMethod.showSoftInput(msg.arg1, (ResultReceiver) msg.obj);

又遇见了一个变量 inputMethod ,这个又是啥玩意呢,先说结果吧, 这个 inputMethod 是通过 onCreateInputMethodInterface() 函数创建的 InputMethodImpl 对象,为啥呢,看 inputMethod 在哪里赋值的,

InputMethod inputMethod = mInputMethod.get();

mInputMethod 呢,又是从哪里赋值的呢?

//IInputMethodWrapper.java
public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) {
  ...
  mInputMethod = new WeakReference<InputMethod>(inputMethod);
  ...
}

还记得之前我们绑定 IMS 的时候,执行了 IMMS 中的bindCurrentInputMethodService()方法,本质上是执行了 mContext.bindServiceAsUser(service, conn , flags , new UserHandle(mSettings.getCurrentUserId())); 我们知道,绑定服务成功后,会执行的 Service 的 onBinder() 方法,因为绑定的是 IMS ,而 IMS 继承 AbsIMS , AbsIMS 又是继承 Service ,并实现了 onBind() 方法,所以在 AbsIMS 中的 onBind() 中寻找

// AbstractInputMethodService.java
@Override
final public IBinder onBind(Intent intent) {
  if (mInputMethod == null) {
    mInputMethod = onCreateInputMethodInterface();
  }
  return new IInputMethodWrapper(this, mInputMethod);
}
//IMS.java
@Override
public AbstractInputMethodImpl onCreateInputMethodInterface() {
  return new InputMethodImpl();
}

所以可知 inputMethod 是通过 AbsIMS 的 onCreateInputMethodInterface() 函数创建的 InputMethodImpl 对象,所以进入到了 InputMethodImpl 的 showSoftInput() 方法中

//InputMethodImpl.java
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
    boolean wasVis = isInputViewShown();
    mShowInputFlags = 0;
    if (onShowInputRequested(flags, false)) {
      try {
        //这个是真正显示 UI 的函数
        showWindow(true);
      } catch (BadTokenException e) {
        。。。
      }
    }
    。。。
    }
}
public void showWindow(boolean showInput) {
  if (mInShowWindow) {
    return;
  }
  try {
    mWindowWasVisible = mWindowVisible;
    mInShowWindow = true;
    showWindowInner(showInput);
  } finally {
    mWindowWasVisible = true;
    mInShowWindow = false;
  }
}

void showWindowInner(boolean showInput) {
      ...
  initialize();
  updateFullscreenMode();
  //这个函数会创建输入法的键盘
  updateInputViewShown();

  if (!mWindowAdded || !mWindowCreated) {
    mWindowAdded = true;
    mWindowCreated = true;
    initialize();
    //创建输入法 dialog 里的候选词View
    View v = onCreateCandidatesView();
    if (v != null) {
      setCandidatesView(v);
    }
  }
  if (mShowInputRequested) {
    if (!mInputViewStarted) {
      if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
      mInputViewStarted = true;
      onStartInputView(mInputEditorInfo, false);
    }
  } else if (!mCandidatesViewStarted) {
    mCandidatesViewStarted = true;
    onStartCandidatesView(mInputEditorInfo, false);
  }
  if (doShowInput) {
    startExtractingText(false);
  }
  if (!wasVisible) {
    mImm.setImeWindowStatus(mToken, IME_ACTIVE , mBackDisposition);
    onWindowShown();
    //这个是 Dialog 的 window ,这里开始就显示 UI 了
    mWindow.show();
  }
}
  1. 在 updateInputViewShown() 中会创建输入法键盘布局,并添加进去
public void updateInputViewShown() {
  boolean isShown = mShowInputRequested && onEvaluateInputViewShown();
  if (mIsInputViewShown != isShown && mWindowVisible) {
    mIsInputViewShown = isShown;
    mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE);
    if (mInputView == null) {
      initialize();
        //这个是核心 view ,创建显示键盘的根view
      View v = onCreateInputView();
      if (v != null) {
        setInputView(v);
      }
    }
  }
}

public void setInputView(View view) {
  mInputFrame.removeAllViews();
  mInputFrame.addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT));
  mInputView = view;
}
  1. onCreateInputView()就是我们开发输入法的时候,设置输入法布局,其实这个布局是R.layout.input_method中 id 为@android:id/inputArea的 FrameLayout 子布局。
  2. 创建输入法 dialog 里的候选词View View v = onCreateCandidatesView();
  3. onWindowShown()当输入法显示的时候,执行的方法,可以在这里面做一些操作
  4. 显示输入法 mWindow.show(); 最后再闲扯淡一下这个 mWindow 到底是什么玩意儿 我们都知道,输入法其实是一个 Dialog ,可是从哪里能看出来呢,就是从这个 mWindow 中看出来的
//IMS.java

public void onCreate() {
    mWindow = new SoftInputWindow(this, "InputMethod", mTheme , null , null , mDispatcherState , WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
    ...
    initViews();
    mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
}

public class SoftInputWindow extends Dialog {}


void initViews() {
  。。。
  mRootView = mInflater.inflate(com.android.internal.R.layout.input_method, null);
  。。。
  mWindow.setContentView(mRootView);
  。。。

  mFullscreenArea = (ViewGroup) mRootView.findViewById(com.android.internal.R.id.fullscreenArea);
  ...
  mExtractFrame = (FrameLayout) mRootView.findViewById(android.R.id.extractArea);
  ...
  mCandidatesFrame = (FrameLayout) mRootView.findViewById(android.R.id.candidatesArea);
  mInputFrame = (FrameLayout) mRootView.findViewById(android.R.id.inputArea);
}

为啥输入法都是在下面,这次找到原因了,Gravity.BOTTOM 为啥输入法会显示到其他页面上面,不管是对话框,还是 Activity ,或者其他类型,原因也找到了WindowManager.LayoutParams.TYPE_INPUT_METHOD,这个值是 2011 ,是一个系统级的窗口,而应用窗口是1~99,子窗口是从1000~1999,数值大的会覆盖在数值小的上面,这是 Window 内部机制决定的

mWindow 加载的布局是R.layout.input_method

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/parentPanel"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:orientation="vertical"
    >

    <LinearLayout
        android:id="@+id/fullscreenArea"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >

        <FrameLayout
            android:id="@android:id/extractArea"
            android:layout_width="match_parent"
            android:layout_height="0px"
            android:layout_weight="1"
            android:visibility="gone">
        </FrameLayout>

        <FrameLayout
            android:id="@android:id/candidatesArea"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="invisible">
        </FrameLayout>

    </LinearLayout>

    <FrameLayout
        android:id="@android:id/inputArea"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone">
    </FrameLayout>

</LinearLayout>

程序的 Window 获得焦点

哪个程序获得焦点是由 WMS 决定的,当系统的 window 状态发生变化时(比如 window 新增,删除)就会调用函数 updateFocusedWindowLocked 来更新焦点window

焦点 view 请求绑定输入法是通过调用InputMethodManager.focusIn()实现的

//上面是 IMMS 端,下面就看 IMS 输入法端的处理  
 public abstract class AbstractInputMethodService extends Service  implements KeyEvent.Callback {  
 public abstract class AbstractInputMethodImpl implements InputMethod {  
    public void createSession(SessionCallback callback) {  
        callback.sessionCreated(onCreateInputMethodSessionInterface());  
      }     
    }  
}  

@Override
public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() {
  //sesion的真正实现
  return new InputMethodSessionImpl();
}
//然后回到了IMMS
void onSessionCreated(IInputMethod method, IInputMethodSession session , InputChannel channel) {
  synchronized (mMethodMap) {
    if (mCurMethod != null && method != null && mCurMethod.asBinder() == method.asBinder()) {
      if (mCurClient != null) {
        //将 session 相关的数据封装到 SessionState 对象里
        mCurClient.curSession = new SessionState(mCurClient, method , session , channel);
        //这个会开始真正的绑定
          InputBindResult res = attachNewInputLocked(true); return;
        }
      }
    }
}

点击 EditText ,键盘启动的流程

点击 EditText , EditText 获得焦点,然后键盘显示,这是最常见的操作,可是里面的内部实现是怎么样的呢?

我们知道, EditText 首先的获得点击事件,才能显示键盘,如果 EditText 连点击事件都没接收到,肯定不会显示键盘,所以, EditText 肯定会执行到 onTouchEvent() 方法,因为 EditText 继承 Textview ,并且本身没有覆盖 onTouchEvent() ,所以我们需要查看 TextView 中的onTouchEvent()

//EditText.java
public class EditText extends TextView {
  ...
}

//TextView.java
public boolean onTouchEvent(MotionEvent event) {
    ...
  final boolean superResult = super.onTouchEvent(event);
  ...
  // 显示 IME ,除非选择只读文本。
  final InputMethodManager imm = InputMethodManager.peekInstance();
  viewClicked(imm);
    //这个是真正显示输入法的调用
  if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
    handled |= imm != null && imm.showSoftInput(this, 0);
  }
  ...
}

在 TextView 的 onTouchEvent() 中,我们发现了两点比较关键信息

  1. 调用了父类的 onTouchEvent() 方法,
  2. 找到了类似显示输入法地方 imm.showSoftInput(),看方法名是显示输入法,

首先我们查看父类的 onTouchEvent() 都做了什么

View #onTouchEvent()

我们知道, onTouchEvent() 的返回值表示是否消耗该事件, true 表示消耗, false 表示不消耗 我们分段分析 onTouchEvent() ,代码里面带有注释

public boolean onTouchEvent(MotionEvent event) {
  ...
if ((viewFlags & ENABLED_MASK) == DISABLED) {//View 处于不可点击状态下
    if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
      setPressed(false);
    }
    //处于不可点击状态下也是可以消耗事件的,只不过会会响应而已
    return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) ==LONG_CLICKABLE));
  }

if (mTouchDelegate != null) {//如果设置的 TouchDelegate ,则会执行 TouchDelegate 的 onTouchEvent() ,
      //onTouchEvent()的机制和 onTouchListener 类似
    if (mTouchDelegate.onTouchEvent(event)) {
      return true;
    }
}

if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
     // 只要设置了 CLICKABLE 或者 LONG_CLICKABLE 任意一个为 true ,就会消耗掉事件
    switch (event.getAction()) {
      case MotionEvent.ACTION_UP:
        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
          // take focus if we don't have it already and we should in
          // touch mode.
          boolean focusTaken = false;
          //让 view 获得焦点
          if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
            focusTaken = requestFocus();
          }
          ...
       }
    }
}

输入法显示

先看看 InputMethodManager imm = InputMethodManager.peekInstance();

//InputMethodManager.java
public static InputMethodManager peekInstance() {
  return sInstance;
}
public static InputMethodManager getInstance() {
  synchronized (InputMethodManager.class) {
    if (sInstance == null) {
      // InputMethodManager其实就是一个 Binder service 的proxy
      IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
      //这个 service 其实就是IMMS
      IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
      sInstance = new InputMethodManager(service, Looper.getMainLooper());
    }
    return sInstance;
  }
}

InputMethodManager.getInstance()又是在哪里执行的呢,然后我们就看到WindowManagerGloabl.java 中

//WindowManagerGloabl.java
public static IWindowSession getWindowSession() {
  synchronized (WindowManagerGlobal.class) {
    if (sWindowSession == null) {
      try {
        //创建 InputMethodManager 实例
        InputMethodManager imm = InputMethodManager.getInstance();
        IWindowManager windowManager = getWindowManagerService();
        sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {
          @Override
          public void onAnimatorScaleChanged(float scale) {
            ValueAnimator.setDurationScale(scale);
          }
        }, imm.getClient(), imm.getInputContext());
      } catch (RemoteException e) {
        Log.e(TAG, "Failed to open window session", e);
      }
    }
    return sWindowSession;
  }
}

而该方法是在 ViewRootImp 初始化的时候执行的

//ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mWindowSession = WindowManagerGlobal.getWindowSession();
    ...
}

我们知道, 每一个 Window 对应一个 View 和 ViewRootImpl , Window 和 View 通过 ViewRootImpl 建立联系 ,所以当我们在 addView() 的时候, InputMethodManager 已经实例化了 继续看 showSoftInput() 逻辑

IMM#showSoftInput()

// IMM.java
public boolean showSoftInput(View view, int flags) {
    return showSoftInput(view, flags , null);
}
public boolean showSoftInput(View view, int flags , ResultReceiver resultReceiver) {
  checkFocus();
  synchronized (mH) {
    if (mServedView != view && (mServedView == null ||!mServedView.checkInputConnectionProxy(view))) {
      return false;
    }
    try {
      return mService.showSoftInput(mClient, flags , resultReceiver);
    } catch (RemoteException e) {
    }
    return false;
  }
}

然后又看到了一个变量 mService ,这是什么鬼玩意儿啊,

final IInputMethodManager mService;

InputMethodManager(IInputMethodManager service, Looper looper) {
  mService = service;//实质上就是一个 IMMS 对象
  mMainLooper = looper;
  mH = new H(looper);
  mIInputContext = new ControlledInputConnectionWrapper(looper, mDummyInputConnection , this);
}

查看源码可知,

  1. 是一个 IInputMethodManager 对象
  2. 在 IMM 初始化的时候被传递过来的,

IMM 初始化是在啥时候呢,

IMM 初始化

public static InputMethodManager getInstance() {
  synchronized (InputMethodManager.class) {
    if (sInstance == null) {
      // InputMethodManager其实就是一个 Binder service 的proxy
      IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
      //这个 service 其实就是IMMS
      IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
      sInstance = new InputMethodManager(service, Looper.getMainLooper());
    }
    return sInstance;
  }
}

看到了吧,就是在 getInstance 的时候,初始化这个 mService 的,可是这个 mService 本质上是个啥玩意呢,其实他是一个 Binder 接口,真正的实现是InputMethodManagerService(IMMS),

public interface IInputMethodManager extends android.os.IInterface {}

public class InputMethodManagerService extends IInputMethodManager.Stub implements ServiceConnection, Handler.Callback {}

所以最后执行到了IMMS#showSoftInput()

IMMS #showSoftInput()

@Override
public boolean showSoftInput(IInputMethodClient client, int flags , ResultReceiver resultReceiver) {
  if (!calledFromValidUser()) {
    return false;
  }
  int uid = Binder.getCallingUid();
  long ident = Binder.clearCallingIdentity();
  try {
    synchronized (mMethodMap) {
      if (mCurClient == null || client == null || mCurClient.client.asBinder() != client.asBinder()) {
        try {
          // 我们需要检查这是否是焦点在窗口管理器中的当前客户端,以便在输入启动之前进行此调用。
          if (!mIWindowManager.inputMethodClientHasFocus(client)) {
            return false;
          }
        } catch (RemoteException e) {
          return false;
        }
      }
      return showCurrentInputLocked(flags, resultReceiver);
    }
  } finally {
    Binder.restoreCallingIdentity(ident);
  }
}

然后执行到了 showCurrentInputLocked() 中

boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
  。。。
  if (mCurMethod != null) {
    executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(MSG_SHOW_SOFT_INPUT, getImeShowFlags() , mCurMethod , resultReceiver));
    mInputShown = true;
    if (mHaveConnection && !mVisibleBound) {
      bindCurrentInputMethodService(mCurIntent, mVisibleConnection , Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY);
      mVisibleBound = true;
    }
    res = true;
  } else if (mHaveConnection && SystemClock.uptimeMillis() >= (mLastBindTime + TIME_TO_RECONNECT)) {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId , SystemClock.uptimeMillis() - mLastBindTime, 1);
    mContext.unbindService(this);
    bindCurrentInputMethodService(mCurIntent, this , Context.BIND_AUTO_CREATE | Context.BIND_NOT_VISIBLE);
  } else {
  }
  return res;
}

搬运地址: