深坑之Webview,解决H5调用android相机拍照和录像

来源:互联网 发布:java 打印异常堆栈 编辑:程序博客网 时间:2024/05/29 12:31

最近在开发过程中遇到一个问题,主要是调用第三方的实名认证,需要拍照和录像;

办过支付宝大宝卡和腾讯的大王卡的都知道这玩意,办卡的时候就需要进行实名认证,人脸识别;

本来第三方平台(xxx流量公司)说的是直接用WebView加载这个H5界面就完事了,我心想这么简单,那不是分分钟的事,放着后面做(公司就我一个安卓,所以开发都是我说的算^_^,独立开发有的时候还是挺爽);

结果到项目快要上线的时候,只想说一句mmp,根本调不了相机,这个时候怎么搞,只有自己去实现了,才发现自己已经进了webview的深坑;

到处找资料,发现webview根本不能让h5自己调用,ios是可以的,项目经理就说是我的锅,真特么又一句mmp(关键是这个H5还特么不能改,不能提供给我调用的方法);

进入正题,首先来了解webview,这里我分享两篇大佬的博客
1,WebView开车指南
2,WebView详解
看完这两篇基本你已经可以随意操作webview了, 但是,当你调用相机的时候你才发现这只是入坑的开始

第一个坑

调用相机之后,文件回调不了,其实根本就是没有回调.这个时候你要去检查你的webview的Settings了关键代码,是否给足了权限;

   WebSettings settings = webView.getSettings();        settings.setUseWideViewPort(true);        settings.setLoadWithOverviewMode(true);        settings.setDomStorageEnabled(true);        settings.setDefaultTextEncodingName("UTF-8");        settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true        settings.setAllowFileAccess(true);    // 是否可访问本地文件,默认值 true        // 是否允许通过file url加载的Javascript读取本地文件,默认值 false        settings.setAllowFileAccessFromFileURLs(false);        // 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false        settings.setAllowUniversalAccessFromFileURLs(false);        //开启JavaScript支持        settings.setJavaScriptEnabled(true);        // 支持缩放        settings.setSupportZoom(true);

其中settings.setDomStorageEnabled(true);这个方法当时我没加,血崩了一次;

第二个坑

WebChromeClient的openFileChooser()只调用了一次

首先了解为什么这个方法只调用了一次,看这篇博客就可
Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明
看了他基本你就了解怎么回事了

第三个坑
怎么区分是要调用相机是需要拍照还是录视频
这个时候你就要看WebViewClient的 shouldOverrideUrlLoading()方法了,我是通过拦截url来判断url里面是拍照还是录制视频来加一个flag
这样你调用你的相机的时候就可以分别处理了

第四个坑
android 7.0的FileProvider的坑
看洪阳大佬的这篇博客基本就了解了
Android 7.0 行为变更 通过FileProvider在应用间共享文件吧

最后附上我自己的代码吧

