【Android開發(fā)技巧】 關(guān)于Webview拍照或從相冊上傳圖片處理總結(jié)

前言:
各公司為了處理更多的業(yè)務流程, 一般都會加入H5與原生交互處理,方便快速開發(fā),迭代項目。但,在Android中,H5與原生的交互處理的就沒有iOS那么好。其中適配也是一個問題,Android系統(tǒng)版本眾多,國內(nèi)手機開發(fā)商都各自定制自家的系統(tǒng),所以適配起來的話,也是一個不小的工作量。本文就總結(jié)一下我本人在公司項目使用到Webview中上傳圖片的處理。

WebView 上傳圖片, 想必很多人都碰到過這樣的場景. 而且 WebView 在4.4前后的區(qū)別非常大, 比如對URL跳轉(zhuǎn)的格式, 對JS的注入聲明等等, 4.4以后的WebView 已經(jīng)是chromium內(nèi)核, 有多強大就無需我贅述. 說這些, 其實也是為了說明也因為WebView的前后變化太大了, 所以在低版本和版本上, WebView上傳文件的方式都略有不同, 而且在安卓4.4.4的一些設(shè)備上難以保證所有機型都成功。

111_看圖王.png
222222_看圖王.jpg

實現(xiàn)過程: 在Android4.4之前,Webview的webkit中支持openFileChooser.當Webview加載一個HTML頁面,點擊按鈕需要模擬form提交的方式去上傳文件時,就會回調(diào):
openFileChooser(...)

然后在這個方法里接受并處理參數(shù)ValueCallback uploadMsg. 里面的 uri 就是所從本地選擇的文件路徑.

然后通過Intent的startActivityForResult(…) 方法跳轉(zhuǎn)到系統(tǒng)選擇文件的界面進行文件選擇, 最后在:
onActivityResult(int requestCode, int resultCode, Intent data)

方法中獲取data中的字符串路徑, 并轉(zhuǎn)換成Uri 格式,并傳給uploadMsg 即可, 類似:

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);

這樣, 接下來的上傳工作, WebView會自動完成. 當然, 這是順利的流程, 如果 onActivityResult(…) 中返回的 resultcode 不等于 Activity.RESULT_OK , 也要做一點處理, 不然再去點擊第二次上傳文件時是沒有反應的. 類似這樣:

if (resultCode != Activity.RESULT_OK) { 
    if (mUploadMsg != null) { 
        mUploadMsg.onReceiveValue(null);
 } 
    return;
}

傳個null 即可 。
OK, 上面就是4.4 以前的實現(xiàn)過程, 上面提及的代碼在下載的demo里會有, 為保證篇幅不會放在文章里. 那么5.0及以上的sdk變動時, webkit不再支持 :

openFileChooser(...)

而是提供了一個代替的方法:

public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
 return false;
}

這個方法的參數(shù)跟 openFileChooser(…) 方法很像, 其實作用都是類似的, 只是 從

ValueCallback <Uri>uploadMsg 變成了 ValueCallback<Uri[]> filePathCallback,

還多了FileChooserParams類型參數(shù). fileChooserParams 其實是一個屬性封裝的參數(shù), 里面包含了acceptType, title等這樣的文件屬性, 名稱等信息.

所以, onShowFileChooser(…) 的使用方法跟 openFileChooser(…) 是很類似的, 這里通過 onActivityResult(…) 看兩者的使用區(qū)別:

@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;
        }
    }
}

主要是針對 5.0前后的系統(tǒng)做了一些區(qū)別處理, 其他無大異.

提高兼容性的解決方案:

  1. 使用加強版的WebView, cordova , 可以考慮編譯這個項目獲得jar包, 然后導入項目中使用它的WebView組件.
  2. 也可以通過JS調(diào)用本地的方法自行實現(xiàn)上傳.
    以上兩個方法的兼容性都相當不錯, 可自行嘗試.

可能導致失敗的原因: 如果選擇文件到上傳文件的過程中失敗, 有可能是以下原因?qū)е碌? 1. 文件的路徑包含中文. (9部設(shè)備中, vivo X6D不支持中文路徑包含中文) 2. 手機系統(tǒng)是Android 6.0以上 (API >= 23), 且沒有獲得文件和攝像頭權(quán)限.

如果你的項目中還兼容到4.0以下的版本, build.gradle 配置文件中的compileSdkVersion 和 targetSdkVersion 是16甚至更低, 那么恭喜你, 直接使用 openFileChooser(…) 這種處理方法即可.

主要類:MyWebChomeClient

