WebView 封裝

WebView 是 Android 最復(fù)雜以及最強大的一個控件(最多坑) 儡遮, 一大堆的 setting 讓人摸不著頭腦 乳蛾, 很多時候壓根不知道這個設(shè)置有什么用 ,加上 WebViewClient 和 WebChromeClient 做為內(nèi)部類 , 一堆業(yè)務(wù)邏輯 肃叶, 使得 Activity 變得亂糟糟的 蹂随,代碼可讀性更是糟糕透了 , 最后被逼上梁山 因惭, 走上了封裝的道路 岳锁。

WebView 封裝思路

對于 WebView 的封裝 , 相信很多人都是抽象在一個基類里面 蹦魔, 封裝成一個 BaseWebActivity , 或者 BaseWebFragment 激率, 對于這種封裝還是不能滿足像我這種有潔癖有程序員的 , 因為復(fù)用性不高 勿决, 而且容易導(dǎo)致 Activity 或者 Fragment 基類膨脹 乒躺。 下面向大家分享我的封裝思路。

首先讓大家看下我封裝的效果

mAgentWeb = AgentWeb.with(this)//傳入Activity
                .setAgentWebParent(mLinearLayout, new LinearLayout.LayoutParams(-1, -1))//傳入AgentWeb 的父控件 低缩,如果父控件為 RelativeLayout 嘉冒, 那么第二參數(shù)需要傳入 RelativeLayout.LayoutParams
                .useDefaultIndicator()// 使用默認(rèn)進度條
                .defaultProgressBarColor() // 使用默認(rèn)進度條顏色
                .setReceivedTitleCallback(mCallback) //設(shè)置 Web 頁面的 title 回調(diào)
                .createAgentWeb()//
                .ready()
                .go("http://www.jd.com");

效果圖

jd.png

上面已經(jīng)封裝成一個 Web 庫了 , 叫 AgentWeb 咆繁, 歡迎大家使用 讳推。

可以看到里面沒有一句 WebSettings , 甚至 WebChromeClient 和 WebViewClient 都不用配置 玩般, 使用的是簡潔鏈?zhǔn)秸{(diào)用 银觅。

AgentWeb 封裝思路是通過代理 , 將 WebView 從 Activity 或者 Fragment 中代理出來 壤短, 不再需要 Activity 或者 Fragment 內(nèi)部創(chuàng)建和管理 设拟,Activity 管理 WebView 需要通過 AgentWeb , 下面通過 UML 圖來簡單說明下 .

BaseActivity 封裝使用的 UML 關(guān)系圖

common_.png

AgentWeb 封裝使用的 UML 關(guān)系圖

agentweb結(jié)構(gòu).png

BaseWebActivity 直接組合 WebView 久脯, 這樣做為什么說復(fù)用性不高呢 纳胧? 主要還是因為 WebView 依附在 BaseWebActivity 身上 ,要別人直接繼承你的 Activity 是很不好的 帘撰,因為 Java 的單繼承關(guān)系 跑慕, 使得使用基類的靈活性受到很大的約束 , 這也是 Effective Java 里面提到的組合優(yōu)先于繼承 摧找。

AgentWeb 則不同 核行, AgentWeb 是一個獨立的庫 , 可以讓你很方便一句話就引入 蹬耘, 不需要依賴 BaseWebActivity 芝雪, 就像上面一樣簡簡單單一句話引入即可。

AgentWeb 把 WebView 代理出來 综苔, 將功能細(xì)分成一個類去管理 惩系, 比如說的 WebCreator 負(fù)責(zé)創(chuàng)建 WebView 以及 進度條 位岔、WebSettings 則是對 WebView 進行統(tǒng)一設(shè)置 , JsEntraceAccess 是對 Javascript 方法訪問進行統(tǒng)一入口 堡牡, 這樣做使得每一個功能獨立 抒抬, 相互不影響 , 也使得 AgentWeb 的結(jié)構(gòu)更清晰 晤柄, 符合單一職責(zé)原則 擦剑。 源碼太多就不貼了 ,下面分享下封裝 WebView 遇到的一些問題 芥颈。

WebView 封裝的一些問題與解決思路

WebView 的封裝可謂真是一波三折啊 惠勒, WebView 實在太多坑了 , 比如說 常見的泄露 浇借, Js 安全 捉撮,低版本跨源問題 , Context 引致的 onJsAlert 失效 妇垢,Android 4.4 不支持文件選擇問題等等巾遭。

內(nèi)存泄露

這個問題在低版本不好解決就算類似下面代碼通過反射制空 sConfigCallback 該字段, 還是有些手機會出現(xiàn)泄露 闯估,對于該問題 灼舍,唯一有效方案解決在 AndroidManifest 里面為 Web Activity 添加 android:process=":web" 屬性 , 然后在 該 Activity onDestroy 里面 執(zhí)行 System.exit(0); 下面可以解決一部分泄露

                Field field = WebView.class.getDeclaredField("mWebViewCore");
                field = field.getType().getDeclaredField("mBrowserFrame");
                field = field.getType().getDeclaredField("sConfigCallback");
                field.setAccessible(true);
                field.set(null, null);      

addJavascriptInterface API 引起的遠(yuǎn)程代碼執(zhí)行漏洞

對于這個問題 涨薪,我相信大家或多或少都有點了解 骑素,問題是由注入類引起 ,從注入類中找到 Runtime 對象 刚夺,可以通過 Runtime 執(zhí)行 shell 命令 献丑。 Google 只針對 Android 4.2.2 版本及以上版本給出了解決方法 ,為了解決兼容 4.2.2 以下版本這個問題 AgentWeb 采用 360 大牛給出的方案 侠姑, 向 Web 頁面注入一段 Js 腳本 创橄,然后通過腳本彈 Prompt 向 Java 通信 ,解決了 4.2.2 以下版本 addJavascriptInterface 安全通信問題 莽红。 下面給出大致實現(xiàn)

比如下面注入類

public class AndroidInterface {   
    public void callAndroid(final String msg) {
            Log.i("Info",""+msg);
    }
}
 mAgentWebView.addJavascriptInterface("android",new AndroidInterface()); //注意 mAgentWebView 是 WebView 子類 妥畏, 并重寫了 addJavascriptInterface 方法 。

低于 Android 4.2.2 的版本上面的注入對象會經(jīng)過包裝拼接成如下腳本 安吁。

注入的腳本

.....省略N多 Js 代碼
android.callAndroid = function() {
        .....省略N多 Js 代碼
        var m = prompt('AgentWeb: ' + JSON.stringify({method: l, types: e, args: f}));
        var g = JSON.parse(m);
        if (g.code != 200) {
            throw "Android call error, code:" + g.code + ", message:" + g.result
        }
        return g.result
    };

通過 webView.loadUrl(腳本) 注入上面的腳本 醉蚁。

Js 執(zhí)行下面方法

function sendHelloToAndroid() {    
        window.android.callAndroid("你好,Android! ");
  }

就會執(zhí)行上面的 function 里面的方法體 鬼店,Android 端回調(diào)onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) message 參數(shù)里面取出 js 要調(diào)的目標(biāo)方法 网棍, 然后通過反射調(diào)用該目標(biāo)方法 。

