WebView 踩坑记

来源:互联网 发布:php一键安装包 编辑:程序博客网 时间:2024/06/12 20:39

1. 通过WebView调起继承微信支付的H5页面

    // 微信H5 支付 官方测试地址    String url = "http://wxpay.wxutil.com/mch/pay/h5.v2.php";    Map<String, String> extraHeaders = new HashMap<String, String>();    extraHeaders.put("Referer", "http://wxpay.wxutil.com");   // 商户申请H5时提交的授权域名    myWebView.loadUrl(url,extraHeaders); // 加载url    mClient = new WebViewClient() {            @Override            public boolean shouldOverrideUrlLoading(WebView view, String url) {                if(url.startsWith("weixin://wap/pay?")) {                    try{                        Intent intent = new Intent();                        intent.setAction(Intent.ACTION_VIEW);                        intent.setData(Uri.parse(url));                        startActivity(intent);                    }catch (ActivityNotFoundException e){                        ToastUtil.showShortToast(FoodWebViewActivity.this,"请安装微信最新版!");                    }                    return true;                } else {                    Map<String, String> extraHeaders = new HashMap<String, String>();                    extraHeaders.put("Referer", "http://wxpay.wxutil.com");   // 微信公共测试地址                    myWebView.loadUrl(url,extraHeaders);                    return true;                }                return true;            }            @Override            public void onPageFinished(WebView view, String url) {                if (mProgressDialog != null) {                    mProgressDialog.dismiss();                }                super.onPageFinished(view, url);            }            @Override            public void onReceivedError(WebView view, int errorCode,                                        String description, String failingUrl) {                // 加载失败                if (mProgressDialog != null) {                    mProgressDialog.dismiss();                }                super.onReceivedError(view, errorCode, description, failingUrl);            }        };        myWebView.setWebViewClient(mClient);

