【工作總結(jié)】系統(tǒng)簽名app運行webview(Android5.0+)閃退問題

image.png

系統(tǒng)簽名app運行webview(Android5.0+)閃退問題


前言:設(shè)置 -> 幫助中心采用加載本地文件顯示device常見問題解答。
需求 update :不同的項目顯示不同內(nèi)容,后臺獲取顯示內(nèi)容

實現(xiàn)分析:

  • webView 實現(xiàn)

webView 簡單使用參考:
Android開發(fā):最全面、最易懂的Webview使用詳解

開發(fā)平臺

Android 4.4 項目平臺開發(fā),如期開發(fā)完成,開發(fā)效果如下:

device-2017-06-29-143438.png-1407.5kB
device-2017-06-29-143438.png-1407.5kB

適配致命問題

其他項目平臺(Android 6.0)運行,直接閃退,打印如下:


image_1bjp9lvav1cb31hvc1pl21oo912dul.png-94.1kB
image_1bjp9lvav1cb31hvc1pl21oo912dul.png-94.1kB

【問題分析】:

  • Android 4.4 平臺正常運行,Android 6.0 提示 XMl中 WebView 加載失敗,難道Android 6.0對于webview中格式要求嚴(yán)格些(以前遇到過類似問題)?

【嘗試解決方案】:

  • 加載WebView xml布局檢查與分析,加載xml 中 WebView 照樣失敗;
  • 既然加載 xml 中 webview失敗,同事建議采用Java 代碼動態(tài)加載webview,無需加載xml 中 WebView 控件,就不會出現(xiàn)上述閃退打印錯誤(曲線救國,心中泛起了一絲笑容)

測試Java 動態(tài)加載webview ,Android 6.0 運行,繼續(xù)閃退,串口查詢錯誤日志:

 java.lang.UnsupportedOperationException: For security reasons, WebView is not allowed in privileged processes
  at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:96)
  at android.webkit.WebView.getFactory(WebView.java:2194)
  at android.webkit.WebView.ensureProviderCreated(WebView.java:2189)
  at android.webkit.WebView.setOverScrollMode(WebView.java:2248)
  at android.view.View.<init>(View.java:3588)
  at android.view.View.<init>(View.java:3682)
  at android.view.ViewGroup.<init>(ViewGroup.java:497)
  at android.widget.AbsoluteLayout.<init>(AbsoluteLayout.java:55)
  at android.webkit.WebView.<init>(WebView.java:544)
  at android.webkit.WebView.<init>(WebView.java:489)
  at android.webkit.WebView.<init>(WebView.java:472)
  at android.webkit.WebView.<init>(WebView.java:459)
  at android.webkit.WebView.<init>(WebView.java:449)

動態(tài)加載webview 錯誤日志大概意思:出于安全的角度,webview 不允許運行在特許進程(系統(tǒng)進程),納悶了,webview 憑什么不允許運行在系統(tǒng)進程,Android 4.4 運行的不是好好的嗎?

尋求萬能 Google 與 stackoverflow Help,在查閱到很多人遇到了此問題,分析apk 使用了android:sharedUserId="android.uid.system"申請了系統(tǒng)權(quán)限,去掉即可解決問題,(沒有解釋原因)自己嘗試寫了一個Demo,確實能規(guī)避問題。
提取Google分析出來的解決方案:

  1. 去掉android:sharedUserId="android.uid.system"
  2. 單獨寫一個apk 運行webview

設(shè)置很多操作需要使用系統(tǒng)進程調(diào)用,第一種方案不能采用,與產(chǎn)品經(jīng)理協(xié)商,把設(shè)置中幫助中心模塊單獨抽取為單獨apk,測試ok

雖然實現(xiàn)了功能,每次出設(shè)置需要出2個apk,麻煩。去掉申請系統(tǒng)權(quán)限可以正常運行webview原因不知道,利用閑余時間查Google與查看webview源碼,WebViewFactory getProvider() 方法中異常打印正是閃退異常打?。?/p>

image_1bjpirdfqe3g1p2p1ihh1j2dl9412.png-33.3kB
image_1bjpirdfqe3g1p2p1ihh1j2dl9412.png-33.3kB

源碼可以看出,首次使用webview時,系統(tǒng)會檢測uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UIDuid 為系統(tǒng)進程或者root進程,直接拋出異常。

為什么會有這種安全機制呢?因為webview允許運行js,如果用戶通過js注入安全代碼,那么js就可以肆無忌憚的使用系統(tǒng)權(quán)限,這無疑是一個漏洞,可謂門戶大開。

聯(lián)想到在framework層去掉加載webview時候?qū)ο到y(tǒng)進程與root進程檢測判斷,(實現(xiàn)較為復(fù)雜,每個項目都需去掉,且不知是否會引出其他異常,沒有驗證,后續(xù)有時間去驗證。。。)

拋出異常之前會檢測sProviderInstance是否為空:
if (sProviderInstance != null) return sProviderInstance;
sProviderInstance 是WebViewFactoryProvider 對象,提供創(chuàng)建webview內(nèi)核機制。WebView Android 4.4 之前使用內(nèi)核webkit,Android 5.0 之后使用chromium內(nèi)核,Google 使用了工廠方法模式,動態(tài)切換內(nèi)核實現(xiàn)方式。能否一開始就主動創(chuàng)建sProviderInstance,放到WebViewFactory中,從而達到欺騙API 繞過系統(tǒng)檢查?

