曲線救國之修復騰訊 X5 核心檔案上傳的相容性問題

KevinOfNeu發表於2018-09-11

來說說可惡的騰訊 X5 核心,記錄下今天問題的排查,定位,和修復過程。

背景

Android 專案中的 WebView 整合了騰訊的 X5 核心,由於 X5 在展示方面的相容性問題深受前端們的喜愛(我能說,他們的 H5 頁面的樣式相容問題,全部推鍋到移動開發嗎, 除了最新版本的 Chrome 核心,是否可以考慮下別的瀏覽器...)。

然而,就在今天,一個不幸的上午,捕獲到了如下異常:

 UncaughtException detected: android.os.FileUriExposedException: file:///storage/emulated/0/DCIM/Camera/1536649175379.jpg exposed beyond app through ClipData.Item.getUri()
        at android.os.StrictMode.onFileUriExposed(StrictMode.java:1958)
        at android.net.Uri.checkFileUriExposed(Uri.java:2356)
        at android.content.ClipData.prepareToLeaveProcess(ClipData.java:941)
        at android.content.Intent.prepareToLeaveProcess(Intent.java:9747)
        at android.content.Intent.prepareToLeaveProcess(Intent.java:9732)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1611)
        at android.app.Activity.startActivityForResult(Activity.java:4536)
        at android.support.v4.app.BaseFragmentActivityApi16.startActivityForResult(BaseFragmentActivityApi16.java:54)
        at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:65)
        at android.app.Activity.startActivityForResult(Activity.java:4494)
        at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:711)
        at android.app.Activity.startActivity(Activity.java:4855)
        at android.app.Activity.startActivity(Activity.java:4823)
        at android.content.ContextWrapper.startActivity(ContextWrapper.java:376)
        at org.chromium.android_webview.ResourcesContextWrapperFactory$WebViewContextWrapper.startActivity(Unknown Source:11)
        at com.tencent.tbs.core.partner.b.a$2.onClick(Unknown Source:406)
        at android.view.View.performClick(View.java:6266)
        at android.view.View$PerformClick.run(View.java:24730)
        at android.os.Handler.handleCallback(Handler.java:789)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at android.os.Looper.loop(Looper.java:171)
        at android.app.ActivityThread.main(ActivityThread.java:6672)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:246)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
複製程式碼

一回頭

幸運的是找到了出現問題的呼叫: <input type="file" acctType="image/*"/>

上述標籤,最終會呼叫到 WebChromeClient 的 onShowFileChooser 方法(不同 Android 版本有所差異,>= 5.0 是此方法),然而,經過測試,這次崩潰並沒有呼叫到此處(一臉懵逼)。

乍一看是跨 APP 檔案共享導致的問題,恰好最近專案剛把 targetSdkVersion 從 22 升級到 26,也就是必須要相容 Android 6.0 的動態許可權以及 7.0 的跨應用檔案共享。

但是,仔細分析堆疊資訊,沒有發現專案主動呼叫的程式碼,可疑點鎖定到這一行 at com.tencent.tbs.core.partner.b.a$2.onClick(Unknown Source:406)。但是下載下來的 騰訊核心 jar 檔案中並沒有包含 com.tencent.tbs.core.partner.**,由於 X5 核心只提供了輕量級的 jar 檔案,實際核心的下載和更新是 APP 安裝後動態進行的,於是乎去了 /data/data/{packageUd}/app_tbs, 並且把所有檔案都導到電腦上,裡面主要包含了包含資原始檔的 apk, .so 檔案以及一個 dex 檔案,經過 dex2jar 這些操作後,仍然沒有找到可疑的類檔案。

二回頭

上一條路被堵死。 很幸運,恰好今天幫測試解決 UI Automator 的問題,順道也用了一下, 獲得如下佈局:

曲線救國之修復騰訊 X5 核心檔案上傳的相容性問題

emmm, 出問題的關鍵就是點選拍照,專案中沒有主動顯示這種樣式的彈窗,因此可能是系統做了攔截處理,或者就是 X5。

