WebView 踩坑记

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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值