/**
 * MyWebChomeClient
 */
public class MyWebChomeClient extends WebChromeClient {

    private OpenFileChooserCallBack mOpenFileChooserCallBack;

    public MyWebChomeClient(OpenFileChooserCallBack openFileChooserCallBack) {
        mOpenFileChooserCallBack = openFileChooserCallBack;
    }

    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
        mOpenFileChooserCallBack.openFileChooserCallBack(uploadMsg, acceptType);
    }

    public void openFileChooser(ValueCallback<Uri> uploadMsg) {
        openFileChooser(uploadMsg, "");
    }

    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
        openFileChooser(uploadMsg, acceptType);
    }

    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                     FileChooserParams fileChooserParams) {
        return mOpenFileChooserCallBack.openFileChooserCallBackAndroid5(webView, filePathCallback, fileChooserParams);
    }

    public interface OpenFileChooserCallBack {
        // for API - Version below 5.0.
        void openFileChooserCallBack(ValueCallback<Uri> uploadMsg, String acceptType);

        // for API - Version above 5.0 (contais 5.0).
        boolean openFileChooserCallBackAndroid5(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                                FileChooserParams fileChooserParams);
    }

參考文章:
WebView File Upload Over Kitkat
HTML file input in android webview (android 4.4, kitkat)
CORDOVA Android WebViews
使用Cordova來解決HTML5制作的WebView手機不兼容的問題
Android使用WebView從相冊/拍照中添加圖片

---------------- 2017-07-25 Update ----------------
適配 Android7.0相機拍照功能FileUriExposedException導致的問題

拍照
  1. 在AndroidManifest.xml 添加provider
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="包名.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

2.在資源文件目錄下增加res/xml/file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>

3.使用FileProvider適配android7.0拍照[以下針對的是Android N的適配]

File photoFile=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists())file.getParentFile().mkdirs();
Uri imageUri = FileProvider.getUriForFile(context, "包名.fileproviderr", photoFile);//通過FileProvider創(chuàng)建一個content類型的Uri
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加這一句表示對目標應用臨時授權(quán)該Uri所代表的文件
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//設(shè)置Action為拍照
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//將拍取的照片保存到指定URI
startActivityForResult(intent,1006);

4 .在 onActivityResult 里面拿到 第3步里面的photoFile。

  if (photoFile != null && Build.VERSION.SDK_INT >= 24) { //適配Android7.0拍照返回圖片處理
                            Uri uri = Uri.fromFile(photoFile);
                            mUploadMsgForAndroid5.onReceiveValue(new Uri[]{uri});
                        } 

這樣android7.0的機型都可以上傳成功了

上述代碼中主要有兩處改變:
1.將之前Uri的scheme類型為file的Uri改成了有FileProvider創(chuàng)建一個content類型的Uri。
2.添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);來對目標應用臨時授權(quán)該Uri所代表的文件。

適配裁剪圖片

在Android7.0之前,你可以通過如下方法來裁切照片:

File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists())file.getParentFile().mkdirs();
Uri outputUri = Uri.fromFile(file);
Uri imageUri=Uri.fromFile(new File("/storage/emulated/0/temp/1474960080319.jpg"));
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(imageUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
startActivityForResult(intent,1008);

和拍照一樣,上述代碼在Android7.0上同樣會引起android.os.FileUriExposedException
異常,解決辦法就是上文說說的(使用FileProvider)。
然后,將上述代碼改為如下即可:

File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists())file.getParentFile().mkdirs();
 if (Build.VERSION.SDK_INT >= 24) {
Uri outputUri = FileProvider.getUriForFile(context, "com.jph.takephoto.fileprovider",file);
Uri imageUri=FileProvider.getUriForFile(context, "com.jph.takephoto.fileprovider", new File("/storage/emulated/0/temp/1474960080319.jpg");//通過FileProvider創(chuàng)建一個content類型的Uri
}else{
Uri outputUri = Uri.fromFile(file);
Uri imageUri=Uri.fromFile(new File("/storage/emulated/0/temp/1474960080319.jpg"));
}
Intent intent = new Intent("com.android.camera.action.CROP");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(imageUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
startActivityForResult(intent,1008);

整合上述,應該清楚知道在Android7.0系統(tǒng)上,Android 框架強制執(zhí)行了 StrictMode API 政策禁止向你的應用外公開 file:// URI。 如果一項包含文件 file:// URI類型 的 Intent 離開你的應用,應用失敗,并出現(xiàn) FileUriExposedException 異常 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容