转载请注明出处: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.(AsYouTypeFormatter.java:130)
at com.android.i18n.phonenumbers.PhoneNumberUtil.getAsYouTypeFormatter(PhoneNumberUtil.java:2694)
at android.telephony.PhoneNumberFormattingTextWatcher.(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 countryCode
.
*
* @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也就不需要打了)