Android WebView-H5互動上傳檔案(包括圖片)
WebView 在4.4前後的區別非常大, 比如對URL跳轉的格式, 對JS的注入宣告等等, 4.4以後的WebView 已經是chromium核心, 有多強大就無需我贅述. 說這些, 其實也是為了說明也因為WebView的前後變化太大了, 所以在低版本和版本上, WebView上傳檔案的方式都略有不同, 而且在安卓4.4的一些裝置上難以保證所有機型都成功。導讀->,
第一部分:注入JS指令碼監聽,網頁中某個上傳圖片按鈕的事件。
這裡以知乎“寫文章”功能的上傳圖片為例子,如圖咱們看一下知乎上傳圖片的網頁程式碼:
(1)廢話不多說,知乎上傳圖片div標籤對應的事件HTML程式碼為:
<input type="file" accept=".jpeg, .jpg, .png" name="upload_file" id="js-title-img-input">
然後我們可以注入我們自己的JS指令碼了,來監聽事件了,相信你經過前面文章JS互動的介紹對這裡內容已經很熟了。JS指令碼如下。
String onclick = "onclick"; String[] abc = {"a", "b"}; String replaceMethd = "obj.zhihuPicclick();return false;"; String href = "href"; String replaceLink = "javascript:;"; jsFuction.append("(function()") .append("{") .append("document.getElementById('").append("js-title-img-input") .append("').href").append("=") .append("'").append(replaceLink) .append("';") .append("document.getElementById('").append("js-title-img-input") .append("').setAttribute('").append(onclick) .append("','").append(replaceMethd) .append("');") .append("})()");
這裡我給<input標籤注入了onclick事件,跟href的連結。href直接賦值=javascipt:或者javascript:void(0),目的是為了防止<input>標籤事件衝突,經過論證onclick事件的優先順序高於href。
(2)android中注入,並監聽事件
mWebView.addJavascriptInterface(new JavascriptInterface(), "obj");//或放到onPageFinshed方法。
注入以後,我們在 JavascriptInterface中的zhihuPicclick()可以監聽到我們點選知乎頁面中上傳圖片的事件,並事件自己的業務邏輯,保證篇幅不廢話,下面接著H5上傳圖片。
第2:Android4.4系統處理
Android 4.4系統的webview 被禁掉了 需要使用js和android介面互調的形式,JS通過介面呼叫 android native的方法 , 由android 上傳圖片 ,成功後獲取伺服器返回的URL地址 (也就是第一部分),再由android webview呼叫JS介面 將圖片的URL 傳給JS顯示。WebView的webkit中支援openFileChooser. 當WebView載入一個HTML頁面, 點選按鈕需要模擬form提交的方式去上傳檔案時, 就會回撥如下方法:
openFileChooser(...)
然後在這個方法裡接收並處理引數ValueCallback uploadMsg. 裡面的 uri 就是所從本地選擇的檔案路徑.
然後通過Intent的startActivityForResult(…) 方法跳轉到系統選擇檔案的介面進行檔案選擇, 最後在([通用程式碼onActiviytReslut)
onActivityResult(int requestCode, int resultCode, Intent data)
方法中獲取 data中的字串路徑, 並轉換成Uri格式, 並傳給uploadMsg 即可,也[通用程式碼onActiviytReslut]部分程式碼:
mUploadMessage.onReceiveValue(result);
這樣, 接下來的上傳工作, WebView會自動完成. 當然, 這是順利的流程, 如果 onActivityResult(…) 中返回的 resultcode 不等於 Activity.RESULT_OK , 也要做一點處理, 不然再去點選第二次上傳檔案時是沒有反應的. 類似這樣:
if (resultCode != Activity.RESULT_OK) {
if (mUploadMsg != null) {
mUploadMsg.onReceiveValue(null);
}
return;
}
傳個 null 即可。OK, 上面就是4.4 以前的實現過程,保證篇幅不再多說。
第3:Android5.0以上系統處理
android5.0及以上的sdk變動時, webkit不再支援,但是google給我們提供了一個openFileChooser(...)替代方法。
public boolean onShowFileChooser(WebView webView, ValueCallback<uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
return false;
}
這個方法的引數跟 openFileChooser(…) 方法很像, 其實作用都是類似的, 只是 從 ValueCallback uploadMsg 變成了 ValueCallback filePathCallback, 還多了FileChooserParams型別引數. fileChooserParams 其實是一個屬性封裝的引數, 裡面包含了acceptType, title等這樣的檔案屬性, 名稱等資訊,並且這裡可以上傳多張圖片,因為是複數的陣列了。
所以, onShowFileChooser(…) 的使用方法跟 openFileChooser(…) 是很類似的, 這裡看兩者的使用區別:
private class MyWebClient extends WebChromeClient {
// For Android 5.0+
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> valueCallback,
android.webkit.WebChromeClient.FileChooserParams fileChooserParams) {
//開啟相簿
}
// For Android 3.0+
public void openFileChooser(ValueCallback uploadMsg) {
//開啟相簿
}
//3.0--版本
public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
openFileChooser(uploadMsg);
}
// For Android 4.1
public void openFileChooser(ValueCallback uploadMsg, String acceptType,
String capture) {
openFileChooser(uploadMsg);
}
}
最後我們通過 onActivityResult(…) ,也就是[通用程式碼onActiviytReslut]把圖片顯示出來。
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
if (mUploadMsg != null) {
mUploadMsg.onReceiveValue(null);
}
if (mUploadMsgForAndroid5 != null) { // for android 5.0+
mUploadMsgForAndroid5.onReceiveValue(null);
}
return;
}
switch (requestCode) {
case REQUEST_CODE_IMAGE_CAPTURE:
case REQUEST_CODE_PICK_IMAGE: {
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
if (mUploadMsg == null) {
return;
}
String sourcePath = ImageUtil.retrievePath(this, mSourceIntent, data);
if (TextUtils.isEmpty(sourcePath) || !new File(sourcePath).exists()) {
Log.e(TAG, "sourcePath empty or not exists.");
break;
}
Uri uri = Uri.fromFile(new File(sourcePath));
mUploadMsg.onReceiveValue(uri);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (mUploadMsgForAndroid5 == null) { // for android 5.0+
return;
}
String sourcePath = ImageUtil.retrievePath(this, mSourceIntent, data);
if (TextUtils.isEmpty(sourcePath) || !new File(sourcePath).exists()) {
Log.e(TAG, "sourcePath empty or not exists.");
break;
}
Uri uri = Uri.fromFile(new File(sourcePath));
mUploadMsgForAndroid5.onReceiveValue(new Uri[]{uri});
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
}
在網上了解到,這裡需要注意幾點內容,當然我並沒有遇到。
- 上傳的給圖片的路徑必須是Uri格式,在實際測試中發現,大部分手機能識別file開頭的uri,但有少部分手機,如魅族,vivo不識別該種uri格式,只識別content開頭的uri,因此,需要做相容處理,呼叫的系統圖庫,預設返回的uri為content開頭,相容較好,自定義相簿則為file開頭。
- 在onActivityResult 中獲取所選圖片 uri,回撥給 H5,有點小缺陷,圖片可能需要壓縮處理,因為在H5載入 base64本地圖片是耗時操作,要先壓縮圖片。
4,程式碼混淆
這個我覺得有些意思啊,混淆後反編譯別人APK的時候會給對手增加一些難度,你思考一件事情,如果你去反編譯別人apk的時候,用apktools,dex2.dex.jar什麼鬼的,但是很不幸,都是abcefgh,那你應該怎麼辦?留個坑在這裡,待填
在混淆檔案中新增
-keepclassmembers class * extends android.webkit.WebChromeClient {
public void openFileChooser(...);
}
Android studio 使用者混淆檔案: proguard-rules.pro
eclipse 使用者混淆檔案: proguard-android.txt