對于同源跨域攻擊問題

什么是同源策略嗎 妇智? 同源策略是由 Netscape 提出來的 滥玷,現(xiàn)在主流的瀏覽器都遵循這種策略 捌锭,同源是一般指http://(協(xié)議)www.google.com(主機):8080 (端口) 三要素都相同 ,但實際上并不是那么嚴(yán)格 罗捎, 比如 IE 就會忽略對端口的判斷 。

同源有什么作用嗎 拉盾?
同源的數(shù)據(jù)默認(rèn)為可以安全訪問的 桨菜。 比如說 url http://www.google.com:xxxx/login 登錄后瀏覽器就會把返回來的 cookies 保存起來 , 對于 http://www.google.com:xxxx/index.html 對于這個 url 瀏覽器會默認(rèn)為跟前者同源 捉偏, 那么這個 url 可以無縫的訪問到 login 保存下來的 cookies 倒得。

大家都知道 Android 應(yīng)用之間的文件和數(shù)據(jù)一般情況下是不能相互訪問的 , 但是不正確的使用 WebView 夭禽,會打破這種狀況 霞掺, 比如說以下這種使用

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview);
        webView = (WebView) findViewById(R.id.webView1);
        webView.getSettings().setAllowFileAccess(true);                    
        webView.getSettings().setJavaScriptEnabled(true);                   
        webView.getSettings().setAllowFileAccessFromFileURLs(true);       
        webView.getSettings().setAllowUniversalAccessFromFileURLs(true); 
        Intent i = getIntent();
        String url = i.getData().toString(); 
        webView.loadUrl(url);
    }

將該 Activity 設(shè)置 exported="true" , 其他應(yīng)用就可以通過隱式啟動 讹躯,將 data 作為 url 菩彬,啟動的該應(yīng)用 , 讓該應(yīng)用加載腳本 潮梯,遍歷該應(yīng)用內(nèi)的文件或者私密文件 骗灶, 上傳服務(wù)器 。

這個問題一直存在 Android 手機中 秉馏,這個問題 Google 并沒有修復(fù)它 耙旦, 只是在 4.2 后的版本把 setAllowFileAccessFromFileURLs 以及 mWebSettings.setAllowUniversalAccessFromFileURLs 設(shè)置為 false , 用戶沒有刻意去開啟它 萝究, 高于 Android 4.2 版本默認(rèn)是安全的 免都。 對于該問題 AgentWeb 使用的是以下設(shè)置 。

        mWebSettings.setAllowFileAccess(true); //允許file 協(xié)議 帆竹, 加載本地文件  
        mWebSettings.setAllowFileAccessFromFileURLs(false); //禁止通過 file url 加載的文件執(zhí)行 Javascript 讀取其他的本地文件 .
        mWebSettings.setAllowUniversalAccessFromFileURLs(false);//禁止通過 file url 加載的文件執(zhí)行 Javascript 可以訪問其他的源 如  http 绕娘, https 。

