Contacts FC問題解決過程記錄之號碼格式化 (libphonenumber)

轉載請註明出處:https://blog.csdn.net/turtlejj/article/details/83748519,謝謝~

 

        在做Android P的升級時,遇到了一個問題,Dialer、Contacts、InCallUi等應用,在打開的瞬間就會發生FC。以Contacts爲例(由於每家廠商都可能會定製自己的Contacts應用,代碼可能與原生Android不同,因此未必能通過我這裏的Call Stack從Android原生代碼中找到相應的調用),抓到的log如下:

 Caused by: java.lang.RuntimeException: cannot load/parse metadata
 	at com.android.i18n.phonenumbers.MetadataManager.loadMetadataAndCloseInput(MetadataManager.java:217)
 	at com.android.i18n.phonenumbers.MetadataManager.getMetadataFromSingleFileName(MetadataManager.java:190)
 	at com.android.i18n.phonenumbers.MetadataManager.getMetadataFromMultiFilePrefix(MetadataManager.java:116)
 	at com.android.i18n.phonenumbers.MultiFileMetadataSourceImpl.getMetadataForRegion(MultiFileMetadataSourceImpl.java:64)
 	at com.android.i18n.phonenumbers.PhoneNumberUtil.getMetadataForRegion(PhoneNumberUtil.java:2212)
 	at com.android.i18n.phonenumbers.PhoneNumberUtil.getCountryCodeForValidRegion(PhoneNumberUtil.java:2373)
 	at com.android.i18n.phonenumbers.PhoneNumberUtil.getCountryCodeForRegion(PhoneNumberUtil.java:2361)
 	at com.android.i18n.phonenumbers.AsYouTypeFormatter.getMetadataForRegion(AsYouTypeFormatter.java:137)
 	at com.android.i18n.phonenumbers.AsYouTypeFormatter.<init>(AsYouTypeFormatter.java:130)
 	at com.android.i18n.phonenumbers.PhoneNumberUtil.getAsYouTypeFormatter(PhoneNumberUtil.java:2694)
 	at android.telephony.PhoneNumberFormattingTextWatcher.<init>(PhoneNumberFormattingTextWatcher.java:71)
 	at com.android.contacts.compat.ContactPhoneNumberFormattingTextWatcherCompat.newInstance(ContactPhoneNumberFormattingTextWatcherCompat.java:23)
 	at com.android.contacts.util.PhoneNumberFormatter$TextWatcherLoadAsyncTask.doInBackground(PhoneNumberFormatter.java:48)
 	at android.os.AsyncTask$2.call(AsyncTask.java:333)
 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)

        通過log可以大致推斷出,FC是在Contacts對手機號碼進行格式化時發生的。

        根據Call Stack進行跟蹤,發現Contacts調用了framework中PhoneNumberFormattingTextWatcher類的構造方法,而該構造方法中調用了一個叫做PhoneNumberUtil的類中的getAsYouTypeFormatter()方法。

/**
 * The formatting is based on the given <code>countryCode</code>.
 *
 * @param countryCode the ISO 3166-1 two-letter country code that indicates the country/region
 * where the phone number is being entered.
 */
public PhoneNumberFormattingTextWatcher(String countryCode) {
    if (countryCode == null) throw new IllegalArgumentException();
    mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
}

        根據查找發現,PhoneNumberUtil這個類是在libphonenumber這個模塊中的,是一個用於對手機號碼進行格式化的開源項目,這種開源項目的代碼一般是不會出現問題的。

        剛看到這裏的時候,我就覺得肯定是我們在給libphonenumber這個模塊打Patch的時候出了什麼問題。所以,我們就Patch進行了排查,發現確實有同事修改了一個bin文件,該修改是爲了解決老版本的Android原生代碼不能正確對166和199開頭的手機號進行格式化的問題。修改是從Android O上直接cherry過來的。

libphonenumber/src/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CN

        然後,我查看了Contacts的mk文件,發現Contacts是靜態依賴於libphonenumber的,即Contacts會將libphonenumber的編譯產物包進自己的apk當中。

        並且,使用apktool工具將Contacts的apk進行反編譯後,發現其中確實包含了libphonenumber的產物。

