轉載請註明出處: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也就不需要打了)