package com.sihaiwanlian.cmccnev.feature.verify.activitys;import android.annotation.TargetApi;import android.app.Activity;import android.content.ClipData;import android.content.Intent;import android.net.Uri;import android.os.Build;import android.os.Bundle;import android.os.Environment;import android.os.SystemClock;import android.provider.MediaStore;import android.support.annotation.RequiresApi;import android.support.v4.content.FileProvider;import android.support.v7.app.AppCompatActivity;import android.text.TextUtils;import android.util.Log;import android.view.KeyEvent;import android.webkit.ValueCallback;import android.webkit.WebChromeClient;import android.webkit.WebSettings;import android.webkit.WebView;import android.webkit.WebViewClient;import android.widget.Button;import android.widget.ImageView;import android.widget.TextView;import com.orhanobut.logger.Logger;import com.sihaiwanlian.cmccnev.R;import com.sihaiwanlian.cmccnev.utils.PhotoUtils;import java.io.File;import butterknife.BindView;import butterknife.ButterKnife;import butterknife.OnClick;public class Certifica extends AppCompatActivity {    private final static String TAG = "villa";    @BindView(R.id.titleBar_iv_back)    ImageView mTitleBarIvBack;    @BindView(R.id.titleBar_btn_back)    Button mTitleBarBtnBack;    @BindView(R.id.titleBar_centerTV)    TextView mTitleBarCenterTV;    private WebView webView;    private ValueCallback<Uri> mUploadMessage;    private ValueCallback<Uri[]> mUploadCallbackAboveL;    private final static int PHOTO_REQUEST = 100;    private final static int VIDEO_REQUEST = 120;    private final static String url = "your_url";    private boolean videoFlag = false;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_certifica);        ButterKnife.bind(this);        initToolBar();        initWebView();    }    private void initToolBar() {        mTitleBarCenterTV.setText("实名认证");    }    //初始化webView    private void initWebView() {        //从布局文件中扩展webView          webView = (WebView) this.findViewById(R.id.certifi_webview);        initWebViewSetting();    }    //初始化webViewSetting    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)    private void initWebViewSetting() {        WebSettings settings = webView.getSettings();        settings.setUseWideViewPort(true);        settings.setLoadWithOverviewMode(true);        settings.setDomStorageEnabled(true);        settings.setDefaultTextEncodingName("UTF-8");        settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true        settings.setAllowFileAccess(true);    // 是否可访问本地文件,默认值 true        // 是否允许通过file url加载的Javascript读取本地文件,默认值 false        settings.setAllowFileAccessFromFileURLs(false);        // 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false        settings.setAllowUniversalAccessFromFileURLs(false);        //开启JavaScript支持        settings.setJavaScriptEnabled(true);        // 支持缩放        settings.setSupportZoom(true);        //辅助WebView设置处理关于页面跳转,页面请求等操作        webView.setWebViewClient(new MyWebViewClient());        //辅助WebView处理图片上传操作        webView.setWebChromeClient(new MyChromeWebClient());        //加载地址        webView.loadUrl(url);    }    @OnClick(R.id.titleBar_btn_back)    public void onViewClicked() {        if (webView.canGoBack()) {            webView.goBack();// 返回前一个页面        } else {            finish();        }    }    //自定义 WebViewClient 辅助WebView设置处理关于页面跳转,页面请求等操作【处理tel协议和视频通讯请求url的拦截转发】    private class MyWebViewClient extends WebViewClient {        @Override        public boolean shouldOverrideUrlLoading(WebView view, String url) {            Logger.e(url);            if (!TextUtils.isEmpty(url)) {                videoFlag = url.contains("vedio");            }            if (url.trim().startsWith("tel")) {//特殊情况tel,调用系统的拨号软件拨号【<a href="tel:1111111111">1111111111</a>】                Intent i = new Intent(Intent.ACTION_VIEW);                i.setData(Uri.parse(url));                startActivity(i);            } else {                String port = url.substring(url.lastIndexOf(":") + 1, url.lastIndexOf("/"));//尝试要拦截的视频通讯url格式(808端口):【http://xxxx:808/?roomName】                if (port.equals("808")) {//特殊情况【若打开的链接是视频通讯地址格式则调用系统浏览器打开】                    Intent i = new Intent(Intent.ACTION_VIEW);                    i.setData(Uri.parse(url));                    startActivity(i);                } else {//其它非特殊情况全部放行                    view.loadUrl(url);                }            }            return true;        }    }    private File fileUri = new File(Environment.getExternalStorageDirectory().getPath() + "/" + SystemClock.currentThreadTimeMillis() + ".jpg");    private Uri imageUri;    //自定义 WebChromeClient 辅助WebView处理图片上传操作【<input type=file> 文件上传标签】    public class MyChromeWebClient extends WebChromeClient {        // For Android 3.0-          public void openFileChooser(ValueCallback<Uri> uploadMsg) {            Log.d(TAG, "openFileChoose(ValueCallback<Uri> uploadMsg)");            mUploadMessage = uploadMsg;            if (videoFlag) {                recordVideo();            } else {                takePhoto();            }        }        // For Android 3.0+          public void openFileChooser(ValueCallback uploadMsg, String acceptType) {            Log.d(TAG, "openFileChoose( ValueCallback uploadMsg, String acceptType )");            mUploadMessage = uploadMsg;            if (videoFlag) {                recordVideo();            } else {                takePhoto();            }        }        //For Android 4.1          public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {            Log.d(TAG, "openFileChoose(ValueCallback<Uri> uploadMsg, String acceptType, String capture)");            mUploadMessage = uploadMsg;            if (videoFlag) {                recordVideo();            } else {                takePhoto();            }        }        // For Android 5.0+          public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {            Log.d(TAG, "onShowFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)");            mUploadCallbackAboveL = filePathCallback;            if (videoFlag) {                recordVideo();            } else {                takePhoto();            }            return true;        }    }    /**     * 拍照     */    private void takePhoto() {        imageUri = Uri.fromFile(fileUri);        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {            imageUri = FileProvider.getUriForFile(Certifica.this, getPackageName() + ".fileprovider", fileUri);//通过FileProvider创建一个content类型的Uri        }        PhotoUtils.takePicture(Certifica.this, imageUri, PHOTO_REQUEST);    }    /**     * 录像     */    private void recordVideo() {        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);        //限制时长        intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);        //开启摄像机        startActivityForResult(intent, VIDEO_REQUEST);    }    @Override    public boolean onKeyDown(int keyCode, KeyEvent event) {        //如果按下的是回退键且历史记录里确实还有页面        if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {            webView.goBack();            return true;        }        return super.onKeyDown(keyCode, event);    }    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        super.onActivityResult(requestCode, resultCode, data);        if (requestCode == PHOTO_REQUEST) {            if (null == mUploadMessage && null == mUploadCallbackAboveL) return;            Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();            if (mUploadCallbackAboveL != null) {                onActivityResultAboveL(requestCode, resultCode, data);            } else if (mUploadMessage != null) {                mUploadMessage.onReceiveValue(result);                mUploadMessage = null;            }        } else if (requestCode == VIDEO_REQUEST) {            if (null == mUploadMessage && null == mUploadCallbackAboveL) return;            Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();            if (mUploadCallbackAboveL != null) {                if (resultCode == RESULT_OK) {                    mUploadCallbackAboveL.onReceiveValue(new Uri[]{result});                    mUploadCallbackAboveL = null;                } else {                    mUploadCallbackAboveL.onReceiveValue(new Uri[]{});                    mUploadCallbackAboveL = null;                }            } else if (mUploadMessage != null) {                if (resultCode == RESULT_OK) {                    mUploadMessage.onReceiveValue(result);                    mUploadMessage = null;                } else {                    mUploadMessage.onReceiveValue(Uri.EMPTY);                    mUploadMessage = null;                }            }        }    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) {        if (requestCode != PHOTO_REQUEST || mUploadCallbackAboveL == null) {            return;        }        Uri[] results = null;        if (resultCode == Activity.RESULT_OK) {            if (data == null) {                results = new Uri[]{imageUri};            } else {                String dataString = data.getDataString();                ClipData clipData = data.getClipData();                if (clipData != null) {                    results = new Uri[clipData.getItemCount()];                    for (int i = 0; i < clipData.getItemCount(); i++) {                        ClipData.Item item = clipData.getItemAt(i);                        results[i] = item.getUri();                    }                }                if (dataString != null)                    results = new Uri[]{Uri.parse(dataString)};            }        }        mUploadCallbackAboveL.onReceiveValue(results);        mUploadCallbackAboveL = null;    }    @Override    protected void onDestroy() {        super.onDestroy();        if (webView != null) {            webView.setWebViewClient(null);            webView.setWebChromeClient(null);            webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);            webView.clearHistory();//            ((ViewGroup) webView.getParent()).removeView(webView);            webView.destroy();            webView = null;        }    }}  

