名词简介

IMMS

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

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

IMM

全称是 InputMethodManager。主要是用来管理各部分的交互,是一个客户端的 API ,存在于各个应用程序的 context 中,用来沟通管理所有进程间交互的全局系统服务,可以通过Context.getSystemService(Context.INPUT_METHOD_SERVICE)来获取,

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

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

IMS

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

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

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

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

注意,还有一个和这个很相近的类,InputManagerService,这个有时候也会简称为IMS,它是和Android触摸事件有关的。与输入法无关。不要混淆了。

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属性可以指定输入法设置窗口。-->

输入法的流程

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

IMMS 启动

我们知道。通过 ServiceManager.getService(Context.INPUT_METHOD_SERVICE) 可以得到一个IMMS对象。这个为啥呢,在哪里设置呢?
这就要说到一个NB的类– SystemServer.java ,它和Zygote是Android 世界的两大支柱,SystemServer是Zygote孵化出来的进程,进程名为system_server,几乎所有的系统服务都在该进程中。例如AMS,PMS,WMS,当然也包括IMMS了, 在开机启动的时候,这个类就会执行。这些系统的服务就会被启动。

我们就仔细看看 SystemServer.java 这个类吧,然后发现了一个非常熟悉的方法,main()。

SystemServer # main()

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

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

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

  imm = new InputMethodManagerService(context, wm);
  ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
  ...
  final InputMethodManagerService immF = imm;

  if (immF != null) immF.systemRunning(statusBarF);
  ...
}

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

IMM 创建

我们知道,在每一个程序中,都可以通过下面的代码获取到一个 IMM 对象。

InputMethodManager imm = (InputMethodManager)Context.getSystemService(Context.INPUT_METHOD_SERVICE)

这个 IMM 对象,就是用来和 IMMS 通信的接口。我们就具体看看getSystemService()中到底做了什么操作吧。由于Context 是一个抽象类,真正的实现是 ContextImpl 。所以我们直接看 ContextImpl 的getSystemService().

ContextImpl # getSystemService()

//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();
          }
  });
  ...
}  
private static void registerService(String serviceName, ServiceFetcher fetcher) {
      if (!(fetcher instanceof StaticServiceFetcher)) {
          fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
      }
      SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}

这样我们就可,在ContextImpl这个类被加载的时候,通过 registerService() 方法,把 InputMethodManager 对象添加到 SYSTEM_SERVICE_MAP 中了, 因为静态代码块只执行一次,所以 整个项目中也就只有一个InputMethodManager 对象。

但是还有一个问题, 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 # 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对象。这个 service 呢,它就是 IMMS 的代理,这个应该能看明白,IInputMethodManager.Stub.asInterface(b), AIDL 技术,不再展开讲。

然后我们看看 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 对象,这个IMM对象持有一个 IMMS 的代理对象 mService ,打开关闭输入法这些操作就是由 IMM 中的 mService实现的。 比如:

  • 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 启动

这个还是在SystemServer.java中的 startOtherServices() 方法中,

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

    imm = new InputMethodManagerService(context, wm);
    ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
    ...
    final InputMethodManagerService immF = imm;
    ...
    if (immF != null) immF.systemRunning(statusBarF);
    ...
}

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

//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();

//InputMethodManagerService.java
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

如果想要切换输入法,直接通过 上面的命令得到输入法的 ID ,然后使用下面的命令就可以了

adb shell ime set 输入法ID

接着继续看代码 ,先看看怎么绑定的吧。
InputMethod.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>

继续说 startInputInnerLocked()中的代码。

根据 mCurMethodId 得到该输入法的信息 InputMethodInfo 对象,info.getComponent() 里面得到的就是该输入法的包名和该 IMS 的名字,

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 ,绑定成功,执行onServiceConnected()方法,同时 mCurToken 就会被赋值,这个 token 是给输入法 service 用来绑定输入法的 window 的,通过这个 token, IMMS 可以很方便的直接管理输入法的window

//InputMethodManagerService.java
@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 在 WindowManagerService 里的数据
      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_ATTACH_TOKEN 消息,然后在 handleMessage()中处理这个消息。

