1. 程式人生 > >Android.Settings類&設定預設輸入法

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中的資料進行恢復。