google拼音分析和问题解决

玛丽莲梦兔
652次浏览
2020年08月03日 19:41
最佳经验
本文由作者推荐

入党积极分子考察意见-内蒙古招生


一、问题描述
警务通终端中,用谷歌拼音输入,物理键盘输入的时候,在数字模式下( 按屏幕左下角?
123键),键入一个数字(字母),显示出现两个;其他模式下,在最终选字提交前, 字
母出现在输入光标出。
经验证,官方google 拼音1.3.4在android模拟器有同样的问题,排除是我方修改引入
的bug。
由于 我们没有google拼音的源代码,只能采用反编译的方式去阅读理解和插入调试代
码。熟悉这类工作 的人不多,而且都比较繁忙,因此这个问题一直拖着无法解决。乘着
假期抽了点时间进行了分析解决,把 过程要点记录如下,供大家参考,希望更多的人学
会分析和解决此类问题。
二、环境准备
反编译、编译、修改、模拟器运行等需要系列工具,包括:
1、 开发环境和虚拟机。我采用Motorola的motodev studio,最新的是v2.2.0,可以从
其官方网站免费下载(需注册),http:,安装后下载sdk。
在motodev studio里随便生产一个项目,运行就可以启动虚拟机(也可以直接命令
行启动)。
2、 Google拼音输入法apk。网上google很多,下载最新的v1.3.4,安装到虚拟机(打开
windows命令行,在android-sdk-windowsplatform- tools下adb install
)。这里有个陷阱浪费了我一个多小时:我用的虚拟机本身自带 谷歌拼
音,结果用户安装的没有被使用,但似乎也没有报错,只是修改始终不起作用。Adb
shell进入虚拟机, mount –o remount,rw devblockmtdblock0 system, rm
; rm datadalvik- cache*PinyinIME*,删除了系统自带谷歌
输入法。
3、 编译、反编译等。推荐apktool( http:droid-apktool),它集成
了编译反 编译的工具,可直接处理apk文件,很方便。目前最新版本是v1.4.1。.
Java –jar d google_pinyin
Java –jar b google_pinyin PinyinIME_
Apktool是采用baksmali反 编译,生成的是一个具有相当可读性的.smali文件,如
果要进一步看java代码,可以采用其它 方法。我采用dex2jar
(http:x2jar)和DJ java decompiler(g oogle可下载v3.11),前
者将.apk变成.class的压缩包,后者可以一个目录下的. class全部反编译为java源文件(同
类的还有jd-gui等)。目前反汇编为java有部分 会有异常,只能参考。谷歌拼音没有经
过混淆处理,因此反编译的可读性相当好。
4、 修改等。推荐使用ultraedit,全局搜索等方便。
三、代码理解和问题分析
对apk进行反编译后,首先大体看看其结构。顶层上是lib, res, smali,


,。lib目录下是本地库,大概就是谷歌拼音的核心算法了,不
用java写一方面是 效率,另一方面也安全,不容易被反汇编抄袭。Res下是各种资源,我们
要快速搞懂程序的内容,看看 程序界面和这里边内容的关系很有帮助。Smali是apktool生产
的反编译结果,按源代码包的 层次组织,见下图。


总体上说,谷歌拼音的代码量不小,完全搞懂需要一些时间 ;但我们关键是要解决问题,只
要搞懂几条路径就可能解决。看看目录结构,很快就能发现
是关 键的输入法实现代码所在。接下来就是解题思路。
从现象看,谷歌输入法从触摸屏软键盘输入的时候,是 不存在字符重复的问题的,但从硬键
盘(模拟器右边那个页面有)输入有。从常规理解来看,两个输入途 径最后的处理方式应该
是一样的(变成键码或者事件后),没有理由写两套一样的逻辑,我们就从差别入 手。从一
般android应用的逻辑我们知道,硬键盘的处理,对应用程序来说(谷歌输入法可看为一 个
特殊的应用),应该是从某个界面view或者窗口的onKeyDown事件开始的;软键盘的处理 ,
本质上是触摸屏事件的处理,应该是从onTouch事件开始。在ultraedit里边搜索on KeyDown
(在pinyin目录上右键,选择在该目录下的文件中查找,并在高级选项中勾上搜 索子目录),
一共找到3个文件出现6次,分别是:两次,
3次,一次。第一个看文字就可以 忽略,显然是一个教程的按
键响应事件;后面两个看起来是正点。具体阅读可以知道,都是一个onKe yDown函数,前
者除了函数名,还有条件调用IME的onKeyDown一次,有条件调用父类的 onKeyDown一次。
因此关键入口在,从这个文件的命名也可以看到,它应该是谷歌输入法的主< br>文件之一。为了验证,在的onkeyDown函数入口处增加打印:
.method public onKeyDown(ILandroidviewKeyEvent;)Z
.locals 3
.parameter
.parameter

.prologue


const-string v0, #本行以下的三行为新增的打印代码,v0放tag

const- string v1, # v1放信息,都可以随意写;只要保证.locals大于1(此
处用到了v0v1两个临时寄存器; smali的语法中,v*寄存器和参数p*是靠.locals数目来区分
的,最大临时寄存器以上的 编码为p*--记得是这样)

invoke-static {v0, v1}, LandroidutilLog;->d(LjavalangString;LjavalangStrin g;)I #smali真
是很好用,首先这样的形式不存在链接、重定位等麻烦事,插入和修改代码 几乎和改脚本类
似,同时这又能很方便的编译回java字节码;具体插入代码的写法,从这么多的sm ali文件
里找相似的来参考就行,这个语法也是其文档有说明的,只要理解java,很容易读懂。比 如
上面这行,就是调用静态函数Log.d,参数是v0,v1,类型都是String,返回整形)

const4 v2, 0x1

保持,用apktool重新编译成apk,用motodev studio签名,卸载老的,安装新的…
Java -jar apktool b google_input PinyinIME_
签名
Adb uninstall
Adb install PinyinIME_
测试:
到settings->languages & keyboard里边选上google pinyin输入 法,其他都不选,回到首页
点谷歌搜索条,输入,在logcat里边可以看到硬键盘输入时,出现我们 添加的log信息。



抓住头之后就好办了,接下来就是阅读理解,一 时理解不了的加打印确认,一步步搞明白主
要的流程。用DJ java decompiler反编译的java文件看得更清楚:
public boolean onKeyDown(int i, KeyEvent keyevent)


{
int j = Log.d(, );
boolean flag;
if(!mIsQWERTYKeyboard && own(i, keyevent))
{
flag = true;
} else
{
if(eatCount() != 0)
flag = true;
else
flag = false;
if(processKey(keyevent, flag))
flag = true;
else
flag = own(i, keyevent);
}
return flag;
}
每个键按下的时候调onKeyDown, 一般情 况下将以false为第二个参数调用processkey,即
不做实际动作(第二个参数为real Action),如果processKey认为已经处理完毕,则返回,
否则让父类去处理。
public boolean onKeyUp(int i, KeyEvent keyevent)
{
boolean flag;
if(!mIsQWERTYKeyboard && p(i, keyevent))
flag = true;
else
if(processKey(keyevent, true))
flag = true;
else
flag = p(i, keyevent);
return flag;
}
类似,只是processkey用true作为第二个参数。
Processkey调 用commitResultTextsendKeyChar等输入法框架函数把键码送到输入处理。
PinyinIME里commitResultText对全键盘和九宫格的处理也不一样,一个是通过Skb Container
处理,一个是直接调用各输入法的commitResultText实现。

从另一条路上,触摸屏的处理。比较麻烦的是onTouch和OnTouchEvent的 区别,查了一些
资料,我的理解是说如果用户有注册某个view的onTouchListener, 那么系统会调用onTouch,
onTouch返回值决定是否继续传播触摸事件,如果没有人con sume掉该事件,最后系统会调
用onTouchEvent。


从代码看,全键盘和九宫格的实现用的不是同一个view(PinyinIME中初始化代码),搜索
onTouch和onTouchEvent难以直接看明白,用打印确认分别是SkbContainer和< br>devSoftKeyboardView,而且两种情况下都没有看到调用onTouch,而是调用o nTouchEvent。
九宫格hEvent -> leTouchEvent

private boolean onSingleTouchEvent(MotionEvent motionevent)
{
long l;
SoftKey softkey;
int i1;
int i = (int)() + 0;
int j = (int)() + 0;
int k = ion();
l = ntTime();
softkey = mCurrentKey;
i1 = findSoftKey(i, j); 根据触摸位置取得键码
if(i1 == -1)
{
k = 1;
} else
{
SoftKey softkey1 = mSoftKeys[i1];
mCurrentKey = softkey1;
}
k;
JVM INSTR tableswitch 0 2: default 80
0 100
1 299
2 140;
goto _L1 _L2 _L3 _L4
_L1:
return true;
_L2:
if(Sound())
yDown();
if(rate())
{
Vibrator vibrator = mVibrator;
long al[] = mVibratePattern;
e(al, -1);
}
_L4:
if(mCurrentKey != softkey)
{


mKeyDownTime = l;
invalidateKey(softkey);
if(( & 4) != 0)
{
SoftKey softkey2 = mCurrentKey;
invalidateKey(softkey2);
}
if(ect != null)
{
Messages(4);
showPopupHint(i1);
}
if(( & 2) != 0)
{
Handler handler = mHandler;
Message message = Message(3, i1, 0);
boolean flag = ssageDelayed(message, 800L);
}
}
if(( & 8) != 0)
{
Handler handler1 = mHandler;
Message message1 = Message(2, i1, 0);
boolean flag1 = ssageDelayed(message1, 800L);
}
continue; * Loopswitch isn't completed *
_L3:
mCurrentKey = null;
Messages(2);
Messages(1);
Messages(3);
hidePopupHint();
if(softkey != null)
{
invalidateKey(softkey);
boolean flag2 = false;
long l1 = mKeyDownTime;
if(l - l1 >= 800L)
{
flag2 = true;
boolean flag3;
if(( & 2) != 0)
flag3 = true;
else


flag3 = false;
flag2 &= flag3;
}
fireEvent(softkey, flag2);
}
if(true) goto _L1; else goto _L5
_L5:
}

private void fireEvent(SoftKey softkey, boolean flag)
{
if(softkey != null && mListener != null)
if(flag)
{
EventListener eventlistener = mListener;
int i =
Object obj =
Press(i, obj);
} else
{
EventListener eventlistener1 = mListener;
int j =
Object obj1 =
(j, obj1);
}
}


OnKey由具体的比例T 9等输入法继承的SoftKeyboardView类实现,这种情况下触摸屏
和硬键盘输入走的路似 乎的确完全不一样,部分可能是一些历史遗留问题导致的,另外的确
键盘和触摸屏也有一些不同的处理, 比如振动回馈等。

全键盘布局下,触摸屏走的是SkbContainer的onTouc hEvent,它会根据具体动作调用
handleUpMoveDownEvent, handle UpEvent会调用responseKeyEvent,它再调用PinyinIME的
respo nseSoftKeyEvent, 后者处理功能键等,最后调用onKeyUp事件,和硬键盘输入走到同一
处。

比 较路径没有发现很明显的问题,但至少知道了onKeyUp事件不会带来问题(触摸屏也
走),从on KeyDown相关的一些函数看,再加一些打印,发现PinyinIME的commitResultTex t(这
个动作应该是输入法把编辑好的输入结果发送给应用程序的接口)也只被调用了一次,尝试
不调用processKey,把返回值固定设置为0,还是一样;设置为1(意味着不调用父类的
o nKeyDown),重复字符消失了。这么看来,就是因为硬键盘输入的情况下,PinyinIME的
onKeyUp本身会用commitResultText给应用提供输入,而父类(即输入法框架)本身对 硬键
盘有个缺省处理,会显示输入的键,导致重复。



一、问题描述
警务通终端中,用谷歌拼音输入,物理键盘输入的时候,在数字 模式下(按屏幕左下角?
123键),键入一个数字(字母),显示出现两个;其他模式下,在最终选字 提交前,字
母出现在输入光标出。
经验证,官方google 拼音1.3.4在android模拟器有同样的问题,排除是我方修改引入
的bug。
由于 我们没有google拼音的源代码,只能采用反编译的方式去阅读理解和插入调试代
码。熟悉这类工作 的人不多,而且都比较繁忙,因此这个问题一直拖着无法解决。乘着
假期抽了点时间进行了分析解决,把 过程要点记录如下,供大家参考,希望更多的人学
会分析和解决此类问题。
二、环境准备
反编译、编译、修改、模拟器运行等需要系列工具,包括:
1、 开发环境和虚拟机。我采用Motorola的motodev studio,最新的是v2.2.0,可以从
其官方网站免费下载(需注册),http:,安装后下载sdk。
在motodev studio里随便生产一个项目,运行就可以启动虚拟机(也可以直接命令
行启动)。
2、 Google拼音输入法apk。网上google很多,下载最新的v1.3.4,安装到虚拟机(打开
windows命令行,在android-sdk-windowsplatform- tools下adb install
)。这里有个陷阱浪费了我一个多小时:我用的虚拟机本身自带 谷歌拼
音,结果用户安装的没有被使用,但似乎也没有报错,只是修改始终不起作用。Adb
shell进入虚拟机, mount –o remount,rw devblockmtdblock0 system, rm
; rm datadalvik- cache*PinyinIME*,删除了系统自带谷歌
输入法。
3、 编译、反编译等。推荐apktool( http:droid-apktool),它集成
了编译反 编译的工具,可直接处理apk文件,很方便。目前最新版本是v1.4.1。.
Java –jar d google_pinyin
Java –jar b google_pinyin PinyinIME_
Apktool是采用baksmali反 编译,生成的是一个具有相当可读性的.smali文件,如
果要进一步看java代码,可以采用其它 方法。我采用dex2jar
(http:x2jar)和DJ java decompiler(g oogle可下载v3.11),前
者将.apk变成.class的压缩包,后者可以一个目录下的. class全部反编译为java源文件(同
类的还有jd-gui等)。目前反汇编为java有部分 会有异常,只能参考。谷歌拼音没有经
过混淆处理,因此反编译的可读性相当好。
4、 修改等。推荐使用ultraedit,全局搜索等方便。
三、代码理解和问题分析
对apk进行反编译后,首先大体看看其结构。顶层上是lib, res, smali,


,。lib目录下是本地库,大概就是谷歌拼音的核心算法了,不
用java写一方面是 效率,另一方面也安全,不容易被反汇编抄袭。Res下是各种资源,我们
要快速搞懂程序的内容,看看 程序界面和这里边内容的关系很有帮助。Smali是apktool生产
的反编译结果,按源代码包的 层次组织,见下图。


总体上说,谷歌拼音的代码量不小,完全搞懂需要一些时间 ;但我们关键是要解决问题,只
要搞懂几条路径就可能解决。看看目录结构,很快就能发现
是关 键的输入法实现代码所在。接下来就是解题思路。
从现象看,谷歌输入法从触摸屏软键盘输入的时候,是 不存在字符重复的问题的,但从硬键
盘(模拟器右边那个页面有)输入有。从常规理解来看,两个输入途 径最后的处理方式应该
是一样的(变成键码或者事件后),没有理由写两套一样的逻辑,我们就从差别入 手。从一
般android应用的逻辑我们知道,硬键盘的处理,对应用程序来说(谷歌输入法可看为一 个
特殊的应用),应该是从某个界面view或者窗口的onKeyDown事件开始的;软键盘的处理 ,
本质上是触摸屏事件的处理,应该是从onTouch事件开始。在ultraedit里边搜索on KeyDown
(在pinyin目录上右键,选择在该目录下的文件中查找,并在高级选项中勾上搜 索子目录),
一共找到3个文件出现6次,分别是:两次,
3次,一次。第一个看文字就可以 忽略,显然是一个教程的按
键响应事件;后面两个看起来是正点。具体阅读可以知道,都是一个onKe yDown函数,前
者除了函数名,还有条件调用IME的onKeyDown一次,有条件调用父类的 onKeyDown一次。
因此关键入口在,从这个文件的命名也可以看到,它应该是谷歌输入法的主< br>文件之一。为了验证,在的onkeyDown函数入口处增加打印:
.method public onKeyDown(ILandroidviewKeyEvent;)Z
.locals 3
.parameter
.parameter

.prologue


const-string v0, #本行以下的三行为新增的打印代码,v0放tag

const- string v1, # v1放信息,都可以随意写;只要保证.locals大于1(此
处用到了v0v1两个临时寄存器; smali的语法中,v*寄存器和参数p*是靠.locals数目来区分
的,最大临时寄存器以上的 编码为p*--记得是这样)

invoke-static {v0, v1}, LandroidutilLog;->d(LjavalangString;LjavalangStrin g;)I #smali真
是很好用,首先这样的形式不存在链接、重定位等麻烦事,插入和修改代码 几乎和改脚本类
似,同时这又能很方便的编译回java字节码;具体插入代码的写法,从这么多的sm ali文件
里找相似的来参考就行,这个语法也是其文档有说明的,只要理解java,很容易读懂。比 如
上面这行,就是调用静态函数Log.d,参数是v0,v1,类型都是String,返回整形)

const4 v2, 0x1

保持,用apktool重新编译成apk,用motodev studio签名,卸载老的,安装新的…
Java -jar apktool b google_input PinyinIME_
签名
Adb uninstall
Adb install PinyinIME_
测试:
到settings->languages & keyboard里边选上google pinyin输入 法,其他都不选,回到首页
点谷歌搜索条,输入,在logcat里边可以看到硬键盘输入时,出现我们 添加的log信息。



抓住头之后就好办了,接下来就是阅读理解,一 时理解不了的加打印确认,一步步搞明白主
要的流程。用DJ java decompiler反编译的java文件看得更清楚:
public boolean onKeyDown(int i, KeyEvent keyevent)


{
int j = Log.d(, );
boolean flag;
if(!mIsQWERTYKeyboard && own(i, keyevent))
{
flag = true;
} else
{
if(eatCount() != 0)
flag = true;
else
flag = false;
if(processKey(keyevent, flag))
flag = true;
else
flag = own(i, keyevent);
}
return flag;
}
每个键按下的时候调onKeyDown, 一般情 况下将以false为第二个参数调用processkey,即
不做实际动作(第二个参数为real Action),如果processKey认为已经处理完毕,则返回,
否则让父类去处理。
public boolean onKeyUp(int i, KeyEvent keyevent)
{
boolean flag;
if(!mIsQWERTYKeyboard && p(i, keyevent))
flag = true;
else
if(processKey(keyevent, true))
flag = true;
else
flag = p(i, keyevent);
return flag;
}
类似,只是processkey用true作为第二个参数。
Processkey调 用commitResultTextsendKeyChar等输入法框架函数把键码送到输入处理。
PinyinIME里commitResultText对全键盘和九宫格的处理也不一样,一个是通过Skb Container
处理,一个是直接调用各输入法的commitResultText实现。

从另一条路上,触摸屏的处理。比较麻烦的是onTouch和OnTouchEvent的 区别,查了一些
资料,我的理解是说如果用户有注册某个view的onTouchListener, 那么系统会调用onTouch,
onTouch返回值决定是否继续传播触摸事件,如果没有人con sume掉该事件,最后系统会调
用onTouchEvent。


从代码看,全键盘和九宫格的实现用的不是同一个view(PinyinIME中初始化代码),搜索
onTouch和onTouchEvent难以直接看明白,用打印确认分别是SkbContainer和< br>devSoftKeyboardView,而且两种情况下都没有看到调用onTouch,而是调用o nTouchEvent。
九宫格hEvent -> leTouchEvent

