google拼音分析和问题解决
入党积极分子考察意见-内蒙古招生
一、问题描述
警务通终端中,用谷歌拼音输入,物理键盘输入的时候,在数字模式下(
按屏幕左下角?
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命令行,在
)。这里有个陷阱浪费了我一个多小时:我用的虚拟机本身自带
谷歌拼
音,结果用户安装的没有被使用,但似乎也没有报错,只是修改始终不起作用。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命令行,在
)。这里有个陷阱浪费了我一个多小时:我用的虚拟机本身自带
谷歌拼
音,结果用户安装的没有被使用,但似乎也没有报错,只是修改始终不起作用。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给应用提供输入,而父类(即输入法框架)本身对
硬键
盘有个缺省处理,会显示输入的键,导致重复。