case MSG_ATTACH_TOKEN:
  args = (SomeArgs) msg.obj;
  try {
    //args.arg1其实就是 InputMethodImpl 对象,
    ((IInputMethod) args.arg1).attachToken((IBinder) args.arg2);
  } catch (RemoteException e) {
  }
  args.recycle();
  return true;

关键代码就一行,((IInputMethod) args.arg1).attachToken((IBinder) args.arg2);

args.arg2 就是 mCurToken,是保存输入法 service 传递过来的通信接口IInputMethod。
args.arg1 就是 mCurMethod , 是 IInputMethodWrapper的代理对象 ,那我们就直接看看 IInputMethodWrapper的 attachToken()。

IInputMethodWrapper # attachToken()

//IInputMethodWrapper.java
public void attachToken(IBinder token) {
  mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
}
//处理 DO_ATTACH_TOKEN 这个消息。
@Override
public void executeMessage(Message msg) {
  InputMethod inputMethod = mInputMethod.get();
  ...
  switch (msg.what) {
    ...
    case DO_ATTACH_TOKEN: {
      inputMethod.attachToken((IBinder) msg.obj);
      return;
    }  
  }
}

又遇见了一个变量 inputMethod ,那就来看看吧。

InputMethod

通过上面的代码我们知道。inputMethod 是在 通过mInputMethod.get()得到的。可这个mInputMethod 呢,又是从哪里赋值的呢?是在 IInputMethodWrapper 初始化的时候,创建的一个软引用对象。

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

问题又来了,这个 IInputMethodWrapper是在哪里初始化的呢?答案是 AbstracInputMethodService中的onBind()中。

还记得之前我们绑定 IMS 的时候,执行了 IMMS 中的bindCurrentInputMethodService()方法,本质上是执行了
mContext.bindServiceAsUser(service, conn , flags , new UserHandle(mSettings.getCurrentUserId()))。

我们知道,绑定服务成功后,会执行的 Service 的 onBind() 方法,因为绑定的是 IMS ,而 IMS 继承 AbstracInputMethodService , AbstracInputMethodService 又是继承 Service ,并实现了 onBind() 方法,所以在 AbstracInputMethodService 中的 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 是通过 AbstracInputMethodService 的 onCreateInputMethodInterface() 函数创建的 InputMethodImpl 对象。InputMethodImpl 是 IMS 的一个内部类。

inputMethod这个变量来历知道了,inputMethod.attachToken((IBinder) msg.obj)

InputMethod # attachToken()

public void attachToken(IBinder token) {
  if (mToken == null) {
    //保存token
    mToken = token;
    //这样输入法的window就绑定这个window token
    mWindow.setToken(token);
  }
}

再次强调,这个token,就是 是保存输入法 service 传递过来的通信接口IInputMethod。

到这里,IMS就已经启动成功,并且和输入法的mWindow绑定到了一起。 可 mWindow 到底是什么玩意儿。我们都知道,输入法其实是一个 Dialog ,可是从哪里能看出来呢,就是从这个 mWindow 中看出来的,这个 SoftInputWindow 就是继承Dialog的。

SoftInputWindow

//InputMethodService.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);
}
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);
}

通过这段代码,我们可以解释两个事情

  1. 为啥输入法都是显示在下面?
    原因找到了。创建 SoftInputWindow 的时候,设置的是 Gravity.BOTTOM

  2. 为啥输入法会显示到其他页面上面,不管是Dialog,还是 Activity ,或者其他类型?
    原因也找到了SoftInputWindow 创建的时候的windowType是 WindowManager.LayoutParams.TYPE_INPUT_METHOD ,这个值是 2011 ,它是一个系统级的窗口,而应用窗口是1~99,子窗口是从1000~1999,数值大的会覆盖在数值小的上面,这是 Window 内部机制决定的,所以输入法UI会显示到其他页面上面。

  3. 输入法加载的布局是啥样的? 这个也找到了,是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>

输入法的启动流程就大概说完了。感兴趣的可以看 源码分析 - Andrid 输入法框架 之 键盘启动的流程


搬运地址: