Android.Settings類&設定預設輸入法
Android的所有系統設定項(如音量、觸控提示音、預設輸入法等資訊)均是儲存到一個數據庫。在介面上調整設定時將值儲存到該資料庫,開機時將從資料庫讀取值作為預設設定。這些讀取、設定操作都可以通過API或adb命令進行。
P.S.本文僅作為學習記錄,知識點、程式碼上下文並不完整與正確,以後再改善吧。
需求
機頂盒專案要求Android系統開機後預設啟用指定的觸寶輸入法;預設關閉觸控聲、鎖屏聲、電源充電聲。
之前的做法似乎是修改韌體實現,但由於早期韌體已經用於生產,後續的OTA升級沒有辦法使修改生效,就導致無法對早期盒子方便地進行預設設定(除非燒寫韌體),因此考慮從應用層實現,因而有了本次實踐。
Settings類API
系統設定主要使用的API位於android.provider.Settings,來看看Settings類的結構:
該類定義了許多常量,實際上就是每個設定項在資料庫中的欄位名;此外還有修改資料的put方法與讀取的get方法。
需要著重注意的是,這些常量被細分到了System、Secure、Global三個內部類中,對應三種不同的許可權或者說作用範圍,在修改不同內部類的屬性時,需呼叫對應內部類的put方法方能生效。
禁用觸控聲的常量定義屬於Settings.System範圍,設定禁用的方法為:
String key = Settings.System.SOUND_EFFECTS_ENABLED;//"sound_effects_enabled"
int value = 0;//0禁用 1啟用
boolean success = Settings.System.putInt(mContext.getContentResolver(), key, value);
再如,預設輸入法常量定義屬於Settings.Secure範圍,設定方法為:
(注意此處putString()方法是Secure內部類的方法)
String key = Settings.Secure.DEFAULT_INPUT_METHOD;//"default_input_method"
String id = "com.android.inputmethod.latin/.LatinIME" ;//Android預設輸入法ID
boolean success = Settings.Secure.putString(mContext.getContentResolver(), key, id);
為什麼要強調使用的內部類呢?因為System、Secure、Global三個內部類都有同樣的put、get方法,方法引數又只是String型別的鍵值,因此很容易用錯方法。通過原始碼知道,當呼叫方法和常量不屬於同一內部類時,最終會設定失敗(返回值為false)。比如,System.putInt()最後會呼叫System.putStringForUser()進行處理,如果常量屬於Secure或Global範圍,將直接返回false而不會進行儲存。
/** @hide */
public static boolean putStringForUser(ContentResolver resolver, String name, String value,
int userHandle) {
if (MOVED_TO_SECURE.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Secure, value is unchanged.");
return false;
}
if (MOVED_TO_GLOBAL.contains(name) || MOVED_TO_SECURE_THEN_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Global, value is unchanged.");
return false;
}
return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
}
Settings資料庫
(本節所述均為個人的理解,時間關係沒有去確認,如果誤導請包涵和指正)
上節所做的設定,最後都被儲存到系統資料庫中,(不知道稱其為資料庫還是ContentProvider比較準確),系統在開機時讀取這些值進行預設的設定。
Settings實際上是一個apk,負責所有系統級別屬性的設定。我司正在Realtek晶片上做產品,Realtek就提供了他們包裝的Settings作為demo供我們除錯,從Settings的原始碼中可以看到很多定製的功能。
Settings資料庫的位置位於
adb shell
cd /data/data/com.android.providers.settings/databases
- settings.db-backup
- settings.db-journal
將settings.db-backup改名為settings.db,即可使用sqlite工具檢視,如下:
可以看到,該資料庫中有三個表system、secure、global對應API中的定義的常量,此處觸控聲”sound_effects_enabled”位於system表中,當前狀態為0。
以上是我個人的推測,因為該資料庫中並不存在預設輸入法的欄位常量“default_input_method”(然而卻可以生效)。從檔名“settings.db-backup”可以知道,這並不是系統的實時資料,而僅是備份資料。並且,對於系統的設定的變更,該資料庫也並沒有實時改變,也驗證了這一點。Settings設定的實時資料被儲存到哪裡,我目前沒有找到,但八九不離十,由於並不影響功能實現,就暫且擱置。
P.S.此處也可以看到realtek關鍵字,可見不同的平臺廠商對android有不同的定製,需要對症下藥。
ADB修改資料庫
除了呼叫android.provider.Settings的API來修改系統設定外,在開發階段還可以使用adb命令方便地修改Settings資料庫,並且介面上是立即生效的哦(視廠商重新整理機制而定)。如,關閉觸控聲、設定預設輸入法,可以用以下adb命令立即生效(仍然需要注意區別system/secure/global):
settings put system sound_effects_enabled 0
settings put secure default_input_method com.android.inputmethod.latin/.LatinIME
可以用ADB命令來確認要修改的鍵值對。
設定預設輸入法
有了上述的背景知識,就可以輕鬆地設定系統的預設輸入法了。
程式碼
這裡從工程中抽出了設定預設輸入法的程式碼,可能並不完整,但思路很清晰了:
import android.provider.Settings;//匯入包
/**
* 若觸寶輸入法已安裝,則設其為系統預設輸入法
* (寫入Android系統資料庫)
*/
public static void setDefaultInputMethod(Context context){
//獲取系統已安裝的輸入法ID
String[] methods = getInputMethodIdList(context);
if (methods == null || methods.length == 0){
EvLog.w(String.format("found no input method."));
return;
}
//檢查是否安裝觸寶輸入法
//觸寶輸入法ID "com.cootek.smartinputv5/com.cootek.smartinput5.TouchPalIME";
String targetKeyword = "TouchPal";
String value = "";
for (String m : methods){
EvLog.d(String.format("find : %s", m));
if (m.toLowerCase().contains(targetKeyword.toLowerCase())){
value = m;//找到觸寶輸入法
}
}
if (value == "") {
EvLog.w(String.format("didn't find " + targetKeyword));
return;
}
//設定預設輸入法
String key = Settings.Secure.DEFAULT_INPUT_METHOD;
boolean success = Settings.Secure.putString(context.getContentResolver(), key, value);
EvLog.d(String.format("writeDbDefaultInputMethod(%s),result: %s", value,success));
//讀取預設輸入法
String current = Settings.Secure.getString(context.getContentResolver(),key);
EvLog.d(String.format("current default: %s",current));
}
/**
* 獲取系統已安裝的輸入法ID
* @param context
* @return
*/
public static String[] getInputMethodIdList(Context context){
InputMethodManager imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null && imm.getInputMethodList() != null){
String[] methodIds = new String[imm.getInputMethodList().size()];
for (int i = 0; i <imm.getInputMethodList().size(); i++) {
methodIds[i] = imm.getInputMethodList().get(i).getId();
}
return methodIds;
}
return new String[]{};
}
輸入法ID
設定Settings.Secure.DEFAULT_INPUT_METHOD的值時需要輸入法的ID:
/**
* Setting to record the input method used by default, holding the ID
* of the desired method.
*/
public static final String DEFAULT_INPUT_METHOD = "default_input_method";
輸入法ID形式如:
com.android.inputmethod.latin/.LatinIME (縮寫)
com.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME (完整)
com.sohu.inputmethod.sogou.xiaomi/.SogouIME (搜狗輸入法小米版)
LatinIME是android自帶輸入法,斜槓/後的點.表示相同包名的縮寫形式,但當IME的路徑與包名並不完全相同時不能使用縮寫形式,如觸寶輸入法: (注意到inputv5與input5的區別)
com.cootek.smartinputv5/com.cootek.smartinput5.TouchPalIME
不能縮寫為
com.cootek.smartinputv5/.TouchPalIME (字首不同,不能縮寫)
getInputMethodIdList()方法展示瞭如何獲取系統已安裝的輸入法ID。一開始因為同事提供了錯誤的觸寶輸入法ID縮寫形式,導致設定預設輸入法後,無法開啟任何輸入法(因為不存在上述縮寫形式表示的輸入法)。於是我通過getInputMethodIdList()獲取系統安裝的輸入法ID,並通過關鍵字來確定要設定的輸入法。
問題解決。
關聯問題
接觸到Settings類後,想起來以前處理的一個問題:通過定製的API修改系統解析度後,重啟後並不生效。
原因是在修改解析度後,還要往Settings資料庫中寫入新的解析度配置,否則,重啟開機會根據Settings中的資料進行恢復。