Android WebView 運行在系統(tǒng)進程引發(fā)的異常

因為最近有個需求是在系統(tǒng)應用中使用 WebView崭孤,所以配置了 android:sharedUserId="android.uid.system", 讓應用共享系統(tǒng)進程沛膳。但是測試的時候就 crash 了褐鸥,我表示有點方...錯誤日志是這樣的:

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ǒng)進程里面使用 WebView狠毯,異常是在 WebView 初始化的時候拋出的护糖,想要解決這個問題還要看源碼(Read the fucking source code)。

這是 Android 5.1(API 22) 里面的類 WebViewFactory 的 getProvider 方法源碼:

    static WebViewFactoryProvider getProvider() {
        synchronized (sProviderLock) {
            // For now the main purpose of this function (and the factory abstraction) is to keep
            // us honest and minimize usage of WebView internals when binding the proxy.
            if (sProviderInstance != null) return sProviderInstance;

            final int uid = android.os.Process.myUid();
            if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
                throw new UnsupportedOperationException(
                        "For security reasons, WebView is not allowed in privileged processes");
            }

            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
            try {
                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
                loadNativeLibrary();
                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);

                Class<WebViewFactoryProvider> providerClass;
                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getFactoryClass()");
                try {
                    providerClass = getFactoryClass();
                } catch (ClassNotFoundException e) {
                    Log.e(LOGTAG, "error loading provider", e);
                    throw new AndroidRuntimeException(e);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                }

                StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
                try {
                    try {
                        sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
                                .newInstance(new WebViewDelegate());
                    } catch (Exception e) {
                        sProviderInstance = providerClass.newInstance();
                    }
                    if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
                    return sProviderInstance;
                } catch (Exception e) {
                    Log.e(LOGTAG, "error instantiating provider", e);
                    throw new AndroidRuntimeException(e);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                    StrictMode.setThreadPolicy(oldPolicy);
                }
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
            }
        }
    }

可以看出嚼松,首次使用時,系統(tǒng)會進行檢查锰扶,如果 UID 是 root 進程或者系統(tǒng)進程献酗,直接拋出異常。sProviderInstance 是 WebViewFactoryProvider 的對象坷牛,主要提供創(chuàng)建 WebView 內(nèi)核的機制罕偎。WebView在 Android 4.4 之前使用的是 Webkit 內(nèi)核,在 Android 4.4 以后切換到了 Chromium 內(nèi)核京闰。Google 使用了工廠方法模式颜及,優(yōu)雅地切換 WebView 內(nèi)核的實現(xiàn)方式甩苛。我們注意到只有 sProviderInstance 為空的時候系統(tǒng)才去檢查進程,然后創(chuàng)建 sProviderInstance對象俏站。所以這給了我們一個啟發(fā) ---- 能不能一開始就主動創(chuàng)建 sProviderInstance 對象讯蒲,把她塞到 WebViewFactory 類里面,從而欺騙 API 繞過系統(tǒng)檢查呢肄扎?

下面就要用到 Hook 的思想了墨林,首先要找到一個合適的點,靜態(tài)變量犯祠、單例是最佳選擇旭等,剛剛好 sProviderInstance 是靜態(tài)的。那就開始拿它開刀衡载,看看系統(tǒng)是怎么創(chuàng)建 sProviderInstance 的搔耕,我們自己也模仿它這么做。其實系統(tǒng)也是通過反射來做的痰娱,這是 getFactoryClass 的源碼弃榨,我們來看看。

    private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
        Application initialApplication = AppGlobals.getInitialApplication();
        try {
            // First fetch the package info so we can log the webview package version.
            String packageName = getWebViewPackageName();
            sPackageInfo = initialApplication.getPackageManager().getPackageInfo(packageName, 0);
            Log.i(LOGTAG, "Loading " + packageName + " version " + sPackageInfo.versionName +
                          " (code " + sPackageInfo.versionCode + ")");

            // Construct a package context to load the Java code into the current app.
            Context webViewContext = initialApplication.createPackageContext(packageName,
                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
            initialApplication.getAssets().addAssetPath(
                    webViewContext.getApplicationInfo().sourceDir);
            ClassLoader clazzLoader = webViewContext.getClassLoader();
            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 (PackageManager.NameNotFoundException 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。這個方法是靜態(tài)的而姐,我們就可以用反射調(diào)用了腊凶。整個創(chuàng)建 sProviderInstance 的過程都可以用反射搞定,其他細節(jié)就不多說了拴念。需要注意的是 API 21 以上在使用 WebView 時系統(tǒng)才會檢查進程钧萍。但是 API 22 和 22 以上源碼還是有差別,這里只是方法名字的改動政鼠,我們根據(jù)版本處理一下就好风瘦。

    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.debug("sProviderInstance isn't null");
                return;
            }
            Method getProviderClassMethod;
            if (sdkInt > 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
            } else if (sdkInt == 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
            } else {
                log.info("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());
                log.debug("sProviderInstance:{}", sProviderInstance);
                field.set("sProviderInstance", sProviderInstance);
            }
            log.debug("Hook done!");
        } catch (Throwable e) {
            log.error(e);
        }
    }

在使用 WebView 之前,我們先 Hook WebViewFactory公般,創(chuàng)建 sProviderInstance 對象万搔,從而繞過系統(tǒng)檢查。經(jīng)過測試官帘,該方案完美解決了我們的問題 ~

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞬雹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子刽虹,更是在濱河造成了極大的恐慌酗捌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胖缤,居然都是意外死亡尚镰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門哪廓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狗唉,“玉大人,你說我怎么就攤上這事撩独〕ú埽” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵综膀,是天一觀的道長澳迫。 經(jīng)常有香客問我,道長剧劝,這世上最難降的妖魔是什么橄登? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮讥此,結果婚禮上拢锹,老公的妹妹穿的比我還像新娘。我一直安慰自己萄喳,他們只是感情好卒稳,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著他巨,像睡著了一般充坑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上染突,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天捻爷,我揣著相機與錄音,去河邊找鬼份企。 笑死也榄,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的司志。 我是一名探鬼主播甜紫,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼骂远!你這毒婦竟也來了棵介?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤吧史,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贸营,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡吨述,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了钞脂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揣云。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖冰啃,靈堂內(nèi)的尸體忽然破棺而出邓夕,到底是詐尸還是另有隱情,我是刑警寧澤阎毅,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布焚刚,位于F島的核電站,受9級特大地震影響扇调,放射性物質(zhì)發(fā)生泄漏矿咕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一狼钮、第九天 我趴在偏房一處隱蔽的房頂上張望碳柱。 院中可真熱鬧,春花似錦熬芜、人聲如沸莲镣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瑞侮。三九已至,卻和暖如春曼库,著一層夾襖步出監(jiān)牢的瞬間区岗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工毁枯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留慈缔,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓种玛,卻偏偏與公主長得像藐鹤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子赂韵,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容