前一阵子,应泰国客户需求,需要在Android TV系统定制一个多语言输入法,至少支持中、英、泰三种语言。拿到这个任务,对于至今还是小白的我来说,当然先去google一下有没有大神专门做过符合要求的输入法应用。很遗憾,网上移动终端倒是有不少满足需求的输入法,而且做得还满酷炫,当时搜到的最全面的最接近需求(包含泰语这种名不见经传的小语种)的输入法应用当属Go Keyboard,后来发现我的HTC one手机自带的HTC Sense Input输入法也满足要求,而且还很纯净,系统原生的无广告,符合Material Design风格,但是能在TV上用的还真没找到(口碑不错的搜狗输入法倒是也做了TV版,可惜只支持中英文切换)。

对于程序员来说,开发是你的本职,没有开发过的东西,对我们恰好是机遇,况且使用第三方输入法,毕竟控制权不在自己手里,用户使用出了问题,也修复不了bug,源码拿不到啊!不过,这次的需求是定制系统输入法,不是做软键盘,在时间精力有限情况下,让一个菜鸟短时间内开发出来不太现实,那怎么办呢,这是后话。我们先看看需求出现的客观原因吧!

分析原因,Android TV起步较晚是一方面,操作方式跟手机不一样是根本原因,不同于手机touch,TV是通过遥控控制焦点来执行用户操作。所以,凡是不支持焦点控制的移动端应用,在TV上要么用不了,要么用户体验差(目前TV BOX支持鼠标、键盘操作),更有存在因分辨率引起的显示问题。当然有需求就会有市场,为什么没人在TV输入法模块投入精力去开发呢?原因很简单,遥控输入法真心不好用,至于原因,想想操作方式,再拿个遥控实际体验一下就知道了,输个密码都很艰难,谁还用?况且有人开发了远程输入法,即手机跟盒子在同一个网关环境下,通过手机输入、TV负责显示输出的方式,相当于手机作为遥控使,很方便。还有更方便的,直接把遥控做成键盘,使用硬键盘输入模式。

既然这样,我后面也就不用写下去了,因为我做的工作已经失去市场价值。但是我还想说两句,毕竟在完成任务过程中,我得到了不少工作经验,想跟大家分享一下。可能有人疑问,老板为什么还要让你开发这样一个市场价值不高的东西呢,原因很简单,公司要降低产品开发成本,前面提到的已有开发技术会增加成本投入,毕竟价格定高了,卖不出去,这个我们不多谈吧,作为员工,踏踏实实完成任务就行。

现在进入正题,前面说,实现这个需求既不是靠第三方应用集成,也不是自己完整去开发,那还有第三条路吗?当然有,那就是从系统源码出发,作为一个已经很成熟的系统,只要不是很变态的功能,你都能通过定制修改编译源码来达到目的。我们知道Android源码中默认的有三种输入法:英文、中文、日文,对应的工程代码路径为:

<android_root>/packages/inputmethods/LatinIME/
<android_root>/packages/inputmethods/PinyinIME/
<android_root>/packages/inputmethods/OpenWnn/

其中Latin输入法支持的语种最多,可惜唯独不支持中文输入,没关系,我们可以曲线救国,不是还有一个拼音输入法吗!现在思路有了,实现要分两步:

第一步:解决输入法焦点问题

也就是修改原生输入法,使之支持TV操作,具体实现过程参见我上上篇博客:Android TV定制输入法

第二步:解决多输入法切换问题

前面很明了,我们要满足需求,系统得集成两种输入法:LatinIME和PinyinIME,那就牵涉到多输入法应用切换问题,这个问题我们不能交给用户去处理,Android默认输入法是LatinIME,当用户使用的系统语言环境是英语和泰语等语言时好说,使用中文时,就要让输入法切换成拼音输入法了。按照这样的思路,那只需要在切换系统语言的代码段里加入切换输入法函数就行了,我开始也是这么想的,但是没调试通。

参考了Android输入法之——如何在代码中强制切换输入法_ccwwff的博客-CSDN博客_android 切换输入法实现这篇博客,没解决问题,可能Android新版本API变了,后来看到了另一种方法:

