项目笔记——安卓WebView加载H5页面问题处理

这篇具有很好参考价值的文章主要介绍了项目笔记——安卓WebView加载H5页面问题处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

项目为Android应用,使用WebView加载H5页面。

此文仅记录项目开发中遇到的问题及解决方法。

目录

一,下拉刷新

二,H5唤起支付宝

三,H5本地文件选择

四,加载图片失败

五,输入框被软键盘遮挡


一,下拉刷新

页面Reload需要下拉刷新功能,所以使用了SwipeRefreshLayout包裹WebView。但使用时不管页面处在哪个位置只要下拉,都会触发刷新。

于是通过对WebView的位置进行判断,来决定是否允许SwipeRefreshLayout刷新功能生效。

现在H5页面大多都不再是页面本身滚动,反映到日志就是WebView的getScrollY() 得到的值一直是0,无法用于判断,于是采用迂回的方式。

首先自定义WebView的子类控件OverScrollWebView,重写过度滚动监听overScrollBy,当其被触发的时候,允许下拉刷新。注意:向上过度滑动也会触发这个回调,但SwipeRefreshLayout的刷新仅会被下拉触发,所以这里没有考虑方向问题。

    private boolean isOverScroll = false;

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
        if (!isOverScroll) {
            isOverScroll = true;
        }
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
    }
   
    public boolean isOverScroll() {
        return isOverScroll;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_UP:
                //在用户开始或结束触屏时复位
                isOverScroll = false;
                break;
        }
        return super.onTouchEvent(event);
    }

 同时,在页面中设置允许WebView过度滚动,并重写触屏事件监听,根据页面是否已处在顶部来设置是否允许SwipeRefreshLayout下拉刷新。

    //允许webView过度滚动
    webView.setOverScrollMode(View.OVER_SCROLL_ALWAYS);
    if (webView instanceof OverScrollWebView) {
         webView.setOnTouchListener((v, event) -> {
            swipeRefreshLayout.setEnabled(((OverScrollWebView) webView).isOverScroll());
            return false;
         });
    }

二,H5唤起支付宝

项目支付功能由H5页面调用支付宝接口,但无法正确唤起支付宝App,仅生成了吱口令。

因为安卓原生的谷歌浏览器自从 chrome25 版本开始,URL Scheme 就无法启动Android应用了。

所以,需要重写拦截逻辑,手动唤起App。

