关于android WebView我趟过的坑!

最近发现偶尔还是会踩到以前踩过的坑,所以开始了我的博客之旅,自己备忘,也可以将这些坑分享出来,帮助大家快速解决问题:

1. ## webview内存泄露 -异常加日志截图 ##
Activity com.dejia.demo.webview.WebViewActivity has leaked ServiceConnection
com.android.org.chromium.com.googlecode.eyesfree.braille.selfbraille.SelfBrailleClient$Connection@426aef18 that was originally bound here
关于android WebView我趟过的坑!_第1张图片

LeakCanary截图:
关于android WebView我趟过的坑!_第2张图片

经过一顿排查,当加上

mWebView.getSettings().setJavaScriptEnabled(true);

词句代码时,就会报以上错误,去掉则不会泄露。从LeakCanary日志中看到是由TextToSpeech.mContext,引用导致内存泄露的;通过google发现TextToSpeech.connecttoengine()

会采用上下文调用bindservice()从日志中也可以发现相关问题;
详细请转本地址了解

http://www.haowuyun.com/view/145

解决方案:
1.方案一:采用微信和QQ的做法,就是 当你要用webview的时候,记得最好 另外单独开一个进程 去使用webview 并且当这个 进程结束时,请手动调用System.exit(0),目前本人感觉是最好的解决办法。
2.方案二 :在代码中动态引用Application的上下文创建webview,

mWebView = new WebView(getApplicationContext());

注意:采用方案二 ,加载个别页面可能会出现功能不能用或者导致应用崩溃,如果影响功能,那就放弃此方法;

2.## 通过h5调支付宝支付,加载页面失败 ##
关于android WebView我趟过的坑!_第3张图片

如果点击返回,我们可以看到正在加载支付宝web支付页面,并且又迅速进入上面的截图页面,这是因为在调支付宝付款的时候支付宝会通过Uri调本地支付宝app, 而如果我们在代码中像如下这样配置就会找不到网页:

     public boolean shouldOverrideUrlLoading(WebView view, String url) {
                super.shouldOverrideUrlLoading(view,url);
                    mWebView.loadUrl(url);
                    return true;
            }

通过打印log发现访问不到的url为

alipays://platformapi/startApp?+支付信息

所以会找不地址,我们应该创建隐式意图去启动支付宝客户端;
解决方案如下:

if (url.contains("alipays://platformapi")){
                    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                    List resolveInfos = getPackageManager().queryIntentActivities(intent,
                            PackageManager.MATCH_DEFAULT_ONLY);
                    if(resolveInfos.size() > 0){
                        startActivity( intent );
                    }
                }else{
                    mWebView.loadUrl(url);
                }

如果本地存在支付宝app则优先唤起app进行支付,如果本地没有安装支付宝app则在web进行支付宝支付。
通过上边处理方法,如果本地没有安装app的话不会导致抛异常,如果直接startActivity(new Intent(…))会找不到该指向并抛异常;

3.## webview清理Cookie ##

 在项目中,前端同志在获取获取token或cookie的时候优先从缓存的Cookie中获取,如果退出webview或是项目的时候未进行清理本地Cookie缓存,则有可能会在携带过期token或Cookie访问服务器的时候不能正常访问,所以在退出webview或是注销登录的时候清楚缓存Cookie;
 处理办法:
CookieManager cookieManager = CookieManager.getInstance();
        cookieManager.removeAllCookie();

4.## webview退出时候,释放内存占用,防止内存泄漏 ##
直接上代码:

@Override
    protected void onDestroy() {
        super.onDestroy();
        if (mWebView != null) {
            mWebView.removeAllViews();
            ((ViewGroup) mWebView.getParent()).removeView(mWebView);
            mWebView.setTag(null);
            mWebView.clearHistory();
            mWebView.destroy();
            mWebView = null;
        }
}

5.## web页面调本地相机拍照或相册时无反应 ##