这里面有个photoUtils,已经封装好了各种调用,简单好用

public class PhotoUtils {    private static final String TAG = "PhotoUtils";    /**     * @param activity    当前activity     * @param imageUri    拍照后照片存储路径     * @param requestCode 调用系统相机请求码     */    public static void takePicture(Activity activity, Uri imageUri, int requestCode) {        //调用系统相机        Intent intentCamera = new Intent();            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {                intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件            }            intentCamera.setAction(MediaStore.ACTION_IMAGE_CAPTURE);            //将拍照结果保存至photo_file的Uri中,不保留在相册中            intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);        if (activity!=null){            activity.startActivityForResult(intentCamera, requestCode);        }    }    /**     * @param activity    当前activity     * @param requestCode 打开相册的请求码     */    public static void openPic(Activity activity, int requestCode) {        Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);        photoPickerIntent.setType("image/*");        activity.startActivityForResult(photoPickerIntent, requestCode);    }    /**     * @param activity    当前activity     * @param orgUri      剪裁原图的Uri     * @param desUri      剪裁后的图片的Uri     * @param aspectX     X方向的比例     * @param aspectY     Y方向的比例     * @param width       剪裁图片的宽度     * @param height      剪裁图片高度     * @param requestCode 剪裁图片的请求码     */    public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX, int aspectY, int width, int height, int requestCode) {        Intent intent = new Intent("com.android.camera.action.CROP");        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);        }        intent.setDataAndType(orgUri, "image/*");        intent.putExtra("crop", "true");        intent.putExtra("aspectX", aspectX);        intent.putExtra("aspectY", aspectY);        intent.putExtra("outputX", width);        intent.putExtra("outputY", height);        intent.putExtra("scale", true);        //将剪切的图片保存到目标Uri中        intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);        intent.putExtra("return-data", false);        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());        intent.putExtra("noFaceDetection", true);        activity.startActivityForResult(intent, requestCode);    }    /**     * 读取uri所在的图片     *     * @param uri      图片对应的Uri     * @param mContext 上下文对象     * @return 获取图像的Bitmap     */    public static Bitmap getBitmapFromUri(Uri uri, Context mContext) {        try {//            Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), uri);            Bitmap bitmapFormUri = getBitmapFormUri(mContext, uri);            return bitmapFormUri;        } catch (Exception e) {            e.printStackTrace();            return null;        }    }    /**     * 通过uri获取图片并进行压缩     *     * @param uri     */    public static Bitmap getBitmapFormUri(Context ac, Uri uri) throws FileNotFoundException, IOException {        InputStream input = ac.getContentResolver().openInputStream(uri);        BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();        onlyBoundsOptions.inJustDecodeBounds = true;        onlyBoundsOptions.inDither = true;//optional        onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional        BitmapFactory.decodeStream(input, null, onlyBoundsOptions);        input.close();        int originalWidth = onlyBoundsOptions.outWidth;        int originalHeight = onlyBoundsOptions.outHeight;        if ((originalWidth == -1) || (originalHeight == -1)){            return null;        }        //图片分辨率以480x800为标准        float hh = 800f;//这里设置高度为800f        float ww = 480f;//这里设置宽度为480f        //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可        int be = 1;//be=1表示不缩放        if (originalWidth > originalHeight && originalWidth > ww) {//如果宽度大的话根据宽度固定大小缩放            be = (int) (originalWidth / ww);        } else if (originalWidth < originalHeight && originalHeight > hh) {//如果高度高的话根据宽度固定大小缩放            be = (int) (originalHeight / hh);        }        if (be <= 0){            be = 1;        }        //比例压缩        BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();        bitmapOptions.inSampleSize = be;//设置缩放比例        bitmapOptions.inDither = true;//optional        bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional        input = ac.getContentResolver().openInputStream(uri);        Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);        input.close();        return compressImage(bitmap);//再进行质量压缩    }    /**     * 质量压缩方法     *     * @param image     * @return     */    public static Bitmap compressImage(Bitmap image) {        ByteArrayOutputStream baos = new ByteArrayOutputStream();        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中        int options = 100;        while (baos.toByteArray().length / 1024 > 100) {  //循环判断如果压缩后图片是否大于100kb,大于继续压缩            baos.reset();//重置baos即清空baos            //第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差  ,第三个参数:保存压缩后的数据的流            image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中            options -= 10;//每次都减少10        }        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片        return bitmap;    }    /**     * @param context 上下文对象     * @param uri     当前相册照片的Uri     * @return 解析后的Uri对应的String     */    @SuppressLint("NewApi")    public static String getPath(final Context context, final Uri uri) {        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;        String pathHead = "file:///";        // DocumentProvider        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {            // ExternalStorageProvider            if (isExternalStorageDocument(uri)) {                final String docId = DocumentsContract.getDocumentId(uri);                final String[] split = docId.split(":");                final String type = split[0];                if ("primary".equalsIgnoreCase(type)) {                    return pathHead + Environment.getExternalStorageDirectory() + "/" + split[1];                }            }            // DownloadsProvider            else if (isDownloadsDocument(uri)) {                final String id = DocumentsContract.getDocumentId(uri);                final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));                return pathHead + getDataColumn(context, contentUri, null, null);            }            // MediaProvider            else if (isMediaDocument(uri)) {                final String docId = DocumentsContract.getDocumentId(uri);                final String[] split = docId.split(":");                final String type = split[0];                Uri contentUri = null;                if ("image".equals(type)) {                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;                } else if ("video".equals(type)) {                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;                } else if ("audio".equals(type)) {                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;                }                final String selection = "_id=?";                final String[] selectionArgs = new String[]{split[1]};                return pathHead + getDataColumn(context, contentUri, selection, selectionArgs);            }        }        // MediaStore (and general)        else if ("content".equalsIgnoreCase(uri.getScheme())) {            return pathHead + getDataColumn(context, uri, null, null);        }        // File        else if ("file".equalsIgnoreCase(uri.getScheme())) {            return pathHead + uri.getPath();        }        return null;    }    /**     * Get the value of the data column for this Uri. This is useful for     * MediaStore Uris, and other file-based ContentProviders.     *     * @param context       The context.     * @param uri           The Uri to query.     * @param selection     (Optional) Filter used in the query.     * @param selectionArgs (Optional) Selection arguments used in the query.     * @return The value of the _data column, which is typically a file path.     */    private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {        Cursor cursor = null;        final String column = "_data";        final String[] projection = {column};        try {            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);            if (cursor != null && cursor.moveToFirst()) {                final int column_index = cursor.getColumnIndexOrThrow(column);                return cursor.getString(column_index);            }        } finally {            if (cursor != null){                cursor.close();            }        }        return null;    }    /**     * @param uri The Uri to check.     * @return Whether the Uri authority is ExternalStorageProvider.     */    private static boolean isExternalStorageDocument(Uri uri) {        return "com.android.externalstorage.documents".equals(uri.getAuthority());    }    /**     * @param uri The Uri to check.     * @return Whether the Uri authority is DownloadsProvider.     */    private static boolean isDownloadsDocument(Uri uri) {        return "com.android.providers.downloads.documents".equals(uri.getAuthority());    }    /**     * @param uri The Uri to check.     * @return Whether the Uri authority is MediaProvider.     */    private static boolean isMediaDocument(Uri uri) {        return "com.android.providers.media.documents".equals(uri.getAuthority());    }}
阅读全文
1 0
原创粉丝点击