2. 为什么 WebView 调不起 弹框

    myWebView.setWebChromeClient(new WebChromeClient() {        private ValueCallback<Uri> mUploadMessage;        @Override        public void onProgressChanged(WebView view, int newProgress) {            if (newProgress == 100) {                // 网页加载完成                // main_pb_load.setVisibility(View.GONE);            } else {                // 加载中                // if(main_pb_load.getVisibility()==View.GONE){                // main_pb_load.setVisibility(View.VISIBLE);                // }                // main_pb_load.setProgress(newProgress);            }        }        /**         * alert弹框         *         * @return         */        @Override        public boolean onJsAlert(WebView view, String url, final String message,                                 JsResult result) {            runOnUiThread(new Runnable() {                @Override                public void run() {                    new AlertDialog.Builder(BusWebViewActivity.this)                            .setTitle("温馨提示")                            .setMessage(message)                            .setPositiveButton("确定", null)                            .show();                }            });            result.cancel();//这里必须调用,否则页面会阻塞造成假死            return true;        }        @Override        public boolean onJsConfirm(WebView view, String url,                                   final String message, final JsResult result) {            runOnUiThread(new Runnable() {                @Override                public void run() {                    new AlertDialog.Builder(BusWebViewActivity.this)                            .setTitle("温馨提示")                            .setMessage(message)                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {                                @Override                                public void onClick(DialogInterface dialog, int which) {                                    result.confirm();                                }                            })                            .setNegativeButton("取消", new DialogInterface.OnClickListener() {                                @Override                                public void onClick(DialogInterface dialog, int which) {                                    result.cancel();                                }                            })                            .show();                }            });            return true;        }    });

3. H5上传图片,调用起 拍照和 图库

其实上传图片的方式有两种,一种是通过设置WebChromeClient。重写WebChromeClient中关于文件选择的方法,onShowFileChooser和openFileChooser,这种方法其实是有坑的,而且麻烦,还要兼容各个版本,当然我已经实现了,但是出现了个Bug,实在是找不到解决方法,我这边简单描述一下,拍照和图库选择图片我都是可以调起,并且在网页上进行显示,但是当我第一次拍照,显示OK,但是如果我马上又拍了一张,发现图片死活显示不出来。如果我是选择图库,然后在拍照,有可以,只要我是连续拍照就不行。算了,你也看得迷糊,你遇到就会清楚。

现在我在这里介绍第二种,不会存在兼容问题,当然也有兼容问题,下面的例子已经解决,所以是OK的。
第二种:通过 H5 调用 android本地方法,调起 拍照和 图库选择,然后选择好之后,用android 调用 JS 的方法,通过把图片转为base64的字符串传递给H5,这里有个坑,就是base64比较大 在低版本的安卓机上,是传不上去的,所以呢,我们把base64 转为JsonObject 传递给 H5的 JS 方法。同样的还有一个坑,就是 如果用Base64.DEFAULT进行转换base64他会在这个base64后面加上一个换行符,所以不能用这个要用Base64.NO_WRAP 。当然如果你不兼容 低版本,直接用Base64.DEFAULT 的base64字符串直接JS是可以显示图片的,但是如果用 JsonObject 就不行。

以上的坑的解决方法我都是通过上网查找资料找到的,很可惜,没有一段完整的代码来实现这个功能,我这边特别整理了所有的坑,然后完整的代码在这里做个笔记。

当然我没有详细的将这些代码到底是干嘛的,有点基础的会知道的,不知道就百度吧,想要认真的写一篇博客,真的 是很浪费精力。我这边只是做个记录,很多小细节我都没讲,代码也不是全部完整的,但是该有的重点全部有,你照着这个搞一定可以搞定

    public class BusJavaScriptMethods  extends  JavaScriptMethods{        public BusJavaScriptMethods(Context context, WebView webView) {            super(context, webView);        }        @JavascriptInterface        public void openPhoto(int selectImageIndex) {            // 启动本地相册            ((BusWebViewActivity) context).selectImageIndex = selectImageIndex;            ((BusWebViewActivity) context).openPhoto();        }    }    private WebSettings webSettings;    webSettings = myWebView.getSettings();    webSettings.setJavaScriptEnabled(true); // 启用JS脚本    myWebView.addJavascriptInterface(                    new BusJavaScriptMethods(this, myWebView), "jsInterface");    // 这个就是 掉起 相册和拍照    public void openPhoto() {        showPhotoAlertDialog();    }    private void showPhotoAlertDialog() {            final AlertDialog alertDialog = new AlertDialog.Builder(this).create();            alertDialog.show();            Window win = alertDialog.getWindow();            win.getDecorView().setPadding(0, 0, 0, 0);            WindowManager.LayoutParams lp = win.getAttributes();            // 设置弹出框的宽高            lp.width = ViewGroup.LayoutParams.MATCH_PARENT;            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;            // 设置弹出框的位置            win.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);            win.setAttributes(lp);            win.setContentView(R.layout.dialog_user_photo);            RelativeLayout tempRl = (RelativeLayout) win                    .findViewById(R.id.rl_takevidio);            tempRl.setVisibility(View.GONE);            // 取消            RelativeLayout cancelRl = (RelativeLayout) win                    .findViewById(R.id.rl_cancle_photo);            cancelRl.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    alertDialog.cancel();                }            });            // 拍照            RelativeLayout takephotoRl = (RelativeLayout) win                    .findViewById(R.id.rl_takephoto);            takephotoRl.setBackgroundResource(R.drawable.corners_takephoto_bg);            takephotoRl.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    takePhoto();                    alertDialog.cancel();                }            });            // 从手机上传            RelativeLayout fromcameraRl = (RelativeLayout) win                    .findViewById(R.id.rl_from_camera);            fromcameraRl.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    // 调用系统相册获取图片 ,未将图片与上传至服务器                    pickPhoto();                    alertDialog.cancel();                }            });        }        /***         * 使用相册中的图片         */        public static final int SELECT_PIC_BY_PICK_PHOTO = 2;        private static final int PHOTO_REQUEST_GALLERY = 0;        private Uri photoUri;        /***         * 使用照相机拍照获取图片         */        public static final int SELECT_PIC_BY_TACK_PHOTO = 1;        /**         * 拍照获取图片         */        private void takePhoto() {            // 执行拍照前,应该先判断SD卡是否存在            String SDState = Environment.getExternalStorageState();            if (SDState.equals(Environment.MEDIA_MOUNTED)) {                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// "android.media.action.IMAGE_CAPTURE"                /***                 * 需要说明一下,以下操作使用照相机拍照,拍照后的图片会存放在相册中的 这里使用的这种方式有一个好处就是获取的图片是拍照后的原图                 * 如果不实用ContentValues存放照片路径的话,拍照后获取的图片为缩略图不清晰                 */                ContentValues values = new ContentValues();                photoUri = this.getContentResolver().insert(                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);                intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, photoUri);                /** ----------------- */                startActivityForResult(intent, SELECT_PIC_BY_TACK_PHOTO);            } else {                MyApplication.mToast.ToastShow("内存卡不存在");            }        }        /***         * 从相册中取图片         */        private void pickPhoto() {            Intent intent = new Intent();            intent.setType("image/*");            intent.setAction(Intent.ACTION_PICK);            intent.setData(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);// 使用以上这种模式,并添加以上两句            startActivityForResult(intent, PHOTO_REQUEST_GALLERY);        }

上面是选择图片
下面是通过onActivityResult 拿到选择的图片或者是拍照的图片,然后调用JS的方法进行传递

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)    public void onActivityResult(int requestCode, int resultCode, Intent data) {        if (resultCode == Activity.RESULT_OK) {            //裁剪图片            if (requestCode == PHOTO_REQUEST_GALLERY) {                if (data == null) {                    MyApplication.mToast.ToastShow("选择图片文件出错");                    return;                }                photoUri = data.getData();                if (photoUri == null) {                    MyApplication.mToast.ToastShow("选择图片文件出错");                    return;                }                setImage(getRealFilePath(this, photoUri));            } else if (requestCode == SELECT_PIC_BY_TACK_PHOTO) {                setImage(getRealFilePath(this, photoUri));            }        }        super.onActivityResult(requestCode, resultCode, data);    }    // 这个是 和 JS 约定的 方法 "javascript:setImg('" + jsonObject + "'," + selectImageIndex + ")"    private void setImage(final String path) {        String base64String = getImageBase64(path);        if (!TextUtils.isEmpty(base64String)) {            JSONObject jsonObject = new JSONObject();            try {                jsonObject.put("img", "data:image/png;base64," + base64String);                myWebView.loadUrl("javascript:setImg('" + jsonObject + "'," + selectImageIndex + ")");            } catch (JSONException e) {            }        }    }        public String getImageBase64(String path) {            String base64Image = bitmapToBase64String(path);            return base64Image;        }        //计算图片的缩放值        public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {            final int height = options.outHeight;            final int width = options.outWidth;            int inSampleSize = 1;            if (height > reqHeight || width > reqWidth) {                final int heightRatio = Math.round((float) height / (float) reqHeight);                final int widthRatio = Math.round((float) width / (float) reqWidth);                inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;            }            return inSampleSize;        }        // 根据路径获得图片并压缩,返回bitmap用于显示        public static Bitmap getSmallBitmap(String filePath) {            final BitmapFactory.Options options = new BitmapFactory.Options();            options.inJustDecodeBounds = true;            BitmapFactory.decodeFile(filePath, options);            // Calculate inSampleSize            options.inSampleSize = calculateInSampleSize(options, 480, 800);            // Decode bitmap with inSampleSize set            options.inJustDecodeBounds = false;            return BitmapFactory.decodeFile(filePath, options);        }        //把bitmap转换成String        public static String bitmapToBase64String(String filePath) {            Bitmap bm = getSmallBitmap(filePath);            ByteArrayOutputStream baos = new ByteArrayOutputStream();            bm.compress(Bitmap.CompressFormat.JPEG, 40, baos);            byte[] b = baos.toByteArray();            // Base64.NO_WRAP 这个坑 之前提过            return Base64.encodeToString(b, Base64.NO_WRAP);        }        public static String getRealFilePath(final Context context, final Uri uri) {            if (null == uri) return null;            final String scheme = uri.getScheme();            String data = null;            if (scheme == null)                data = uri.getPath();            else if (ContentResolver.SCHEME_FILE.equals(scheme)) {                data = uri.getPath();            } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {                Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);                if (null != cursor) {                    if (cursor.moveToFirst()) {                        int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);                        if (index > -1) {                            data = cursor.getString(index);                        }                    }                    cursor.close();                }            }            return data;        }

下面是H5 的代码,对照着看吧,你会看懂的

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=0">    <meta content="telephone=no" name="format-detection">    <meta name="msapplication-tap-highlight" content="no">    <meta charset="utf-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">    <meta content="yes" name="apple-mobile-web-app-capable">    <meta name="apple-touch-fullscreen" content="yes">    <meta name="format-detection" content="telephone=no">    <meta http-equiv="cleartype" content="on">    <title>图片上传测试</title>    <script src="/Public/api/js/buses/zepto.js"></script>    <script src="/Public/api/js/buses/uploadImg.js"></script>    <script src="/Public/api/js/page.js"></script><meta name="__hash__" content="f3d10c9b6eeb384d1bfc1ae58e3cbdf9_b585d710b9132dbe319f2475665d42eb" /></head><body><p>测试 JS调本地相册</p><button onclick="openPhotoFromAndroid()">JS调用相册</button><p><img id="img" src="" width="100" height="100"/></p><p>参数: <span id="param">0</span></p><br><br><button onclick="backApp()">返回</button><script type="text/javascript">    var u = navigator.userAgent;    var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //android终端    var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端    console.log('是否是Android:' + isAndroid);    console.log('是否是iOS:' + isiOS);    /**     * 调用安卓打开相册对应的openPhoto方法     */    function openPhotoFromAndroid() {        //调用Android的方法启动相册/拍照        //if (isAndroid) alert('当前系统为:安卓');        //if (isiOS) alert('当前系统为:IOS');        window.runApp('openPhoto', rnd(1, 2));        // 这边做过封装,想要掉我的android代码,应该这样写        // window.jsInterface.openPhoto(rnd(1, 2));        // 这个jsInterface不是乱取的是和android那边定义好的,具体看myWebView.addJavascriptInterface(new BusJavaScriptMethods(this, myWebView), "jsInterface");    }    /**     * 安卓调用的js方法,将图片路径返回     * @param path     * @param id     */    function setImg(path, id) {        try {            alert('返回数据类型:' + typeof path);            path = JSON.parse(path);            alert(' 解析后的数据:' + path.img);            //$('#img').attr('src', path[0].img);            //$('#param').text(id);            //readFile();        } catch (err) {            alert('设置图片路径发生错误:' + err.message);        }    }    /**     * 读取文件并转为base64     * @returns {boolean}     */    function readFile() {        alert('开始读取图片');        var path = $('#img').attr('src');        lrz(path).then(function (rst) {            console.log(rst);            $('#img').attr('src', rst.base64);        }).catch(function (err) {            // 处理失败会执行            alert('读取图片发生错误1:' + err.message + ' 图片路径为:' + path);        }).always(function () {            // 不管是成功失败,都会执行        });    }    /**     * 调用app退出浏览器的方法     */    function backApp() {        window.runApp('stopWebActivity');// 这边做过封装,想要掉我的android代码,应该这样写window.jsInterface.stopWebActivity();    }    function rnd(min, max) {        return min + Math.floor(Math.random() * (max - min + 1));    }</script></body></html>
原创粉丝点击