private boolean onSingleTouchEvent(MotionEvent motionevent)
{
long l;
SoftKey softkey;
int i1;
int i = (int)() + 0;
int j = (int)() + 0;
int k = ion();
l = ntTime();
softkey = mCurrentKey;
i1 = findSoftKey(i, j); 根据触摸位置取得键码
if(i1 == -1)
{
k = 1;
} else
{
SoftKey softkey1 = mSoftKeys[i1];
mCurrentKey = softkey1;
}
k;
JVM INSTR tableswitch 0 2: default 80
0 100
1 299
2 140;
goto _L1 _L2 _L3 _L4
_L1:
return true;
_L2:
if(Sound())
yDown();
if(rate())
{
Vibrator vibrator = mVibrator;
long al[] = mVibratePattern;
e(al, -1);
}
_L4:
if(mCurrentKey != softkey)
{


mKeyDownTime = l;
invalidateKey(softkey);
if(( & 4) != 0)
{
SoftKey softkey2 = mCurrentKey;
invalidateKey(softkey2);
}
if(ect != null)
{
Messages(4);
showPopupHint(i1);
}
if(( & 2) != 0)
{
Handler handler = mHandler;
Message message = Message(3, i1, 0);
boolean flag = ssageDelayed(message, 800L);
}
}
if(( & 8) != 0)
{
Handler handler1 = mHandler;
Message message1 = Message(2, i1, 0);
boolean flag1 = ssageDelayed(message1, 800L);
}
continue; * Loopswitch isn't completed *
_L3:
mCurrentKey = null;
Messages(2);
Messages(1);
Messages(3);
hidePopupHint();
if(softkey != null)
{
invalidateKey(softkey);
boolean flag2 = false;
long l1 = mKeyDownTime;
if(l - l1 >= 800L)
{
flag2 = true;
boolean flag3;
if(( & 2) != 0)
flag3 = true;
else


flag3 = false;
flag2 &= flag3;
}
fireEvent(softkey, flag2);
}
if(true) goto _L1; else goto _L5
_L5:
}

private void fireEvent(SoftKey softkey, boolean flag)
{
if(softkey != null && mListener != null)
if(flag)
{
EventListener eventlistener = mListener;
int i =
Object obj =
Press(i, obj);
} else
{
EventListener eventlistener1 = mListener;
int j =
Object obj1 =
(j, obj1);
}
}


OnKey由具体的比例T 9等输入法继承的SoftKeyboardView类实现,这种情况下触摸屏
和硬键盘输入走的路似 乎的确完全不一样,部分可能是一些历史遗留问题导致的,另外的确
键盘和触摸屏也有一些不同的处理, 比如振动回馈等。

全键盘布局下,触摸屏走的是SkbContainer的onTouc hEvent,它会根据具体动作调用
handleUpMoveDownEvent, handle UpEvent会调用responseKeyEvent,它再调用PinyinIME的
respo nseSoftKeyEvent, 后者处理功能键等,最后调用onKeyUp事件,和硬键盘输入走到同一
处。

比 较路径没有发现很明显的问题,但至少知道了onKeyUp事件不会带来问题(触摸屏也
走),从on KeyDown相关的一些函数看,再加一些打印,发现PinyinIME的commitResultTex t(这
个动作应该是输入法把编辑好的输入结果发送给应用程序的接口)也只被调用了一次,尝试
不调用processKey,把返回值固定设置为0,还是一样;设置为1(意味着不调用父类的
o nKeyDown),重复字符消失了。这么看来,就是因为硬键盘输入的情况下,PinyinIME的
onKeyUp本身会用commitResultText给应用提供输入,而父类(即输入法框架)本身对 硬键
盘有个缺省处理,会显示输入的键,导致重复。


钢铁是怎么炼成的读后感-员工辞职信范文


成人高考满分作文-做客阅读答案


社会实践活动内容-产品质量承诺书


国际关系学院分数线-电脑的利与弊


中考标语-创先争优活动方案


上海政法学院教务管理系统-五年级教学工作总结


广东交通学院-安徽中考试卷


微段子-轻叩诗歌的大门手抄报