Settings.Secure.putString( mContext.getContentResolver(),Settings.Secure.DEFAULT_INPUT_METHOD,myIME );

倒是起作用,但是有个新问题,系统语言切换后,系统管理输入法的服务类InputMethodManagerService.java会强制切换默认输入法为LatinIME,如果这个类执行重置默认输入法方法在我切换输入法方法后面,那我的代码编写的切换输入法动作就会被覆盖。

问题的关键是如何控制我的设置默认输入法函数在系统那个方法后面执行,这个新问题出现后,首先想到的解决方法是写一个监听系统语言切换广播,通过广播来控制代码执行时机,不过可惜的是,未能如愿解决问题,系统语言切换后,重置默认输入法并不是立即执行,具体什么时候执行,还得深入研究。

那换一个思路吧,加个Handler消息延时发送,问题看起来好像解决了,因为调试后,切换输入法的确成功了,但是偶尔还会失败,通过Log打印发现,有时系统重置默认输入法方法还是会跑到我的切换函数后面执行。按照常规思路,把延时加长不就行了?但是增加延时值会导致系统响应操作变慢,况且这样做也不符合程序健壮性,只能另辟蹊径了!

还是从源码出发,细细研究InputMethodManagerService.java这个类,里面有个resetDefaultImeLocked方法,他是设置系统默认输入法的,我们就从这里入手,加一个限制条件:当系统语言为中文时,设置默认输入法为Pinyin输入法,到这里,问题貌似已经解决了。但是修改源码是有风险的,首先你无法保证你修改的东西会不会带来不可预知的问题,毕竟源码是经过时间考验的。其次,你修改的东西只是针对某个项目,可能其他方案就是要用原生的,所以这里还得加标志位,把修改带来的影响减小到最低,这里就要用到Android的属性系统(System Property)了。

这里简要介绍一下有关Android System Property

顾名思义系统属性,肯定对整个系统全局共享。通常程序的执行以进程为单位各自相互独立,如何实现全局共享呢?System Properties是怎么一回事,又是如何实现的呢?

属性系统是android的一个重要特性。它作为一个服务运行,管理系统配置和状态。所有这些配置和状态都是属性。每个属性是一个键值对(key/value pair),其类型都是字符串。这些属性可能是有些资源的使用状态,进程的执行状态,系统的特有属性……

代码中大量存在SystemProperties.set()/SystemProperties.get();通过这两个接口可以对系统的属性进行读取/设置。

可以通过命令adb shell :getprop查看手机上所有属性状态值,或者getprop init.svc.bootanim制定查看某个属性状态,使用setprop init.svc.bootanim start设置某个属性的状态。

特别属性 :

  • 如果属性名称以“ro.”开头,那么这个属性被视为只读属性。一旦设置,属性值不能改变。
  • 如果属性名称以“persist.”开头,当设置这个属性时,其值也将写入/data/property。
  • 如果属性名称以“net.”开头,当设置这个属性时,“net.change”属性将会自动设置,以加入到最后修改的属性名。(这是很巧妙的,netresolve模块的使用这个属性来追踪在net.*属性上的任何变化。)
  • 属性“ ctrl.start ”和“ ctrl.stop ”是用来启动和停止服务。每一项服务必须在/init.rc中定义.系统启动时,与init守护进程将解析init.rc和启动属性服务。一旦收到设置“ ctrl.start ”属性的请求,属性服务将使用该属性值作为服务名找到该服务,启动该服务。这项服务的启动结果将会放入“ init.svc.<服务名>“属性中。客户端应用程序可以轮询那个属性值,以确定结果。

那在本问题中如何使用该属性系统呢?我们可以自定义以“persist.”开头的属性,如果你的项目引入了layoutlib.jar包,可以直接调用android.os.SystemProperties,因为这个类是加了{@hide}标签的,如果是普通apk,需要通过Java反射机制调用。