webView.setWebViewClient(new WebViewClient() {
        
        @Override
        public boolean shouldOverrideUrlLoading (WebView view, String url){
            JCLog.i(TAG, " shouldOverrideUrlLoading url: " + url);
            if (url.startsWith("http:") || url.startsWith("https:")) {
                //正常的页面,不拦截不处理
                return false;
            }
            try {
                //将H5唤起App,变为App间互相唤起
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                startActivity(intent);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;

        }
    });

这里是笼统的处理,也可以根据页面需求进行精准处理。

参考:android中WebView调用H5页面的支付宝、微信支付失败错误返回ERR_UNKNOWN_URL_SCHEME_webview打开支付宝付款没反应_吕氏春秋i的博客-CSDN博客

三,H5本地文件选择

 这个就比较简单了,就是重写WebChromeClient的onShowFileChooser方法,将H5的文件选择请求转换成安卓原声的文件选取。

需要注意几点:

1,项目需要选择多类型文件,所以我在注册回调时用的是OpenMultipleDocuments,如果是单文件、仅图片等要根据具体情况选择。

2,mFilePathCallback的onReceiveValue方法一定要调用,就算没有内容,也要像我这样传一个空数组new Uri[0]给它调用,这个方法如果不调用,onShowFileChooser就不会再工作了,就是说你没法再次进行文件选择了。

3,数组fileChooserParams.getAcceptTypes()是H5提供的要选择文件类型,部分控件给的是文件后缀,但安卓需要的必须是MimeType,所以我代码里有一部分是专门用于转换参数的。

    private ValueCallback<Uri[]> mFilePathCallback;

    ActivityResultLauncher<String[]> chooseFileLauncher = registerResultCallback(new ActivityResultContracts.OpenMultipleDocuments(), result -> {
        JCLog.i(TAG, "onActivityResult result:" + result);
        Uri[] uris;
        if (null == result || result.isEmpty()) {
            uris = new Uri[0];
        } else {
            uris = new Uri[result.size()];
            for (int i = 0; i < result.size(); i++) {
                uris[i] = result.get(i);
            }
        }
        JCLog.i(TAG, "onActivityResult uris: " + Arrays.toString(uris));
        if (null != mFilePathCallback) {
            mFilePathCallback.onReceiveValue(uris);
            mFilePathCallback = null;
        }
    });

    webView.setWebChromeClient(new WebChromeClient() {
        
        @Override
        public boolean onShowFileChooser (WebView webView, ValueCallback < Uri[]>
        filePathCallback, WebChromeClient.FileChooserParams
        fileChooserParams){
            JCLog.i(TAG, "onShowFileChooser: " + Arrays.toString(fileChooserParams.getAcceptTypes()));
            if (null != chooseFileLauncher) {
                mFilePathCallback = filePathCallback;
                String[] types = fileChooserParams.getAcceptTypes();
                if (null != types && types.length > 0) {
                    for (int i = 0; i < types.length; i++) {
                        String type = types[i];
                        if (TextUtils.isEmpty(type) || !type.startsWith(".")) {
                            continue;
                        }
                        type = type.replaceFirst(".", "");
                        String newType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(type);
                        if (!TextUtils.isEmpty(newType)) {
                            types[i] = newType;
                        }
                    }
                }
                JCLog.i(TAG, "onShowFileChooser before launch: " + Arrays.toString(types));
                chooseFileLauncher.launch(types);
                return true;
            }
            return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
        }
    });

四,加载图片失败

测试时发现某些图片加载失败,与H5开发人员确认,图片使用的scheme是http。

WebView在安卓5以后,则需要增加设置:

    WebSettings webSettings = webView.getSettings();
    ……
    webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

这样就能正常显示http的图片了。

五,输入框被软键盘遮挡

H5页面中有输入框,点击后,安卓弹出的软键盘遮挡了输入框,使用网上找到的AndroidBug5497Workaround解决问题,结果发现手机底部虚拟按键会遮挡页面,于是再次寻找解决办法,最后找到了通过修改AndroidBug5497Workaround完美解决这两个问题的办法。特此记录。因项目开发工作比较忙,此次更新时间间隔较久,所以无法找到代码引用的原文了,见谅。文章来源地址https://www.toymoban.com/news/detail-787783.html

public class AndroidBug5497Workaround {
    private final Activity activity;

    // For more information, see https://code.google.com/p/android/issues/detail?id=5497
    // To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
    public static void assistActivity(Activity activity) {
        new AndroidBug5497Workaround(activity);
    }

    private View mChildOfContent;
    private int usableHeightPrevious;
    private FrameLayout.LayoutParams frameLayoutParams;
    private int statusBarHeight;

    //状态栏高度
    private AndroidBug5497Workaround(final Activity activity) {
        this.activity = activity;
        if (checkDeviceHasNavigationBar(activity)) {
            //获取状态栏的高度
            int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
            statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId);
        }

        //1、找到Activity的最外层布局控件,它其实是一个DecorView,它所用的控件就是FrameLayout
        FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
        //2、获取到setContentView放进去的View
        mChildOfContent = content.getChildAt(0);
        //3、给Activity的xml布局设置View树监听,当布局有变化,如键盘弹出或收起时,都会回调此监听
        mChildOfContent.getViewTreeObserver().
                addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    //4、软键盘弹起会使GlobalLayout发生变化
                    public void onGlobalLayout() {
                        //5、当前布局发生变化时,对Activity的xml布局进行重绘
                        possiblyResizeChildOfContent(checkDeviceHasNavigationBar(activity));
                    }
                });
        //6、获取到Activity的xml布局的放置参数
        frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
    }

    /*** 重新调整布局高度
     * 获取界面可用高度,如果软键盘弹起后,Activity的xml布局可用高度需要减去键盘高度
     ** @param hasNav*/
    private void possiblyResizeChildOfContent(boolean hasNav) {
        //1、获取当前界面可用高度,键盘弹起后,当前界面可用布局会减少键盘的高度
        int usableHeightNow = computeUsableHeight(hasNav);
        //2、如果当前可用高度和原始值不一样
        if (usableHeightNow != usableHeightPrevious) {
            //3、获取Activity中xml中布局在当前界面显示的高度
            int usableHeightSansKeyboard;
            if (hasNav) usableHeightSansKeyboard = mChildOfContent.getHeight();//兼容华为等机型
            else {
                usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();//这个判断是为了解决19之前的版本不支持沉浸式状态栏导致布局显示不完全的问题
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
                    Rect frame = new Rect();
                    activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
                    int statusBarHeight = frame.top;
                    usableHeightSansKeyboard -= statusBarHeight;
                }
            }
            //4、Activity中xml布局的高度-当前可用高度
            int heightDifference = usableHeightSansKeyboard - usableHeightNow;
            //5、高度差大于屏幕1/4时,说明键盘弹出
            if (heightDifference > (usableHeightSansKeyboard / 4)) {
                // keyboard probably just became visible
                // 6、键盘弹出了,Activity的xml布局高度应当减去键盘高度
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && hasNav) {
                    frameLayoutParams.height = usableHeightSansKeyboard - heightDifference + statusBarHeight;
                } else {
                    frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
                }
            } else {
                if (hasNav) frameLayoutParams.height = usableHeightNow + statusBarHeight;
                else frameLayoutParams.height = usableHeightSansKeyboard;
            }
            //7、 重绘Activity的xml布局
            mChildOfContent.requestLayout();
            usableHeightPrevious = usableHeightNow;
        }
    }

    /**
     * 计算mChildOfContent可见高度
     * * @return
     */
    private int computeUsableHeight(boolean hasNav) {
        if (hasNav) {
            Rect r = new Rect();
            mChildOfContent.getWindowVisibleDisplayFrame(r);
            // 全屏模式下:直接返回r.bottom,r.top其实是状态栏的高度
            if (r.top < statusBarHeight)
                return r.bottom - statusBarHeight;
            else return r.bottom - r.top;
        } else {
            Rect frame = new Rect();
            activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
            int statusBarHeight = frame.top;
            Rect r = new Rect();
            mChildOfContent.getWindowVisibleDisplayFrame(r);
            //这个判断是为了解决19之后的版本在弹出软键盘时,键盘和推上去的布局(adjustResize)之间有黑色区域的问题
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                return (r.bottom - r.top) + statusBarHeight;
            }
            return (r.bottom - r.top);
        }
    }

    /*** 通过"qemu.hw.mainkeys"判断是否存在NavigationBar
     ** @return 是否有NavigationBar
     */
    private static boolean checkDeviceHasNavigationBar(Activity activity) {
        boolean hasNavigationBar = false;
        Resources rs = activity.getResources();
        int id = rs.getIdentifier("config_showNavigationBar", "bool", "android");
        if (id > 0) {
            hasNavigationBar = rs.getBoolean(id);
        }
        try {
            Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
            Method m = systemPropertiesClass.getMethod("get", String.class);
            String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys");
            if ("1".equals(navBarOverride)) {
                hasNavigationBar = false;
            } else if ("0".equals(navBarOverride)) {
                hasNavigationBar = true;
            } else {
                hasNavigationBar = hasNavBar(activity);
            }
        } catch (Exception e) {

        }
        return hasNavigationBar;
    }

    /***
     * 根据屏幕真实宽高-可用宽高>0来判断是否存在NavigationBar
     ** @param activity 上下文* @return 是否有NavigationBar
     */
    private static boolean hasNavBar(Activity activity) {
        WindowManager windowManager = activity.getWindowManager();
        Display d = windowManager.getDefaultDisplay();
        DisplayMetrics realDisplayMetrics = new DisplayMetrics();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            d.getRealMetrics(realDisplayMetrics);
        }
        int realHeight = realDisplayMetrics.heightPixels;
        int realWidth = realDisplayMetrics.widthPixels;
        DisplayMetrics displayMetrics = new DisplayMetrics();
        d.getMetrics(displayMetrics);
        int displayHeight = displayMetrics.heightPixels;
        int displayWidth = displayMetrics.widthPixels;
        return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
    }
}