Context 引致的 onJsAlert 失效

這個問題根本原因在防止泄露時候創(chuàng)建 new WebView(activity.getApplicationContext()); 導(dǎo)致 馆揉, 很正常啊 业舍, 因為你創(chuàng)建 WebView 傳的是 Application , Application 本身是無法彈 Dialog 的 升酣。 所以只能無反應(yīng) 舷暮!這個問題解決方案只要你創(chuàng)建 WebView 時候傳入 Activity , 或者 自己實現(xiàn) onJsAlert 方法即可噩茄。

Android 4.4 文件訪問

Android 4.4 WebView 內(nèi)核正式有 WebKit 替換 為 Chromium 使得很多 Api 都廢棄掉了 下面, 這是 Google 正式宣告拋棄 Webkit 的一個句號 。 所以要兼容 Android 4.4 以下的 WebView 的應(yīng)用特別難受 绩聘。 回到正題 沥割, 4.4 文件訪問 耗啦, 你會發(fā)現(xiàn) 4.4 點擊 input 標(biāo)簽沒反應(yīng) , 瞬間一萬只曹尼瑪在崩騰 机杜, 幸慶的是還有 Js 通信 帜讲,解決方案:可以通能過 Js 訪問 Java 然后打開文件選擇器 , 拿到文件后 椒拗, 將文件轉(zhuǎn)成 Base64 字符串回傳給 Js 似将, 因為拿到的文件路徑是 Content:// 開頭 JS 是無法解析的 。(這個代碼跨度有點大 蚀苛, 就不貼源碼了 在验, 有興趣可以克隆倉庫看下)

WebView 封裝后使用

App 下載

AgentWeb 在 Fragment 中使用

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mAgentWeb = AgentWeb.with(this)// Fragment  傳入
                .setAgentWebParent((LinearLayout) view, new LinearLayout.LayoutParams(-1, -1))// 設(shè)置 AgentWeb 的父控件 , 這里的view 是 LinearLayout 堵未, 那么需要傳入 LinearLayout.LayoutParams
                .useDefaultIndicator()// 使用默認(rèn)進度條
                .setReceivedTitleCallback(mCallback) //標(biāo)題回調(diào)
                .setSecurityType(AgentWeb.SecurityType.strict) //注意這里開啟 strict 模式 腋舌, 設(shè)備低于 4.2 情況下回把注入的 Js 全部清空掉 , 這里推薦使用 onJsPrompt 通信
                .createAgentWeb()//
                .ready()//
                .go(getUrl());
        
    }

跟原先 WebFragment 比簡潔多了 渗蟹。

Js 調(diào)用 块饺。

function callByAndroid(msg1,msg2){
      console.log("callByAndroid")
  }

沒有經(jīng)過封裝的

mWebView.loadUrl("javascript:callByAndroid("+"\"hello\""+","+"\" js\""+")");

封裝后

mAgentWeb.getJsEntraceAccess().quickCallJs("callByAndroid","Hello","js");

Github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市雌芽,隨后出現(xiàn)的幾起案子刨沦,更是在濱河造成了極大的恐慌,老刑警劉巖膘怕,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件想诅,死亡現(xiàn)場離奇詭異,居然都是意外死亡岛心,警方通過查閱死者的電腦和手機来破,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忘古,“玉大人徘禁,你說我怎么就攤上這事∷杩埃” “怎么了送朱?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長干旁。 經(jīng)常有香客問我驶沼,道長,這世上最難降的妖魔是什么争群? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任回怜,我火速辦了婚禮,結(jié)果婚禮上换薄,老公的妹妹穿的比我還像新娘玉雾。我一直安慰自己翔试,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布复旬。 她就那樣靜靜地躺著垦缅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驹碍。 梳的紋絲不亂的頭發(fā)上失都,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音幸冻,去河邊找鬼。 笑死咳焚,一個胖子當(dāng)著我的面吹牛洽损,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播革半,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼碑定,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了又官?” 一聲冷哼從身側(cè)響起延刘,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎六敬,沒想到半個月后碘赖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡外构,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年普泡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片审编。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡撼班,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出垒酬,到底是詐尸還是另有隱情砰嘁,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布勘究,位于F島的核電站矮湘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏口糕。R本人自食惡果不足惜板祝,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧褐耳,春花似錦每瞒、人聲如沸匾寝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽被盈。三九已至貌夕,卻和暖如春炸枣,著一層夾襖步出監(jiān)牢的瞬間虏等,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工适肠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留霍衫,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓侯养,卻偏偏與公主長得像敦跌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逛揩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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