package com.gotech.tv.launcher.util;
/*** @author john* @created 2016-2-19*/
import java.lang.reflect.Method;public class SystemPropertiesUtil
{public static String get(String key){String ret = null;try{Class<?> clazz = Class.forName("android.os.SystemProperties");Method mthd = clazz.getMethod("get", new Class[] { String.class });mthd.setAccessible(true);Object obj = mthd.invoke(clazz, new Object[] { key });if (obj != null && obj instanceof String){ret = (String) obj;}}catch (Exception e){e.printStackTrace();}return ret;}public static String get(String key, String def){String ret = def;try{Class<?> clazz = Class.forName("android.os.SystemProperties");Method mthd = clazz.getMethod("get", new Class[] { String.class, String.class });mthd.setAccessible(true);Object obj = mthd.invoke(clazz, new Object[] { key, def });if (obj != null && obj instanceof String){ret = (String) obj;}}catch (Exception e){e.printStackTrace();}return ret;}public static boolean getBoolean(String key, boolean def){boolean ret = def;try{Class<?> clazz = Class.forName("android.os.SystemProperties");Method mthd = clazz.getMethod("getBoolean", new Class[] { String.class, boolean.class });mthd.setAccessible(true);Object obj = mthd.invoke(clazz, new Object[] { key, def });if (obj != null && obj instanceof Boolean){ret = (Boolean) obj;}}catch (Exception e){e.printStackTrace();}return ret;}public static int getInt(String key, int def){int ret = def;try{Class<?> clazz = Class.forName("android.os.SystemProperties");Method mthd = clazz.getMethod("getInt", new Class[] { String.class, int.class });mthd.setAccessible(true);Object obj = mthd.invoke(clazz, new Object[] { key, def });if (obj != null && obj instanceof Integer){ret = (Integer) obj;}}catch (Exception e){e.printStackTrace();}return ret;}public static long getLong(String key, long def){long ret = def;try{Class<?> clazz = Class.forName("android.os.SystemProperties");Method mthd = clazz.getMethod("getLong", new Class[] { String.class, long.class });mthd.setAccessible(true);Object obj = mthd.invoke(clazz, new Object[] { key, def });if (obj != null && obj instanceof Long){ret = (Long) obj;}}catch (Exception e){e.printStackTrace();}return ret;}public static void set(String key, String value){try{Class<?> clazz = Class.forName("android.os.SystemProperties");Method mthd = clazz.getMethod("set", new Class[] { String.class, String.class });mthd.setAccessible(true);mthd.invoke(clazz, new Object[] { key, value });}catch (Exception e){e.printStackTrace();}}
}

下面就是在项目中自定义一个系统属性,我们在启动Activity的onCreate()里添加:

// john add for default IME settingSystemProperties.set("persist.sys.sync.ime" , "true");

最后贴上源码修改部分,在InputMethodManagerService.java的resetDefaultImeLocked方法添加:

