1. 程式人生 > >Android 6.0撥號介面號碼格式化

Android 6.0撥號介面號碼格式化

需求及分析

客戶需求:
這裡寫圖片描述
使用hierarchyviewer工具可以發現這個介面對應的activity是DialtactsActivity.

通過搜尋撥號盤的source id(dialpad_view)找到dialpad_fragment.xml,從而找到DialpadFragment.java。

最後在Dialpad_view.xml裡面自定義了一個EditText類來容納撥號的內容:

<view class="com.android.phone.common.dialpad.DigitsEditText"
            xmlns:ex="http://schemas.android.com/apk/res-auto"
android:id="@+id/digits" public class DigitsEditText extends ResizingTextEditText

所以,主要的函式邏輯在DialpadFragment.java裡面。

線索

在DialpadFragment裡面全域性搜尋mDigits。在初始化函式onCreateView裡面,對mDigits註冊了對輸入內容變化的監聽器。

mDigits.addTextChangedListener(this);
PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher
(getActivity(), mDigits);

在PhoneNumberFormatter中會啟動一個TextWatcherLoadAsyncTask來另起一個執行緒來進行數字的分割。


//PhoneNumberFormatter.java
public static final void setPhoneNumberFormattingTextWatcher(Context context, TextView textView) {
        new TextWatcherLoadAsyncTask(GeoUtil.getCurrentCountryIso(context), textView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null
); }

注意,使用國家ISO碼以及mDigits這個TextView來初始化這個物件。這樣在該物件中就可以直接操作TextView的內容了。而在這個AsyncTask中會例項化一個PhoneNumberFormattingTextWatcher物件來對變化的數字進行格式化。

最後的數字格式處理是在這個TextWatcher的afterTextChanged中進行處理。

//PhoneNumberFormattingTextWatcher.java
public synchronized void afterTextChanged(Editable s) {
    String formatted = reformat(s, Selection.getSelectionEnd(s));
    if (formatted != null) {
        int rememberedPos = mFormatter.getRememberedPosition();
        s.replace(0, s.length(), formatted, 0, formatted.length()); 
    }
}

針對通過Intent,啟動撥號介面顯示的號碼,我們在DialpadFragment中可以找到fillDigitsIfNecessary。這個函式中,該函式接收通過intent呼叫傳送過來的號碼,並且呼叫下面的setFormattedDigits來格式化這個號碼,並且顯示在mDigits這個TextView中。

/** DialpadFragment.java
     * Sets formatted digits to digits field.
     */
    private void setFormattedDigits(String data, String normalizedNumber) {
        // strip the non-dialable numbers out of the data string.
        String dialString = PhoneNumberUtils.extractNetworkPortion(data);
        dialString =

                PhoneNumberUtils.formatNumber(dialString, normalizedNumber, mCurrentCountryIso);
        if (!TextUtils.isEmpty(dialString)) {
            Editable digits = mDigits.getText();
            digits.replace(0, digits.length(), dialString);
            // for some reason this isn't getting called in the digits.replace call above..
            // but in any case, this will make sure the background drawable looks right
            afterTextChanged(digits);
        }
    }

這兩個的共同點是都使用了CountryIso這個引數。所以數字是按照不同的國家,顯示不同的樣式的。

這兩個分支中CountryIso都是通過下面的程式碼獲取的:

mCurrentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
//packages/apps/contactcommon/src/com/android/contacts/common/location/CountryDetector.java
public String getCurrentCountryIso() {
        String result = null;
        if (isNetworkCountryCodeAvailable()) {
            result = getNetworkBasedCountryIso();
            Log.w(TAG, "getNetworkBasedCountryIso." + result);
        }

        if (TextUtils.isEmpty(result)) {
            result = getLocationBasedCountryIso();
            Log.w(TAG, "getLocationBasedCountryIso." + result);
        }

        if (TextUtils.isEmpty(result)) {
            result = getSimBasedCountryIso();
            Log.w(TAG, "getSimBasedCountryIso." + result);
        }

        if (TextUtils.isEmpty(result)) {
            result = getLocaleBasedCountryIso();
            Log.w(TAG, "getLocaleBasedCountryIso." + result);
        }
        if (TextUtils.isEmpty(result)) {
            result = DEFAULT_COUNTRY_ISO;
        }
        Log.w(TAG, "DEFAULT_COUNTRY_ISO." + result);


        //for debug
        result = "BR";
        return result.toUpperCase(Locale.US);
    }

