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

image.png

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


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

實(shí)現(xiàn)分析:

  • webView 實(shí)現(xiàn)

webView 簡(jiǎn)單使用參考:
Android開(kāi)發(fā):最全面晤愧、最易懂的Webview使用詳解

開(kāi)發(fā)平臺(tái)

Android 4.4 項(xiàng)目平臺(tái)開(kāi)發(fā)麻捻,如期開(kāi)發(fā)完成,開(kāi)發(fā)效果如下:

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

適配致命問(wèn)題

其他項(xiàng)目平臺(tái)(Android 6.0)運(yùn)行拉鹃,直接閃退窘疮,打印如下:


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

【問(wèn)題分析】:

  • Android 4.4 平臺(tái)正常運(yùn)行,Android 6.0 提示 XMl中 WebView 加載失敗炫乓,難道Android 6.0對(duì)于webview中格式要求嚴(yán)格些(以前遇到過(guò)類似問(wèn)題)刚夺?

【嘗試解決方案】:

  • 加載WebView xml布局檢查與分析,加載xml 中 WebView 照樣失斈┑贰侠姑;
  • 既然加載 xml 中 webview失敗,同事建議采用Java 代碼動(dòng)態(tài)加載webview箩做,無(wú)需加載xml 中 WebView 控件莽红,就不會(huì)出現(xiàn)上述閃退打印錯(cuò)誤(曲線救國(guó),心中泛起了一絲笑容)

測(cè)試Java 動(dòng)態(tài)加載webview ,Android 6.0 運(yùn)行安吁,繼續(xù)閃退醉蚁,串口查詢錯(cuò)誤日志:

 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)

動(dòng)態(tài)加載webview 錯(cuò)誤日志大概意思:出于安全的角度,webview 不允許運(yùn)行在特許進(jìn)程(系統(tǒng)進(jìn)程)鬼店,納悶了网棍,webview 憑什么不允許運(yùn)行在系統(tǒng)進(jìn)程,Android 4.4 運(yùn)行的不是好好的嗎妇智?

尋求萬(wàn)能 Google 與 stackoverflow Help滥玷,在查閱到很多人遇到了此問(wèn)題,分析apk 使用了android:sharedUserId="android.uid.system"申請(qǐng)了系統(tǒng)權(quán)限巍棱,去掉即可解決問(wèn)題惑畴,(沒(méi)有解釋原因)自己嘗試寫(xiě)了一個(gè)Demo,確實(shí)能規(guī)避問(wèn)題航徙。
提取Google分析出來(lái)的解決方案:

  1. 去掉android:sharedUserId="android.uid.system"
  2. 單獨(dú)寫(xiě)一個(gè)apk 運(yùn)行webview

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

雖然實(shí)現(xiàn)了功能,每次出設(shè)置需要出2個(gè)apk夭禽,麻煩霞掺。去掉申請(qǐng)系統(tǒng)權(quán)限可以正常運(yùn)行webview原因不知道,利用閑余時(shí)間查Google與查看webview源碼讹躯,WebViewFactory getProvider() 方法中異常打印正是閃退異常打悠斜颉:

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

源碼可以看出,首次使用webview時(shí)潮梯,系統(tǒng)會(huì)檢測(cè)uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UIDuid 為系統(tǒng)進(jìn)程或者root進(jìn)程骗灶,直接拋出異常。

為什么會(huì)有這種安全機(jī)制呢秉馏?因?yàn)閣ebview允許運(yùn)行js耙旦,如果用戶通過(guò)js注入安全代碼,那么js就可以肆無(wú)忌憚的使用系統(tǒng)權(quán)限萝究,這無(wú)疑是一個(gè)漏洞免都,可謂門(mén)戶大開(kāi)。

聯(lián)想到在framework層去掉加載webview時(shí)候?qū)ο到y(tǒng)進(jìn)程與root進(jìn)程檢測(cè)判斷帆竹,(實(shí)現(xiàn)較為復(fù)雜绕娘,每個(gè)項(xiàng)目都需去掉,且不知是否會(huì)引出其他異常栽连,沒(méi)有驗(yàn)證险领,后續(xù)有時(shí)間去驗(yàn)證侨舆。。绢陌。)