聯(lián)想到Hook思想:
Hook 英文翻譯過來就是「鉤子」的意思,那我們在什么時候使用這個「鉤子」呢?在 Android 操作系統(tǒng)中系統(tǒng)維護著自己的一套事件分發(fā)機制。應(yīng)用程序,包括應(yīng)用觸發(fā)事件和后臺邏輯處理,也是根據(jù)事件流程一步步地向下執(zhí)行。而「鉤子」的意思,就是在事件傳送到終點前截獲并監(jiān)控事件的傳輸,像個鉤子鉤上事件一樣,并且能夠在鉤上事件時,處理一些自己特定的事件。

image_1bjpldukg17dj1i3u1hc61elr1mou1f.png-15.5kB
image_1bjpldukg17dj1i3u1hc61elr1mou1f.png-15.5kB

參考:Hook技術(shù)及其簡單實戰(zhàn)

系統(tǒng)創(chuàng)建sProviderInstance,系統(tǒng)使用getProviderClass()創(chuàng)建,利用反射:

private static Class<WebViewFactoryProvider> getProviderClass() {
        Context webViewContext = null;
        Application initialApplication = AppGlobals.getInitialApplication();

        try {
            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                    "WebViewFactory.getWebViewContextAndSetProvider()");
            try {
                webViewContext = getWebViewContextAndSetProvider();
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
            }
            Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
                    sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");

            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
            try {
                initialApplication.getAssets().addAssetPathAsSharedLibrary(
                        webViewContext.getApplicationInfo().sourceDir);
                ClassLoader clazzLoader = webViewContext.getClassLoader();

                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
                loadNativeLibrary(clazzLoader);
                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);

                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
                try {
                    return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY,
                            true, clazzLoader);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                }
            } catch (ClassNotFoundException e) {
                Log.e(LOGTAG, "error loading provider", e);
                throw new AndroidRuntimeException(e);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
            }
        } catch (MissingWebViewPackageException e) {
            // If the package doesn't exist, then try loading the null WebView instead.
            // If that succeeds, then this is a device without WebView support; if it fails then
            // swallow the failure, complain that the real WebView is missing and rethrow the
            // original exception.
            try {
                return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
            } catch (ClassNotFoundException e2) {
                // Ignore.
            }
            Log.e(LOGTAG, "Chromium WebView package does not exist", e);
            throw new AndroidRuntimeException(e);
        }
    }

返回值是一個 WebViewFactoryProvider 的類,可以看到系統(tǒng)會首先加載 CHROMIUM_WEBVIEW_FACTORY,也就是使用 Chrome 內(nèi)核的 WebView,整個創(chuàng)建 sProviderInstance 的過程都可以用反射搞定,自己寫一個HookwebView 創(chuàng)建sProviderInstance;

public static void hookWebView() {
        int sdkInt = Build.VERSION.SDK_INT;
        try {
            Class<?> factoryClass = Class.forName("android.webkit.WebViewFactory");
            Field field = factoryClass.getDeclaredField("sProviderInstance");
            field.setAccessible(true);
            Object sProviderInstance = field.get(null);
            if (sProviderInstance != null) {
                Log.d(TAG,"sProviderInstance isn't null");
                return;
            }
            Method getProviderClassMethod;
            if (sdkInt > 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
            } else if (sdkInt == 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
            } else {
                Log.d(TAG,"Don't need to Hook WebView");
                return;
            }
            getProviderClassMethod.setAccessible(true);
            Class<?> providerClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);
            Class<?> delegateClass = Class.forName("android.webkit.WebViewDelegate");
            Constructor<?> providerConstructor = providerClass.getConstructor(delegateClass);
            if (providerConstructor != null) {
                providerConstructor.setAccessible(true);
                Constructor<?> declaredConstructor = delegateClass.getDeclaredConstructor();
                declaredConstructor.setAccessible(true);
                sProviderInstance = providerConstructor.newInstance(declaredConstructor.newInstance());
                field.set("sProviderInstance", sProviderInstance);
            }
            Log.d(TAG,"Hook done!");
        } catch (Throwable e) {
        }
    }

在使用WebView 即setContentView()之前調(diào)用hookWebView()方法,先Hook WebViewFactory,創(chuàng)建 sProviderInstance 對象,從而繞過系統(tǒng)檢查。經(jīng)過測試,該方案完美解決了在高版本系統(tǒng)中運行系統(tǒng)簽名的webview閃退問題;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,222評論 25 708
  • 我們認(rèn)識十年了。 生活不止有眼前的茍且,還有前任的喜帖。大紅色的喜帖格外的刺眼,一點也不亞于今天的32°大...
    糖炒栗子哦閱讀 262評論 0 1
  • 跟丈夫吵架,他的脾氣很爆裂,常常像是間接性的神經(jīng)病,隔段時間就會發(fā)作一次,把我說的極為不堪。 每次都哭到哽咽,起初...
    子川vs玄冰閱讀 158評論 2 0
  • 《春夏秋冬》四部曲之《冬》 帝都168年9月,一場蓄謀已久的戰(zhàn)爭拉開序幕,戰(zhàn)火連綿三個月,硝煙彌漫整個帝都...
    橙子Cat閱讀 369評論 2 4

友情鏈接更多精彩內(nèi)容