Android Webview 一些积累

1、Android Webview onPageFinished()加载两次解决办法
在onPageFinished()中进行了一些操作,个别地址会出现加载多次的问题,先解决方法如下

 private boolean isLoading;
 webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
                isLoading = true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                if (isLoading) {
                    isLoading = false;
                    //做些处理
                    return;
                }
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                return false;
            }
        });

2、webview长截图

public static Bitmap capture(WebView webView) {
        Picture picture = webView.capturePicture();
        int width = picture.getWidth();
        int height = picture.getHeight();
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        picture.draw(canvas);
        return bitmap;
    }

public void goScreenShotAction() {
                Bitmap longImage = capture(myWebView);
                try {
                    File file = new File(Environment.getExternalStorageDirectory().toString() + Constants.DOWNIMAGES);
                    if (!file.exists()) {
                        file.mkdirs();
                    }
                    String fileName = file.getAbsolutePath() + "/screenshot" + System.currentTimeMillis() + ".png";
                    FileOutputStream fos = new FileOutputStream(fileName);
                    //压缩bitmap到输出流中
                    longImage.compress(Bitmap.CompressFormat.JPEG, 70, fos);
                    fos.close();
                    Toast.makeText(CommonSearchResultActivity.this, "截屏已保存至SD卡XXX文件夹下", Toast.LENGTH_LONG).show();

                } catch (Exception e) {
                    Log.e("", e.getMessage());
                } finally {
                    if (longImage != null) {
                        longImage.recycle();
                    }

                }
            }

3、webview内存泄漏问题

(1)、尽量不在xml布局中 定义Webview,而是在文件中new出来

//传ApplicationContext可以防止webview对activity的引用而造成的内存泄漏;
mWebView=new WebView(getApplicationContext());
LinearLayout linearLayout  = findViewById(R.id.xxx);
linearLayout.addView(mWebView);

(2)、 从源码的角度解决内存泄漏

@Override
protected void onDestroy() {
    if( mWebView!=null) {
    
        ViewParent parent = mWebView.getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(mWebView);
        }
        mWebView.stopLoading();
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.clearView();
        mWebView.removeAllViews();
        mWebView.destroy();

    }
    super.on Destroy();
}

4、404页面拦截处理
Android6.0以下判断404或者500:

 @Override
    public void onReceivedTitle(WebView view, String title) {
        super.onReceivedTitle(view, title);
        // android 6.0 以下通过title获取
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            if (title.contains("404") || title.contains("500") || title.contains("Error")) {
                view.loadUrl("about:blank");// 避免出现默认的错误界面
                view.loadUrl(mErrorUrl);
            }
        }
    }

Android6.0以上判断404或者500:

   @TargetApi(android.os.Build.VERSION_CODES.M)
    @Override
    public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
        super.onReceivedHttpError(view, request, errorResponse);
        // 这个方法在6.0才出现
        int statusCode = errorResponse.getStatusCode();
        System.out.println("onReceivedHttpError code = " + statusCode);
        if (404 == statusCode || 500 == statusCode) {
            view.loadUrl("about:blank");// 避免出现默认的错误界面
            view.loadUrl(mErrorUrl);
        }
    }

判断断网和链接超时

   @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        super.onReceivedError(view, errorCode, description, failingUrl);
        // 断网或者网络连接超时
        if (errorCode == ERROR_HOST_LOOKUP || errorCode == ERROR_CONNECT || errorCode == ERROR_TIMEOUT) {
            view.loadUrl("about:blank"); // 避免出现默认的错误界面
            view.loadUrl(mErrorUrl);
        }
    }

监听页面返回按钮

 webView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            webView.goBack();
                        }
                    });
                    return true;
                }
                return false;
            }
        });

webview拨电电话和转到邮件

if (url.contains("tel:")) {
RxPermissions rxPermissions = new RxPermissions(activity);
rxPermissions.request(Manifest.permission.CALL_PHONE).subscribe(accept -> {
if (accept) {
String mobile = getPhoneNumber(url);
if (!TextUtils.isEmpty(mobile)) {//判断是否为空
Uri uri = Uri.parse("tel:" + mobile);
Intent intent = new Intent(Intent.ACTION_DIAL, uri);
activity.startActivity(intent);
}
} else {
new ToastUtil(mContext).showToast(mContext.getString(R.string.permission_deny_phone));
}
});
return true;
} else if (url.contains("mailto:")) {
Intent data = new Intent(Intent.ACTION_SENDTO);
data.setData(Uri.parse(url));
data.putExtra(Intent.EXTRA_SUBJECT, "业务联系");
// data.putExtra(Intent.EXTRA_TEXT, "这是内容");
activity.startActivity(data);
}

