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>
- WebView 踩坑记
- WebView
- WebView
- WebView
- webView
- webView
- webview
- webview
- WebView
- webview
- webview
- webview
- WebView
- webView
- webview
- webView
- webview
- WebView
- Python中zip()函数用法举例
- 门电路&二进制
- G
- 亲自录制的C#全套编程视频,适合Unity前期脚本语言的学习,想学Unity开发同学可以下载观看
- 我想要个C币
- WebView 踩坑记
- JavaScript高级程序设计笔记(3)_基本概念
- 欧几里德与拓展欧几里德
- 颜色表及html代码
- 查看db2表空间对应的物理文件
- JAVA基础——异常类
- Android之Activity生命周期总结(二)
- eclipse端口号被占用
- 机器学习之决策树