车机上输入法显示不完整问题解决方案

在车机上,导航栏通常在左侧,目的是离驾驶员近一点,方便操作。最近遇到一个问题,发现输入法的键盘会超出屏幕,显示不全。屏幕的物理分辨率为1920x720,输入法为AOSP里原生的输入法,代码路径在/packages/inputmethods/LatinIME 。 全屏页面,输入法显示正常:

当左侧有导航栏时候,显示效果如下,右侧有部分超出了屏幕,输入法显示不全。

用布局检查器查看,键盘是一个自定义view,宽度为1920,期望的宽度应该是1920 - 130(导航栏的宽度) = 1790。

用sougou输入法,存在同样的问题

排查思路

按常理, 子view通常是不会超出父view的,但是对于直接继承View的自定义view,如果在onMeasure里设置的宽度大于父布局的宽度,就会出现子view超出父view的情况。 看看输入法的代码,重点看看MainKeyboardView,onMeasure的实现在父类KeyboardView

java 复制代码
//packages/inputmethods/LatinIME/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    final Keyboard keyboard = getKeyboard();
    if (keyboard == null) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        return;
    }
    // The main keyboard expands to the entire this {@link KeyboardView}.
    final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
    Log.d(TAG, "width:" + width + ",mOccupiedWidth:" + keyboard.mOccupiedWidth);

    final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
    setMeasuredDimension(width, height);
}

这里可以加个打印,把width打印出来,发现,不管页面是否有左侧导航栏,width都是1920,keyboard.mOccupiedWidth的值也一直是1920。我尝试把width改为1790, 看效果,键盘的布局是不会超出屏幕了,但是依然显示不全,看源码发现,每个按键的宽度是根据keyboard.mOccupiedWidth计算得来的。 直接把keyboard.mOccupiedWidth改为1790呢? 不行,mOccupiedWidth是final变量, 把final去掉再修改呢? 改了,还是不行,因为计算按键宽度的逻辑在onMeasure之前。 所以要重点看看keyboard.mOccupiedWidth的值是怎么来的。代码有点多,我也懒得细看代码调用流程,猜测应该有地方会获取屏幕的分辨率,然后赋值给mOccupiedWidth,搜索代码果然找到获取屏幕分辨率的地方,代码如下。 在这里,我直接return 1790, 然后看上面onMeasure里的width也变成了1790, 再看看在有导航栏的页面,输入法显示也OK。 问题就这么解决了? 肯定没这么简单,如果这里直接返回1790, 在全屏页面下,输入法的右侧会空出一个导航栏的宽度。

java 复制代码
//packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
public static int getDefaultKeyboardWidth(final Resources res) {
    final DisplayMetrics dm = res.getDisplayMetrics();
    return dm.widthPixels; // 尝试直接返回1790
}

解决方案

现在问题转变为:在全屏页面,getDefaultKeyboardWidth 需要返回1920, 而在带有导航栏的页面,需要返回1790。 需要解决的问题有2个:

  1. getDefaultKeyboardWidth在输入法的源码里,怎么更改dm.widthPixels的值?直接在输入法源码吗?

    复制代码
    因为搜狗输入法也有同样的问题,我没法改搜狗输入法的源码,所以只能再想想在哪里修改。
  2. 怎么知道导航栏有没有显示?

2.1 怎么修改dm.widthPixels的值?

看看getDefaultKeyboardWidth 的调用栈

java 复制代码
at com.android.inputmethod.latin.utils.ResourceUtils.getDefaultKeyboardWidth(ResourceUtils.java:189)
at com.android.inputmethod.keyboard.KeyboardSwitcher.loadKeyboard(KeyboardSwitcher.java:115)
at com.android.inputmethod.latin.LatinIME.onStartInputViewInternal(LatinIME.java:994)
at com.android.inputmethod.latin.LatinIME$UIHandler.onStartInputView(LatinIME.java:510)
at com.android.inputmethod.latin.LatinIME.onStartInputView(LatinIME.java:816)
at android.inputmethodservice.InputMethodService.showWindowInner(InputMethodService.java:1863)
at android.inputmethodservice.InputMethodService.showWindow(InputMethodService.java:1803)
at android.inputmethodservice.InputMethodService$InputMethodImpl.showSoftInput(InputMethodService.java:572)
at android.inputmethodservice.IInputMethodWrapper.executeMessage(IInputMethodWrapper.java:207)
at com.android.internal.os.HandlerCaller$MyHandler.handleMessage(HandlerCaller.java:37)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6683)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:982)

看看showWindowInner, 因为这个方法是距离输入法代码最近的地方。

java 复制代码
//frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
void showWindowInner(boolean showInput) {
    //省略部分代码
    if (mShowInputRequested) {
            if (!mInputViewStarted) {
                //注释1
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                onStartInputView(mInputEditorInfo, false);
            }
        } else if (!mCandidatesViewStarted) {
            if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
            mCandidatesViewStarted = true;
            onStartCandidatesView(mInputEditorInfo, false);
        }
    //省略部分代码
}