webview安装处理某个scheme开头的url的APP

try {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
return true;
}
} catch (Exception e) {//防止crash (如果手机上没有安装处理某个scheme开头的url的APP, 会导致crash)
return true;//没有安装该app时,返回true,表示拦截自定义链接,但不跳转,避免弹出上面的错误页面
}

我的webview

/**
 * @author yanxu
 * @date:2019/2/21
 * @description:
 */

public class ProgressWebView extends WebView {
    private WebViewProgressBar progressBar;//进度条的矩形(进度线)
    private Handler handler;
    private WebView mWebView;
    private JSBridge mJsBridge;
    private Context mContext;
    private Activity activity;
    /**
     * 视频全屏参数
     */
    protected static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
            new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    private View customView;
    private FrameLayout fullscreenContainer;
    private WebChromeClient.CustomViewCallback customViewCallback;

    public ProgressWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        activity = CommonUtils.scanForActivity(mContext);
        //实例化进度条
        progressBar = new WebViewProgressBar(context);
        //设置进度条的size
        progressBar.setLayoutParams(new ViewGroup.LayoutParams
                (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        //刚开始时候进度条不可见
        progressBar.setVisibility(GONE);
        //把进度条添加到webView里面
        addView(progressBar);
        //初始化handle
        handler = new Handler();
        mWebView = this;
        initSettings();
    }


    private void initSettings() {
        WebSettings webSettings = mWebView.getSettings();

        mJsBridge = new JSBridge(mContext, mWebView);
        mWebView.addJavascriptInterface(mJsBridge, "webViewApp");
        String ua = mWebView.getSettings().getUserAgentString();//原来获取的UA
        webSettings.setUserAgentString(ua + CommonUtils.getAgent());
        //自动播放
        webSettings.setMediaPlaybackRequiresUserGesture(false);
        webSettings.setJavaScriptEnabled(true);
        // 设置可以访问文件
        webSettings.setAllowFileAccess(true);
        // 设置可以支持缩放
        webSettings.setSupportZoom(true);
        // 支持保存数据
        webSettings.setSaveFormData(false);
        // 设置默认缩放方式尺寸是far
        webSettings.setDefaultZoom(WebSettings.ZoomDensity.MEDIUM);
        // 设置出现缩放工具
        webSettings.setBuiltInZoomControls(false);
        //设置 缓存模式
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        // 开启 DOM storage API 功能
        webSettings.setDomStorageEnabled(true);
        //http https混合
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }
        webSettings.setPluginState(WebSettings.PluginState.ON);
        webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH);
        // 清除缓存
        mWebView.clearCache(true);
        // 清除历史记录
        mWebView.clearHistory();
        mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
        webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
        setWebViewClient(new MyWebClient());
        setWebChromeClient(new MyWebChromeClient());
        SensorsDataAPIUtil.showUpWebView(this);
        setOnLongClickListener(new MyOnLongClickListener());
    }

    /**
     * 长按下载图片
     */
    private class MyOnLongClickListener implements View.OnLongClickListener {
        @Override
        public boolean onLongClick(View v) {
            if (v instanceof WebView) {
                WebView.HitTestResult result = ((WebView) v).getHitTestResult();
                if (result != null) {
                    int type = result.getType();
                    if (type == WebView.HitTestResult.IMAGE_TYPE
                            || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
                        downloadImage(mContext, result.getExtra());
                    }
                }
            }
            return false;
        }
    }

    public static void downloadImage(final Context context, final String url) {
        new RxPermissions(CommonUtils.scanForActivity(context))
                .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .subscribe(aBoolean -> {
                    if (aBoolean) {
                        AlertDialog alertDialog = new AlertDialog.Builder(context)
                                .setTitle("下载")
                                .setMessage("保存图片到手机?")
                                .setNegativeButton("是", (dialog, which) ->
                                        new SaveImage(context, url).execute()).setPositiveButton("否", null).show();
                        alertDialog.show();
                        alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.BLACK);
                        alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setTextColor(Color.BLACK);

                    }
                });

    }

    /**
     * 自定义WebChromeClient
     */
    private class MyWebChromeClient extends WebChromeClient {
        /**
         * 进度改变的回掉
         *
         * @param view        WebView
         * @param newProgress 新进度
         */
        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            if (newProgress == 100) {
                progressBar.setProgress(100);
                handler.postDelayed(runnable, 200);//0.2秒后隐藏进度条
            } else if (progressBar.getVisibility() == GONE) {
                progressBar.setVisibility(VISIBLE);
            }
            //设置初始进度10,这样会显得效果真一点,总不能从1开始吧
            if (newProgress < 10) {
                newProgress = 10;
            }
            //不断更新进度
            progressBar.setProgress(newProgress);
            super.onProgressChanged(view, newProgress);
        }

        /*** 视频播放相关的方法 **/

        @Override
        public View getVideoLoadingProgressView() {
            FrameLayout frameLayout = new FrameLayout(mContext);
            frameLayout.setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            return frameLayout;
        }

        @Override
        public void onShowCustomView(View view, CustomViewCallback callback) {
            showCustomView(view, callback);
        }

        @Override
        public void onHideCustomView() {
            hideCustomView();
        }
    }

    /**
     * 视频播放全屏
     **/
    private void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
        // if a view already exists then immediately terminate the new one
        if (customView != null) {
            callback.onCustomViewHidden();
            return;
        }

        CommonUtils.scanForActivity(mContext).getWindow().getDecorView();
        FrameLayout decor = (FrameLayout) CommonUtils.scanForActivity(mContext).getWindow().getDecorView();
        fullscreenContainer = new FullscreenHolder(mContext);
        fullscreenContainer.addView(view, COVER_SCREEN_PARAMS);
        decor.addView(fullscreenContainer, COVER_SCREEN_PARAMS);
        customView = view;
        setStatusBarVisibility(false);
        customViewCallback = callback;
    }

    /**
     * 隐藏视频全屏
     */
    private void hideCustomView() {
        if (customView == null) {
            return;
        }
        setStatusBarVisibility(true);
        FrameLayout decor = (FrameLayout) CommonUtils.scanForActivity(mContext).getWindow().getDecorView();
        decor.removeView(fullscreenContainer);
        fullscreenContainer = null;
        customView = null;
        customViewCallback.onCustomViewHidden();
        mWebView.setVisibility(View.VISIBLE);
    }

    /**
     * 全屏容器界面
     */
    static class FullscreenHolder extends FrameLayout {

        public FullscreenHolder(Context ctx) {
            super(ctx);
            setBackgroundColor(ctx.getResources().getColor(android.R.color.black));
        }

        @Override
        public boolean onTouchEvent(MotionEvent evt) {
            return true;
        }
    }

    private void setStatusBarVisibility(boolean visible) {
        int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN;
        CommonUtils.scanForActivity(mContext).getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN);
    }

    private class MyWebClient extends WebViewClient {
        /**
         * 加载过程中 拦截加载的地址url
         *
         * @param view
         * @param url  被拦截的url
         * @return
         */
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if (url == null) return false;
            if (url.contains("tel:")) {
                RxPermissions rxPermissions = new RxPermissions(activity);
                rxPermissions.request(Manifest.permission.CALL_PHONE).subscribe(accept -> {
                    if (accept) {
                        String mobile = getPhoneNumber(url);
                        if (!TextUtils.isEmpty(mobile)) {//判断是否为空
                            Uri uri = Uri.parse("tel:" + mobile);
                            Intent intent = new Intent(Intent.ACTION_DIAL, uri);
                            activity.startActivity(intent);
                        }
                    } else {
                        new ToastUtil(mContext).showToast(mContext.getString(R.string.permission_deny_phone));
                    }
                });
                return true;
            } else if (url.contains("mailto:")) {
                Intent data = new Intent(Intent.ACTION_SENDTO);
                data.setData(Uri.parse(url));
                data.putExtra(Intent.EXTRA_SUBJECT, "业务联系");
//                      data.putExtra(Intent.EXTRA_TEXT, "这是内容");
                activity.startActivity(data);
            }
            try {
                if (!url.startsWith("http://") && !url.startsWith("https://")) {
                    return true;
                }
            } catch (Exception e) {//防止crash (如果手机上没有安装处理某个scheme开头的url的APP, 会导致crash)
                return true;//没有安装该app时,返回true,表示拦截自定义链接,但不跳转,避免弹出上面的错误页面
            }
            // TODO Auto-generated method stub
            //返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
            view.loadUrl(url);
            return true;
        }

        /**
         * 页面加载过程中,加载资源回调的方法
         *
         * @param view
         * @param url
         */
        @Override
        public void onLoadResource(WebView view, String url) {
            super.onLoadResource(view, url);
        }

        /**
         * 页面加载完成回调的方法
         *
         * @param view
         * @param url
         */
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            // 关闭图片加载阻塞
            view.getSettings().setBlockNetworkImage(false);
        }

        /**
         * 页面开始加载调用的方法
         *
         * @param view
         * @param url
         * @param favicon
         */
        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
        }

        @Override
        public void onReceivedError(WebView webView, int errorCode, String description, String failingUrl) {
            super.onReceivedError(webView, errorCode, description, failingUrl);
            StatusUtils.create(webView).fail(view -> {
                StatusUtils.create(webView).showLoading();
                new Handler().postDelayed(() -> {
                    StatusUtils.create(webView).hint();
                    webView.reload();
                }, 1000);
            });
        }

        @TargetApi(android.os.Build.VERSION_CODES.M)    // 处理404错误
        @Override
        public void onReceivedHttpError(WebView webView, WebResourceRequest request,
                                        WebResourceResponse errorResponse) {
            super.onReceivedHttpError(webView, request, errorResponse);
            // 这个方法在6.0才出现
            int statusCode = errorResponse.getStatusCode();
            System.out.println("onReceivedHttpError code = " + statusCode);
//            if (404 == statusCode || 500 == statusCode) {
//                StatusUtils.create(webView).fail(view -> {
//                    StatusUtils.create(webView).showLoading();
//                    new Handler().postDelayed(() -> {
//                        StatusUtils.create(webView).hint();
//                        webView.reload();
//                    }, 1000);
//                });
//            }
        }

        @Override
        public void onScaleChanged(WebView view, float oldScale, float newScale) {
            super.onScaleChanged(view, oldScale, newScale);
            ProgressWebView.this.requestFocus();
            ProgressWebView.this.requestFocusFromTouch();
        }
    }


    /**
     * 从url中提取电话号码
     *
     * @param url
     * @return
     */
    private String getPhoneNumber(String url) {
        try {
            //对url字符进行转义
            String transferUrl = URLDecoder.decode(url, "utf-8");
            //tel://(+8610) 87869999
            String mobile = transferUrl.substring(transferUrl.lastIndexOf("/") + 1);
            return RegexUtils.extractNumberFromStr(mobile);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 刷新界面(此处为加载完成后进度消失)
     */
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            progressBar.setVisibility(View.GONE);
        }
    };


    /**
     * destroy
     */
    public void dodestroy() {
        if (mWebView != null) {
            ViewParent parent = mWebView.getParent();
            if (parent != null) {
                ((ViewGroup) parent).removeView(mWebView);
            }
            mWebView.stopLoading();
            // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
            mWebView.getSettings().setJavaScriptEnabled(false);
            mWebView.clearHistory();
            mWebView.removeAllViews();
            mWebView.destroy();
        }
    }

    public JSBridge getJsEngine() {
        return mJsBridge;
    }
}

