1. 程式人生 > >Android WebView-H5互動上傳檔案(包括圖片)

Android WebView-H5互動上傳檔案(包括圖片)

WebView 在4.4前後的區別非常大, 比如對URL跳轉的格式, 對JS的注入宣告等等, 4.4以後的WebView 已經是chromium核心, 有多強大就無需我贅述. 說這些, 其實也是為了說明也因為WebView的前後變化太大了, 所以在低版本和版本上, WebView上傳檔案的方式都略有不同, 而且在安卓4.4的一些裝置上難以保證所有機型都成功。導讀->,

  • Android HTML開啟相簿上傳照片
  • 擴充 webview 防止js注入
  • 解決 android 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