這個值是由很多條件判斷來決定的。先判斷當前連線上的網路所屬的國家。如果為空,判斷當前的物理位置所在的國家。如果依然為空,獲取Sim卡所屬的國家。依然為空,判斷當前選擇的語言Locale所屬的國家。否則就預設使用DEFAULT_COUNTRY_ISO = “US”作為國家碼。

注意,上面的Log列印語句都是新增用來驗證國家碼是否會影響分割方法的。

修改之後輸出結果為:

W/CountryDetector( 3948): getLocationBasedCountryIso.null
W/CountryDetector( 3948): getSimBasedCountryIso.
W/CountryDetector( 3948): getLocaleBasedCountryIso.US
W/CountryDetector( 3948): DEFAULT_COUNTRY_ISO.US

說明Location和Sim都是為null,而LocaleBaseCountryIso則為US。

getLocaleBasedCountryIso()這個函式還有點意思,是通過屬性中的值類判定使用者屬於哪個locale的:


//Locale.java
public static Locale getDefaultLocaleFromSystemProperties() {
        final String languageTag = System.getProperty("user.locale", "");
        final Locale defaultLocale;
        if (!languageTag.isEmpty()) {
            defaultLocale = Locale.forLanguageTag(languageTag);
        } else {
            String language = System.getProperty("user.language", "en");
            String region = System.getProperty("user.region", "US");
            String variant = System.getProperty("user.variant", "");
            defaultLocale = new Locale(language, region, variant);
        }
        return defaultLocale;
    }

然後通過defaultLocale.getCountry()來獲取國家。第二個,也就是region實際上就是country的值。

查一下“String region = System.getProperty(“user.region”, “US”);”這個值是怎麼獲取的(其實也就是systemProperties.getProperty(name, defaultValue))。

在liccore/luni/src/main/java/java/lang/System.java裡面有如下語句:

static {
        in = new BufferedInputStream(new FileInputStream(FileDescriptor.in));
        unchangeableSystemProperties = initUnchangeableSystemProperties();
        systemProperties = createSystemProperties();//會將unchangeableSystemProperties的內容新增到systemProperties裡面
        addLegacyLocaleSystemProperties();
    }

在addLegacyLocaleSystemProperties裡面,會根據user.locale的值來設定一些值,如下:

private static void addLegacyLocaleSystemProperties() {
    final String locale = getProperty("user.locale", "");
    if (!locale.isEmpty()) {
        Locale l = Locale.forLanguageTag(locale);
        setUnchangeableSystemProperty("user.language", l.getLanguage());
        setUnchangeableSystemProperty("user.region", l.getCountry());
        setUnchangeableSystemProperty("user.variant", l.getVariant());
    } else {
        // If "user.locale" isn't set we fall back to our old defaults of
        // language="en" and region="US" (if unset) and don't attempt to set it.
        // The Locale class will fall back to using user.language and user.region if unset.
        final String language = getProperty("user.language", "");
        final String region = getProperty("user.region", "");
        if (language.isEmpty()) {            setUnchangeableSystemProperty("user.language", "en");
        }
        if (region.isEmpty()) {
            setUnchangeableSystemProperty("user.region", "US");
        }}}

其實就是根據user.locale的值來設定user.language和user.region的值。

總結

通過上面的分析,我們知道撥號介面數字的分割是按照當前網路所屬的國家來顯示。如果針對特定國家的專案,可以通過設定user.locale來改變撥號介面數字的分割方式,以適應相關的客戶需求。這個分割方式都是google提供的原生方式,推薦不要修改。當然如果客戶有特殊需求,那麼就需要修改PhoneNumberFormattingTextWatcher裡面afterTextChanged函式中的reformat方法,來滿足客戶需求。