/**
 * @author yanxu
 * @date:2019/2/21
 * @description:
 */

public class WebViewProgressBar extends View {
    private int progress = 1;//进度默认为1
    private final static int HEIGHT = 5;//进度条高度为5
    private Paint paint;//进度条的画笔
    //  渐变颜色数组
    private final static int colors[] = new int[]{0xFF7AD237, 0xFF8AC14A, 0x35B056 }; //int类型颜色值格式:0x+透明值+颜色的rgb值

    public WebViewProgressBar(Context context) {
        this (context,null);
    }
    public WebViewProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public WebViewProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint(context);
    }

    private void initPaint(Context context) {
        //颜色渐变从colors[0]到colors[2],透明度从0到1
//        LinearGradient shader = new LinearGradient(
//                0, 0,
//                100, HEIGHT,
//                colors,
//                new float[]{0 , 0.5f, 1.0f},
//                Shader.TileMode.MIRROR);
        paint=new Paint(Paint.DITHER_FLAG);
        paint.setStyle(Paint.Style.STROKE);// 填充方式为描边
        paint.setStrokeWidth(HEIGHT);//设置画笔的宽度
        paint.setAntiAlias(true);// 抗锯齿
        paint.setDither(true);// 使用抖动效果
        paint.setColor(context.getResources().getColor(R.color.main_text));//画笔设置颜色
    }
    /**
     * 设置进度
     * @param progress 进度值
     */
    public void setProgress(int progress){
        this.progress = progress;
        invalidate();//刷新画笔
    }
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(0, 0, getWidth() * progress / 100, HEIGHT, paint);//画矩形从(0.0)开始到(progress,height)的区域
    }
}

你可能感兴趣的:(Android Webview 一些积累)