換了幾臺不同廠商的測試機實驗,樣式都是一樣,基本排除是系統攔截處理的鍋。最可疑的就是 X5 了。

中午還去了浜燒市場吃了頓飯,心好大....

三回頭

基本鎖定問題後,就開始各種預先申請許可權,StrictMode 上折騰,試圖解決許可權問題,無果。

但每次 APP 崩潰幾次後,再次呼叫,發現又會呼叫到 WebChromeClient 的 onShowFileChooser 方法,由於我們自己做過許可權處理,一切又恢復正常。 (後來發現是 X5 發現崩潰後,降級邏輯)。

四回頭

測試發現,出問題的點只是拍照這一個地方,可惡的騰訊 X5 核心並沒有做相容 7.0 的邏輯處理,並且惡意攔截 input file 標籤,美美的彈出自己的檔案選擇框。 相容都沒有做好,有碧蓮彈窗。。。。佩服!!!

五回頭

不知怎麼滴靈光一現,就想如果我們去掉拍照這個按鈕,問題不就解決了嗎?分析佈局,目測是 X5 在 WebView 後面動態 add 了一個 android.widget.LinearLayout, 別問我怎麼知道的...

於是乎誕生了如下程式碼:

專案中 WebView.java

    LinearLayout fuckTbsLayout;
    List<TextView> fuckTextViews;
    @Override
    public void onViewAdded(View child) {
        if (child.getClass().getName().startsWith("com.tencent.tbs.")
                &&
                child instanceof LinearLayout
                &&
                ((LinearLayout) child).getChildCount() > 0
                ) {
            fuckTbsLayout = (LinearLayout) child;
            TextView fuckItem;
            if (fuckTbsLayout.getChildAt(0) instanceof TextView) {
                fuckItem = (TextView) fuckTbsLayout.getChildAt(0);
                String fuckTitle = fuckItem.getText().toString();
                if (fuckTitle.contains("請選擇上傳方式") || fuckTitle.contains("相簿") || fuckTitle.contains("拍照") || fuckTitle.contains("其它方式")) {
                    fuckTextViews = new ArrayList<>();
                    for (int i = 0; i < fuckTbsLayout.getChildCount(); i++) {
                        TextView fuckTextView = null;
                        if (fuckTbsLayout.getChildAt(i) instanceof TextView) {
                            fuckTextView = (TextView) fuckTbsLayout.getChildAt(i);
                        }
                        if (fuckTextView != null && fuckTextView.getText().toString().trim().contains("拍照")) {
                            fuckTextViews.add(fuckTextView);
                        }
                    }
                    if (fuckTextViews != null && fuckTextViews.size() > 0) {
                        for (TextView todoRemoveFuckTextView : fuckTextViews) {
                            fuckTbsLayout.removeView(todoRemoveFuckTextView);
                        }
                    }
                    fuckTextViews = null;
                }
            }
        }
        super.onViewAdded(child);
    }
//sorry for the F words.
複製程式碼

主要思想就是在 WebView 的 onViewAdded 方法中做手腳,此方法是作甚的呢?

    /**
     * Called when a new child is added to this ViewGroup. Overrides should always
     * call super.onViewAdded.
     *
     * @param child the added child view
     */
    public void onViewAdded(View child) {
    }
複製程式碼

非常通俗易懂,當 X5 偷偷的去動態 addView 的時候,所在的父元件此方法必定會被呼叫,只要過濾一下,記錄下來拍照的所屬的 View,然後從父元件上呼叫 removeView 移除掉就好了。

再回頭脖子就要斷了

上述問題,曲線救國得以解決。

方案的缺點

  • TextView 變化,文案變化,會再次失效

尾巴

  • 在問題排查以及解決過程中,騰訊的 X5 文件,以及論壇,QQ 群形同虛設,幾乎沒有什麼有價值的線索,狂吐槽。

  • X5 估計自己都沒有做相容測試,就動態下發錯誤的邏輯程式碼,屬實是狂傲。

  • X5 檢測應用崩潰後,降級邏輯總算是有點良心,降低對使用者的影響,但是我們的 crash 率飆升了...唉

相關文章