LOCAL_STATIC_JAVA_LIBRARIES := \
    android-common \
    com.android.vcard \
    guava \
    libphonenumber

        我回退了這個Patch,重新編譯了Contacts的apk並push到手機裏,心想問題應該不會復現了。但是,出乎意料的時,Contacts還是會發生一模一樣的FC問題。

        於是,我猜想,問題也許不是由於這個Patch導致的?因此,我在libphonenumber的代碼里加了log,想看看到底問題出在哪裏?重新編譯了Contacts的apk,並push到手機裏後,發現新加的log並沒有打印出來,而且最令人感到疑惑的是,由於加了log,代碼的實際行數發生了變化,但是新抓取的FC log卻還是與之前發生在同樣的行數。

        這讓我不由得懷疑,是不是libphonenumber模塊在編譯Contacts時並沒有重新生成,而是使用了原來的編譯產物。

        接下來,我在libphonenumber的代碼中隨意加入了一些語法錯誤,想看看,在編譯Contacts的時候,libphonenumber會不會報錯。然而,結果是,libphonenumber確實報了編譯錯誤,而且就是由於我加入的語法錯誤而引起的。

        我又用apktool將新編譯出來的Contacts進行反編譯,發現PhoneNumberMetadataProto_CN文件確實被我回退了,那就說明,libphonenumber的的確確是被編譯出來幷包進Contacts的apk中了。

        那爲什麼修改就是不生效呢?沒有辦法,我只能用Android Studio的單步調試功能,一步一步的跟蹤代碼的流程(關於單步調試的方法,可以參考《使用Android Studio對Android系統源碼進行單步調試》)。

 

        然而就是這次跟蹤,讓我發現了問題所在。

        在跟蹤代碼到MetadataManager.java中的loadMetadataAndCloseInput(InputStream source)方法時,我發現了一個很奇怪的地方,這裏嘗試去讀的不是Contacts的apk中的PhoneNumberMetadataProto_CN,而是ext.jar中的PhoneNumberMetadataProto_CN。

        看到這之後,我猜測,Contacts根本沒有讀取自己apk中libphonenumber相關的代碼與文件,而是去讀取了ext.jar中的相應模塊。於是我去找了一下frameworks/base/Android.bp文件中ext.jar是如何編譯的,果不其然,ext.jar也包含了libphonenumber,但是,其名字卻叫libphonenumber-platform。

// Build ext.jar
// ============================================================
java_library {
    name: "ext",
    no_framework_libs: true,
    static_libs: [
        "libphonenumber-platform",
        "nist-sip",
        "tagsoup",
        "rappor",
    ],
    dxflags: ["--core-library"],
}

        繼續查看libphonenumber的Android.bp文件,找到libphonenumber-platform相關的內容,可以看到,有一條jarjar_rules

// For platform use, builds directly against core-libart to avoid circular
// dependencies. *NOT* for unbundled use.
java_library_static {
    name: "libphonenumber-platform",

    // For the platform, compile everything except the carrier to phone number
    // which isn't used.
    java_resource_dirs: [
        "libphonenumber/src",
        "geocoder/src",
        "internal/prefixmapper/src",
    ],

    srcs: [
        "libphonenumber/src/**/*.java",
        "geocoder/src/**/*.java",
        "internal/prefixmapper/src/**/*.java",
    ],
    jarjar_rules: "jarjar-rules.txt",

    sdk_version: "core_current",
    java_version: "1.7",
}

        在當前目錄下找到jarjar-rules.txt文件,發現這個文件只有一行

rule com.google.** com.android.@1

        即,將libphonenumber這個模塊中com.google的包名,全部替換爲com.android。於是我到libphonenumber和Contacts中的代碼裏去看了一下,確實如此。

        libphonenumber中的代碼包名是com.google.i18n.phonenumbers,而Contacts所調用的framework中的PhoneNumberFormattingTextWatcher類在import的時候,全部用的是com.android.i18n.phonenumbers;同樣,Call Stack中打印出來的包名也都是com.android.i18n.phonenumbers。

        據此,反過來查看libphonenumber的Android.bp中關於libphonenumber的編譯信息,發現編譯libphonenumber時,是沒有替換包名的。

// For unbundled use, supports gingerbread and up.
java_library_static {
    name: "libphonenumber",
    defaults: ["libphonenumber-unbundled-defaults"],

    srcs: ["geocoder/src/**/*.java"],
    java_resource_dirs: ["geocoder/src"],

    sdk_version: "9",
    java_version: "1.7",
}

        由此,終於明白,爲什麼不論如何修改代碼重新編譯Contacts,FC的Call Stack都不會發生變化了,因爲代碼調用的一直都是ext.jar中的libphonenumber,而回退Patch並添加log後ext.jar沒有重新編譯並push到手機裏。

        回退Patch,重新編譯ext.jar,並push到手機中,問題不在復現。

        究其原因,是因爲Android P和Android O在讀取和寫入PhoneNumberMetadataProto_CN文件的相關代碼發生了變化,不能直接將O上對該文件的修改直接merge到P上(P版本的PhoneNumberMetadataProto_CN文件已經添加了對166以及199開頭號碼的格式化,因此該Patch也就不需要打了)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章