拋出異常之前會(huì)檢測(cè)sProviderInstance是否為空:
if (sProviderInstance != null) return sProviderInstance;
sProviderInstance 是WebViewFactoryProvider 對(duì)象挨下,提供創(chuàng)建webview內(nèi)核機(jī)制。WebView Android 4.4 之前使用內(nèi)核webkit下面,Android 5.0 之后使用chromium內(nèi)核复颈,Google 使用了工廠方法模式,動(dòng)態(tài)切換內(nèi)核實(shí)現(xiàn)方式沥割。能否一開(kāi)始就主動(dòng)創(chuàng)建sProviderInstance耗啦,放到WebViewFactory中,從而達(dá)到欺騙API 繞過(guò)系統(tǒng)檢查机杜?

聯(lián)想到Hook思想:
Hook 英文翻譯過(guò)來(lái)就是「鉤子」的意思帜讲,那我們?cè)谑裁磿r(shí)候使用這個(gè)「鉤子」呢?在 Android 操作系統(tǒng)中系統(tǒng)維護(hù)著自己的一套事件分發(fā)機(jī)制椒拗。應(yīng)用程序似将,包括應(yīng)用觸發(fā)事件和后臺(tái)邏輯處理,也是根據(jù)事件流程一步步地向下執(zhí)行蚀苛。而「鉤子」的意思在验,就是在事件傳送到終點(diǎn)前截獲并監(jiān)控事件的傳輸,像個(gè)鉤子鉤上事件一樣堵未,并且能夠在鉤上事件時(shí)腋舌,處理一些自己特定的事件。

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

參考:Hook技術(shù)及其簡(jiǎn)單實(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);
        }
    }

返回值是一個(gè) WebViewFactoryProvider 的類,可以看到系統(tǒng)會(huì)首先加載 CHROMIUM_WEBVIEW_FACTORY雌芽,也就是使用 Chrome 內(nèi)核的 WebView授艰,整個(gè)創(chuàng)建 sProviderInstance 的過(guò)程都可以用反射搞定,自己寫(xiě)一個(gè)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 對(duì)象屉佳,從而繞過(guò)系統(tǒng)檢查来破。經(jīng)過(guò)測(cè)試,該方案完美解決了在高版本系統(tǒng)中運(yùn)行系統(tǒng)簽名的webview閃退問(wèn)題忘古;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市诅诱,隨后出現(xiàn)的幾起案子髓堪,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件干旁,死亡現(xiàn)場(chǎng)離奇詭異驶沼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)争群,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)回怜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人换薄,你說(shuō)我怎么就攤上這事玉雾。” “怎么了轻要?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵复旬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我冲泥,道長(zhǎng)驹碍,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任凡恍,我火速辦了婚禮志秃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嚼酝。我一直安慰自己浮还,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布革半。 她就那樣靜靜地躺著碑定,像睡著了一般。 火紅的嫁衣襯著肌膚如雪又官。 梳的紋絲不亂的頭發(fā)上延刘,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音六敬,去河邊找鬼碘赖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛外构,可吹牛的內(nèi)容都是我干的普泡。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼审编,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼撼班!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起垒酬,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤砰嘁,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后矮湘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磕蛇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了十办。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秀撇。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡橘洞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炸枣,到底是詐尸還是另有隱情虏等,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布适肠,位于F島的核電站霍衫,受9級(jí)特大地震影響侯养,放射性物質(zhì)發(fā)生泄漏敦跌。R本人自食惡果不足惜逛揩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惧笛。 院中可真熱鬧逞泄,春花似錦患整、人聲如沸喷众。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)憔四。三九已至愈涩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背斟览。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留已烤,地道東北人妓羊。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像躁绸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子净刮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,863評(píng)論 25 707
  • 我們認(rèn)識(shí)十年了淹父。 生活不止有眼前的茍且,還有前任的喜帖困介。大紅色的喜帖格外的刺眼,一點(diǎn)也不亞于今天的32°大...
    糖炒栗子哦閱讀 194評(píng)論 0 1
  • 跟丈夫吵架座哩,他的脾氣很爆裂捡鱼,常常像是間接性的神經(jīng)病,隔段時(shí)間就會(huì)發(fā)作一次驾诈,把我說(shuō)的極為不堪。 每次都哭到哽咽乍迄,起初...
    子川vs玄冰閱讀 107評(píng)論 2 0
  • 《春夏秋冬》四部曲之《冬》 帝都168年9月闯两,一場(chǎng)蓄謀已久的戰(zhàn)爭(zhēng)拉開(kāi)序幕褥伴,戰(zhàn)火連綿三個(gè)月,硝煙彌漫整個(gè)帝都...
    橙子Cat閱讀 308評(píng)論 2 4