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.(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也就不需要打了)

你可能感兴趣的:(问题分析)