private void resetDefaultImeLocked(Context context) {// Do not reset the default (current) IME when it is a 3rd-party IMEif (mCurMethodId != null&& !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {return;}InputMethodInfo defIm = null;for (InputMethodInfo imi : mMethodList) {if (defIm == null) {if (InputMethodUtils.isValidSystemDefaultIme(mSystemReady, imi, context)) {defIm = imi;Slog.i(TAG, "Selected default: " + imi.getId());}}}if (defIm == null && mMethodList.size() > 0){//john add for sync system language and input method -->Slog.i(TAG, "persist.sys.sync.ime : " +SystemProperties.get("persist.sys.sync.ime", "false")+"************Language : "+mRes.getConfiguration().locale.getLanguage());if (SystemProperties.get("persist.sys.sync.ime", "false").equals("true") && mRes.getConfiguration().locale.getLanguage().equals("zh")){for (InputMethodInfo imi : mMethodList){if (imi.getId().equals(REMOTE_IME)){defIm = imi;Slog.i(TAG, "Custom default : " + defIm.getId());}}}// <--endelse{defIm = InputMethodUtils.getMostApplicableDefaultIME(mSettings.getEnabledInputMethodListLocked());Slog.i(TAG, "No default found, using " + defIm.getId());}}if (defIm != null) {setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);}}

附上我们自定义默认输入法的ID:

 /*** john add for Chinese Input*/private static final String REMOTE_IME="com.hisilicon.android.inputmethod.remote/.RemoteIME";

Android TV 源码修改默认输入法相关推荐

  1. android 9 源码 修改 默认 屏幕锁定 -- 无

    配置文件路径: frameworks/base/packages/SettingsProvider/res/values/defaults.xml 修改: <bool name="de ...

  2. Android源码配置默认输入法

    文章目录 Android源码定制默认输入法 声明 Android源码修改默认输入法 关于配置默认输入法的包名和类名 修改说明 Android源码定制默认输入法 声明 郑重声明:博文为原创内容,可以转载 ...

  3. Android源码修改默认音量大小的方法

    目前网上有一些关于修改系统默认音量大小的方法,比如修改AudioSystem.java的DEFAULT_STREAM_VOLUME: public static int[] DEFAULT_STREA ...

  4. MTK android系统源码修改快速上手

    1.拷贝代码仓库 从git@192.168.1.3:a89.git 到work目录下: cbk@YCS:~/work$ ll cbk@YCS:~/work$ rm -rf a89/ cbk@YCS:~ ...

  5. android 4.0中修改默认输入法

    在 android4.0/frameworks/base/services/java/com/android/server/InputMethodManagerService.java中 void b ...

  6. 【Android系统源码修改】修改系统默认定位方式

    gps 移动网络位置信息 gps 修改为 gps,network 或者 network 或者 是否支持gps \frameworks\base\services\core\java\com\andro ...

  7. 【Android系统源码修改】如何内置字体,添加字体文件到system/fonts

    1 添加字体文件 将字体文件复制到frameworks/base/data/fonts/ 2 在Android.mk中添加模块 添加模块后,才能在编译时,把字体拷贝到/system/fonts/ 下 ...

  8. Android项目源码分享

    ├─android web应用 │      jqmDemo_static.zip │      jqmMobileDemo-master.zip │      jqmMobileDemo1_1-ma ...

  9. android代码修改默认输入法,Android 4.3 源码下如何修改默认输入法

    应项目(Android 4.3)需求需要修改默认输入法,本来想的是,这个应该很简单的撒.随google之-,发现网上主要罗列出以下这种方法:在 frameworks\base\core\res 因项目 ...

最新文章

  1. 面试题:查询连续出现的数字
  2. 解决vista和win7在windows服务中交互桌面权限问题:穿透Session 0 隔离
  3. 31/100. Palindromic Substrings
  4. 刪除github上的一個repository
  5. c++ 快速排序_常用排序算法之快速排序
  6. 八个角最多可以把平面分成多少部分?_一个空间最多能被分成几块?
  7. Oracle 20c 新特性:缺省的只读 Oracle HOME 支持
  8. 如何用计算机管理员权限,计算机管理员权限在哪里设置_电脑系统如何设置管理员权限-win7之家...
  9. PL/SQL Developer远程连接Oracle数据库
  10. u8应用服务器设置eai,用友U8+V15EAI用户使用手册.pdf
  11. mysql数据库异地备份
  12. termux python turtle_如何在termux上安装Python的turtle库?
  13. 使用Python分析英文句子的词性和情感倾向
  14. 节奏大师小游戏unity实现
  15. java长方体的父类_一个长方形类Rectangle,一个子类长方体类,计算周长,面积,体积...
  16. 流年暗换,是什么偷走了我们的爱情
  17. PyTorch实战使用Resnet迁移学习
  18. PS不显示文字光标、文本框、选择后不高亮的解决办法
  19. matplotlib绘图教程
  20. 【开源访谈】zTree 作者张其纲访谈实录

热门文章

  1. JMH例子详解20-29(共38个)
  2. k8s容器优雅退出一则研究
  3. 鸿蒙OS真的是PPT吗?
  4. flash跨域修复建议
  5. 趣味网站和软件分享,快来看看有没有你pink的
  6. 电脑用久了变卡怎么办?
  7. vlan实验(三种模式)
  8. coroutine倒计时 kotlin_Kotlin实战-倒计时按钮(Rxjava实现)
  9. python二郎成长笔记(三)(matlab标定工具箱详解,旋转矩阵旋转向量,matlab标定数据传入opencv)
  10. 两个性格和文化差异的例子