如果遇到此问题,则需要在改WebViewActivity中进行一些配置,代码如下:
private ValueCallback mUploadMessage;// 表单的数据信息
    private ValueCallback mUploadCallbackAboveL;
    private final static int FILECHOOSER_RESULTCODE = 1;// 表单的结果回调
    private Uri imageUri;

    WebChromeClient webChromeClient = new WebChromeClient() {

        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            super.onProgressChanged(view, newProgress);

        }


        //=========h5调相机相册==========================================================

        @Override
        public boolean onShowFileChooser(WebView webView,
                                         ValueCallback filePathCallback,
                                         FileChooserParams fileChooserParams) {
            mUploadCallbackAboveL = filePathCallback;
            take();
            return true;
        }


        public void openFileChooser(ValueCallback uploadMsg) {
            mUploadMessage = uploadMsg;
            take();
        }

        public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
            mUploadMessage = uploadMsg;
            take();
        }

        public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) {
            mUploadMessage = uploadMsg;
            take();
        }
        //=========h5调相机相册==========================================================
    };

    //=========h5调相机相册==========================================================
    private void take() {
        File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyApp");
        // Create the storage directory if it does not exist
        if (!imageStorageDir.exists()) {
            imageStorageDir.mkdirs();
        }
        File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".jpg");
        imageUri = Uri.fromFile(file);

        final List cameraIntents = new ArrayList();
        final Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        final PackageManager packageManager = getPackageManager();
        final List listCam = packageManager.queryIntentActivities(captureIntent, 0);
        for (ResolveInfo res : listCam) {
            final String packageName = res.activityInfo.packageName;
            final Intent i = new Intent(captureIntent);
            i.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
            i.setPackage(packageName);
            i.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
            cameraIntents.add(i);

        }
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("image/*");
        Intent chooserIntent = Intent.createChooser(i, "Image Chooser");
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[]{}));
        WebViewActivity.this.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
    }

    //=========h5调相机相册==========================================================
    @SuppressWarnings("null")
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) {
        if (requestCode != FILECHOOSER_RESULTCODE
                || mUploadCallbackAboveL == null) {
            return;
        }

        Uri[] results = null;

        if (resultCode == Activity.RESULT_OK) {

            if (data == null) {

                results = new Uri[]{imageUri};
            } else {
                String dataString = data.getDataString();
                ClipData clipData = data.getClipData();

                if (clipData != null) {
                    results = new Uri[clipData.getItemCount()];
                    for (int i = 0; i < clipData.getItemCount(); i++) {
                        ClipData.Item item = clipData.getItemAt(i);
                        results[i] = item.getUri();
                    }
                }

                if (dataString != null)
                    results = new Uri[]{Uri.parse(dataString)};
            }
        }
        if (results != null) {
            mUploadCallbackAboveL.onReceiveValue(results);
            mUploadCallbackAboveL = null;
        } else {
            results = new Uri[]{imageUri};
            mUploadCallbackAboveL.onReceiveValue(results);
            mUploadCallbackAboveL = null;
        }

        return;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//=========h5调相机相册==========================================================
        if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage && null == mUploadCallbackAboveL) return;
            Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
            if (mUploadCallbackAboveL != null) {
                onActivityResultAboveL(requestCode, resultCode, data);
            } else if (mUploadMessage != null) {

                if (result != null) {
                    String path = getPath(getApplicationContext(),
                            result);
                    Uri uri = Uri.fromFile(new File(path));
                    mUploadMessage
                            .onReceiveValue(uri);
                } else {
                    mUploadMessage.onReceiveValue(imageUri);
                }
                mUploadMessage = null;

            }
        }
    }

    //=========h5调相机相册==========================================================
    @SuppressLint("NewApi")
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static String getPath(final Context context, final Uri uri) {
        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }

                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[]{split[1]};

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    //=========h5调相机相册==========================================================
    public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {column};

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null) cursor.close();
        }
        return null;
    }

    //=========h5调相机相册==========================================================
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

//=========h5调相机相册==========================================================

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

//=========h5调相机相册=======

直接copy到你的WebViewActivity中即可,亲测有效;


6. 访问https资源无法正常显示

在webview中加载https资源无法正常显示,原来是因为没有没有识别的证书所以无法正常显示资源,那么我们应该设置WebView接受所有网站的证书,具体代码如下:

webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onReceivedSslError(WebView view,
                    SslErrorHandler handler, SslError error) {
                 // handler.cancel();// Android默认的处理方式
                handler.proceed();// 接受所有网站的证书
                // handleMessage(Message msg);// 其他处理
            }
});

7. 在WebView中无法通过Scheme唤起QQ或其它应用
在WebView的使用中遇到在三星手机上无法通过Scheme唤起QQ,经过排查,是因为下面这句代码导致:

mWebSettings.setSupportMultipleWindows(true);

那为什么这句会导致无法打开呢,这其实和html的代码有关,html页面中设置点击按钮打开QQ,它设置了这个属性

target="_blank"

也就是在新窗口打开,但是如果设置支持多窗口,但是又没有重写下面onCreateWindow(…)就导致出问题;所以默认为不支持多窗口,如果设置多窗口,就必须重写下面的onCreateWindow(…)方法:

WebChromeClient webChromeClient = new WebChromeClient() {

        @Override
        public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
            WebView newWebView = new WebView(view.getContext());
            view.addView(newWebView);
            newWebView.setWebViewClient(new WebViewClient(){
                @Override
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
                    if (!TextUtils.isEmpty(url) && url.contains("mqq://im/chat")){
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                        List resolveInfos = getPackageManager().queryIntentActivities(intent,
                                PackageManager.MATCH_DEFAULT_ONLY);
                        if(resolveInfos.size() > 0){
                            startActivity( intent );
                        }else if(url.contains("mqq://im/chat")){ //本地未安装QQ后的操作
                            Toast.makeText(getApplicationContext(),"请安装QQ后使用此功能",Toast.LENGTH_LONG).show();
                        }
                    }else{
                        mWebView.loadUrl(url);
                    }
                    return true;
                }
            });
            newWebView.setWebChromeClient(this);
            WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
            transport.setWebView(newWebView);
            resultMsg.sendToTarget();
            return true;
        }
      }

但其实最简单的办法就是不支持多窗口:直接像如下这样设置或者不设置也可以解决问题:

mWebSettings.setSupportMultipleWindows(false);

如果我们仅仅是将WebView嵌入我们自己的应用然后加载网页,很少有必要去设置支持多窗口。只在当前窗口加载新的网页就可。

希望本博客,可以帮到你 ——勤劳的码农们!

你可能感兴趣的:(Android基础)