到了这里,关于项目笔记——安卓WebView加载H5页面问题处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android WebView加载h5打开麦克风与摄像头的权限问题

    Android webview h5 麦克风权限,摄像头(相机)权限实现与填坑。 app 必须先具备如下权限(本文只讨论录音与相机) AndroidManifest中添加: 注意:前面两项需要app动态申请 懒人快速做法(可直接copy代码) 默许授权(不再确认) 当h5向app申请的时候,重写 WebChromeClient 的onPermis

    2024年02月11日
    浏览(63)
  • 利用webview 内嵌实现小程序,h5 ,app 页面跳转和数据通讯,附带实现pdf文件的预览(兼容ios和安卓)

    承载网页的容器。会自动铺满整个小程序页面, 个人类型的小程序暂不支持使用。 客户端 6.7.2 版本开始,navigationStyle: custom 对 web-view 组件无效 相关的属性说明:开放能力 / web-view (qq.com) 2.bindmessage 可以实现网页端和小程序之间通讯, 但只在特定时机触发 网页端向小程序

    2024年02月02日
    浏览(72)
  • 微信小程序webview嵌入H5页面,返回 UniAppJSBridgeReady,不触发问题

    公司的一个项目,要求用微信小程序实现对pdf文件,预览签字,用的uniapp开发 pdf预览这部分,用的pdf.js插件实现。 由于微信小程序中webview展示页面不能放在项目本地,要求远端请求网页。而且webview会铺满整个页面,导致我的签字按钮只能放在远端的H5页面实现,当时因为偷

    2024年02月15日
    浏览(55)
  • react-native-webview使用postMessage后H5不能监听问题(iOS和安卓的兼容问题)

     chatgpt: https://chat.xutongbao.top/ 参考链接: https://blog.csdn.net/liuxingyuzaixian/article/details/125199131 https://chat.xutongbao.top/

    2024年02月12日
    浏览(40)
  • 微信小程序使用webview内嵌h5页面 wx.miniProgram.getEnv失效问题

    背景 最近接到一个h5需求,和普通的h5不一样,这个h5页面是嵌入到小程序中使用的,需求简单来说就是展示一个跳转按钮,判断如果是小程序环境下就进行跳转到其他小程序页面。 实现思路 核心逻辑其实就是判断小程序环境这一块,我们可以直接使用wxsdk来进行判断小程序

    2024年02月09日
    浏览(83)
  • 安卓部分手机使用webview加载链接后白屏(Android低版本会出现的问题)

    大爷:小伙我这手机怎么打开你们呢这个是白屏什么都不显示。 大娘:小伙我这也是打开你们呢这功能,就是一个白屏什么也没有,你们呢的应用不会有病毒吧。 小伙:我的手机也正常; 同事:我的也正常可以显示; 小伙:你们都是什么手机型号; 大爷:我的Android7.1.1

    2024年04月15日
    浏览(47)
  • react项目做的h5页面加载缓慢优化(3s优化到0.6s)

    打包到生产环境时去掉SOURCEMAP 禁用生成 Source Map 是一种权衡,可以根据项目的实际需求和优化目标来决定是否禁用。如果您对调试需求不是特别强烈,可以考虑在生产构建中禁用 Source Map 以获取更好的性能。但如果需要保留调试能力,可以在生产构建中保留生成 Source Map “

    2024年02月13日
    浏览(45)
  • 安卓WebView(H5)调用原生相机及相册

    在开始叙述正文之前笔者先声明一下应用场景:例如在网页上的即时通讯需要能拍照或者从图库选择图片来进行上传,此场景下就可以用到这篇文章的内容 正文 首先,如果你已经把相机以及访问文件夹的权限都加上了并且WebView的基础操作都做完了,就差上传图片了的话那就参

    2024年02月11日
    浏览(46)
  • 小程序通过webView打开H5页面并传参(包含webView业务域名配置)、H5页面实现返回小程序并实现传参

    小程序内嵌webview实现跳转、传参 1、小程序通过webView打开H5页面并传参 2、H5接收小程序传参,H5返回小程序并实现传参,小程序接收H5传参 一、小程序通过webView打开H5页面并传参 在小程序中一般通过webview打开H5页面 常见问题: 1、小程序通过webview打开H5页面,需要配置业务域

    2024年02月12日
    浏览(47)
  • uniapp WebView与H5页面通信

    1、参数拼在url里 在h5页面里用window对象获取拼接的url   2、H5环境中 用window.postMessage传递数据    用window.addEventListener(\\\"message\\\", receiveMessage, false);   3、APP环境与微信小程序中 可以通过 uni.postMessage 在 HTML 中向应用发送消息 H5端 uni.postMessage参数格式,传递的消息信息必须在 d

    2024年02月11日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包