发现,每次输入法弹出时,都会调到注释1处,而且这段代码在framework里,但是运行在输入法进程。尝试在这里改dm.widthPixels。

java 复制代码
//frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
void showWindowInner(boolean showInput) {
    //省略部分代码
    if (mShowInputRequested) {
            if (!mInputViewStarted) {
                //注释1
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                Resources res = getResources();
                if (res != null) {
                    final DisplayMetrics dm = res.getDisplayMetrics();
                    dm.widthPixels = 1790;
                    Log.d(TAG, "mInputEditorInfo:" + mInputEditorInfo.packageName);
                }
                onStartInputView(mInputEditorInfo, false);
            }
        } else if (!mCandidatesViewStarted) {
            if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
            mCandidatesViewStarted = true;
            onStartCandidatesView(mInputEditorInfo, false);
        }
    //省略部分代码
}

先在这里写死dm.widthPixels = 1790; 编译后,push到系统,发现在带有导航栏的页面,输入法显示OK。说明这里改dm.widthPixels的值是有效的。 接下来再看看怎么获取导航栏是否显示, 系统没有相关的API。 如果有读者知道,请告知下,谢谢!

2.2 怎么知道导航栏有没有显示?

看PhoneWindowManager,发现如下代码,每次导航栏显示或者隐藏,会回调到这里。我能想到的是在这里用系统属性记录下导航栏是否显示。

java 复制代码
//frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
        new BarController.OnBarVisibilityChangedListener() {
    @Override
    public void onBarVisibilityChanged(boolean visible) {
        mAccessibilityManager.notifyAccessibilityButtonVisibilityChanged(visible);
    }
};

修改如下:

java 复制代码
//frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
        new BarController.OnBarVisibilityChangedListener() {
    @Override
    public void onBarVisibilityChanged(boolean visible) {
        if (visible) {
            SystemProperties.set("sys.navigationbar.show", "true");
        } else {
            SystemProperties.set("sys.navigationbar.show", "false");
        }
        mAccessibilityManager.notifyAccessibilityButtonVisibilityChanged(visible);
    }
};

2.3 终极解决方案

在InputMethodService里通过系统属性sys.navigationbar.show获取导航栏是否显示,然后修改dm.widthPixels的值

java 复制代码
//frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
void showWindowInner(boolean showInput) {
    //省略部分代码
    if (mShowInputRequested) {
            if (!mInputViewStarted) {
                //注释1
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                Resources res = getResources();
                if (res != null) {
                    final DisplayMetrics dm = res.getDisplayMetrics();
                    String naviShow = SystemProperties.get("sys.navigationbar.show");
                    if ("true".equals(naviShow)) {
                        dm.widthPixels = 1790; //减去导航栏的宽度
                    } else {
                        dm.widthPixels = 1920;
                    }
                    Log.d(TAG, "mInputEditorInfo:" + mInputEditorInfo.packageName);
                }
                onStartInputView(mInputEditorInfo, false);
            }
        } else if (!mCandidatesViewStarted) {
            if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
            mCandidatesViewStarted = true;
            onStartCandidatesView(mInputEditorInfo, false);
        }
    //省略部分代码
}

3. 搜狗输入法的问题

但是上述解决办法只对AOSP原生的输入法生效。对搜狗输入法不生效。 推测的原因: 搜狗输入法进程开机启动,在进程起来时,拿到的屏幕宽度就是1920, 后面不再获取屏幕宽度,所以在导航栏显示的页面,键盘会超出屏幕,但如果此时把搜狗输入法杀掉,再次弹出输入法,此时拿到的屏幕宽度就是我修改后的1790, 此时输入法显示正常。 这需要搜狗输入法适配下车机。

相关推荐
DengDongQi18 分钟前
Jetpack Compose 滚轮选择器
android
stevenzqzq19 分钟前
Android Studio Logcat 基础认知
android·ide·android studio·日志
代码不停27 分钟前
MySQL事务
android·数据库·mysql
朝花不迟暮33 分钟前
使用Android Studio生成apk,卡在Running Gradle task ‘assembleDebug...解决方法
android·ide·android studio
yngsqq1 小时前
使用VS(.NET MAUI)开发第一个安卓APP
android·.net
Android-Flutter1 小时前
android compose LazyVerticalGrid上下滚动的网格布局 使用
android·kotlin
Android-Flutter1 小时前
android compose LazyHorizontalGrid水平滚动的网格布局 使用
android·kotlin
千里马-horse1 小时前
RK3399E Android 11 将自己的库放到系统库方法
android·so·设置系统库
美狐美颜sdk1 小时前
Android直播美颜SDK:选择指南与开发方案
android·人工智能·计算机视觉·第三方美颜sdk·视频美颜sdk·人脸美型sdk
我命由我123451 小时前
Kotlin 面向对象 - 装箱与拆箱
android·java·开发语言·kotlin·android studio·android jetpack·android-studio