WebView常见问题及解决方案

     目前HTML5发展快速,很多native app都会适当地嵌入网页,以此来适应多变的需求变化。但是android的WebView默认支持的功能很弱,很多都需要自定义,才能实现我们想要的效果。并且WebView在不同的版本下,均有不同程度的bug。总结通常使用WebView需要注意的地方如下:

1.重写WebViewClient

     protected ProgressBar mProgressBar;
     private RelativeLayout mLayoutNetError;          //显示网络错误
     private Button mBtnRetry;                         //重新刷新
         
     private boolean mInited = false;          //是否网页初始化完成
     private boolean mNetworkError = false;     //是否网络错误的标记
     protected String mEntryUrl;                    //刚进入时打开的url
    
     private ValueCallback<Uri> mUploadMessage;     //上传文件时用
     
     /**
      * Web View Client
      *
      * @author houjinyun
      * @date 2013-11-18 下午12:44:28
      */
     public class CustomWebViewClient extends WebViewClient {
         
          private static final String TAG = "CustomWebViewClient";
         
           //网页开始加载,在这里可以进行加载动画等等
          @Override
          public void  onPageStarted(WebView view, String url, Bitmap favicon) {
               super.onPageStarted(view, url, favicon);
               mProgressBar.setVisibility(View.VISIBLE);
          }
         
          
          @Override
          public void  onPageFinished(WebView view, String url) {
               super.onPageFinished(view, url);
               mProgressBar.setVisibility(View.GONE);
               if(!mInited && !mNetworkError) {
                    mInited = true;
               }
          }
         
          /**
           * SSL接收过程发生错误
           */
          @Override
          public void  onReceivedSslError(WebView view,
                    SslErrorHandler handler, SslError error) {
               if(!mInited) {
                    mWebView.loadData("", "text/html", "utf_8");
                    mLayoutNetError.setVisibility(View.VISIBLE);     //显示网络错误
                    mBtnRetry.setTag(mEntryUrl);          //记录URL
                    mNetworkError = true;
               } else {
                     //这样才能让WebView处理https请求,否则WebView将无法处理
                     handler.proceed();
               }
          }
         
          /**
           * 接收过程发生错误
           */
          @Override
          public void  onReceivedError(WebView view, int errorCode,
                    String description, String failingUrl) {
               Log.d(TAG, description + "-- error code " + errorCode + " of " + failingUrl);
                //网页加载出现错误,只处理如下几种情况。默认的WebView会直接显示出错的网络地址url,有可能暴露数据。在这种情况下,需要将网页内容置空,提示用户网络错误
               if(errorCode == WebViewClient.ERROR_CONNECT || errorCode == WebViewClient.ERROR_TIMEOUT || errorCode == WebViewClient.ERROR_HOST_LOOKUP) {
                    mWebView.loadData("", "text/html", "utf_8");
                    mLayoutNetError.setVisibility(View.VISIBLE);     //显示网络错误
                    mBtnRetry.setTag(failingUrl);     //记录URL
                    mNetworkError = true;
               }
          }
         
           //return false表示WebView自己处理,return true表示我们自己来处理该url。像“tel:10086”等等类似的url,WebView是不会处理的,需要我们自己来处理。
           //通常情况下,以http、https、file(表示本地网页)开头的url,都给WebView自己来加载,其他类型的url都通过系统应用程序来打开
          @Override
          public boolean  shouldOverrideUrlLoading(WebView view, String url) {
               if(Uri.parse(url).getScheme().startsWith("file")) {
                    //如果是本地加载的话,直接用当前浏览器加载
                    return false;
               }
               if(Uri.parse(url).getScheme().startsWith("http")) {
                    return false;
               }
               if ("about:blank".equals(url)){
                    return false;                         //不需要处理空白页
               }
                //不能识别的,启动系统程序进行加载
               Intent intent = new Intent(Intent.ACTION_VIEW);
               try {
                    intent.setData(Uri.parse(url));
                   context.startActivity(intent);
               } catch (Exception e) {
                    e.printStackTrace();
               }
               return true;
          }
         
     }

2.WebChromeClient

     /**
      * Web Chrome Client
      *
      * @author houjinyun
      *
      */
     public class CustomWebChromeClient extends WebChromeClient{
          private static final String TAG = "WebChromeClient";
          /**
           * 控制台消息输出
           */
          @Override
          public void  onConsoleMessage(java.lang.String message, int lineNumber, java.lang.String sourceId){
               Log.d(TAG, String.format("%s -- From line %s of %s", message, lineNumber, sourceId));
          }
          /**
           * 控制台消息输出
           */
          @Override
          public boolean  onConsoleMessage(ConsoleMessage cm) {
               if (cm.messageLevel() == ConsoleMessage.MessageLevel.DEBUG){
                    Log.d(TAG, String.format("%s -- From line %s of %s", cm.message(), cm.lineNumber(), cm.sourceId()));
               }else if (cm.messageLevel() == ConsoleMessage.MessageLevel.LOG
                         || cm.messageLevel() == ConsoleMessage.MessageLevel.TIP){
                    Log.i(TAG, String.format("%s -- From line %s of %s", cm.message(), cm.lineNumber(), cm.sourceId()));
               }else if (cm.messageLevel() == ConsoleMessage.MessageLevel.WARNING){
                    Log.w(TAG, String.format("%s -- From line %s of %s", cm.message(), cm.lineNumber(), cm.sourceId()));
               }else if (cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR){
                    Log.e(TAG, String.format("%s -- From line %s of %s", cm.message(), cm.lineNumber(), cm.sourceId()));
               }
               return true;
          }
          @Override
          public boolean  onJsAlert(WebView view, String url, String message, final JsResult result){
               AlertDialog.Builder builder = new AlertDialog.Builder(BaseWebActivity.this);
               builder.setMessage(message);
               builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {                        
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                         result.confirm();
                    }
               });
               builder.setCancelable(false).create().show();
               return true;
          }
    
          /**
           * 页面加载进度
           */
          @Override
          public void  onProgressChanged(android.webkit.WebView view, int newProgress){
               Log.d(TAG, String.format("正在加载...(%s)", newProgress));
          }

          // For Android 3.0+
          public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
               this.openFileChooser(uploadMsg, acceptType, null);
          }

          // For Android < 3.0
          public void openFileChooser(ValueCallback<Uri> uploadMsg) {
               this.openFileChooser(uploadMsg, null, null);
          }

           //针对网页里的<input type="file"  accept="image/*" capture="camera" >, WebView默认是不会弹出选择文件对话框的,需要重写该方法,自己来弹出选择文件对话框。
          //注意SDK不同的版本会有不同的方法,需要统一处理
          // For Android > 4.1.1
          public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
               Log.d("BankkaActivity", "acceptType=" + acceptType + ",capture="+capture);
               mUploadMessage = uploadMsg;
               if ("camera".equalsIgnoreCase(capture) && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){//有存贮卡的话,才走拍照
                    //拍照
                    takePicture4InputFile();
               }else{
                    if (capture == null && (acceptType == null || acceptType.startsWith("image/")) && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
                         showMenu4InputFile(acceptType);
                    }else{
                         //文件选择
                         chooseFile4InputFile(acceptType);
                    }
               }
          }
         
     }

3.openFileChooser()
      针对网页里<input type="file" />来进行文件上传时,必须重写该方法。见第3点里的重写方法。

     /**
     * 选择文件为客户端的Input File功能
     */
    private void chooseFile4InputFile(String acceptType){
         Intent intent = new Intent(Intent.ACTION_GET_CONTENT);     
          intent.addCategory(Intent.CATEGORY_OPENABLE);      //能够返回一个Uri结果
          if (CommonMethod.isEmpty(acceptType)){//接收类型
               acceptType = "*/*";      //选择的文件类型,例如:image/*表示图片
          }
          intent.setType(acceptType);
          startActivityForResult(Intent.createChooser(intent, "File Chooser"), FILECHOOSER_CHOOSERFILE);
    }
    /**
     * 拍照为客户端的Input File功能
     */
    private void takePicture4InputFile(){
         Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
          startActivityForResult(takePictureIntent, FILECHOOSER_TAKEPICTURE);
    }
    /**
     * 显示选择菜单为Input File功能
     * @param acceptType
     */
    private void showMenu4InputFile(final String acceptType){
         String[] menus = {"拍照", "相册"};
          Dialog dialog = new AlertDialog.Builder(this).setItems(menus, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int which) {
                    switch(which){
                         case 0:
                              takePicture4InputFile();
                              break;
                         case 1:
                         default:
                              chooseFile4InputFile(acceptType);
                              break;
                    }
               }
          }).create();

           //这里很重要,如果弹出对话框,用户选择一个图片或者进行拍照,但是进行到一半的时候,用户cancel了,这个时候再去点击“选择文件”按钮时,网页会失去响应。
           //原因是:点击“选择文件”按钮时,网页会缓存一个ValueCallback 对象,必须触发了该对象的onReceiveValue()方法,WebView才会释放,进而才能再一次的选择文件。
          //当弹层被取消时,返回未选择文件
          dialog.setOnCancelListener(new OnCancelListener(){
               @Override
               public void onCancel(DialogInterface dialog) {
                    mUploadMessage.onReceiveValue(null);
                    mUploadMessage = null;
               }
          });
          dialog.show();
    }
     
在onActivityResult()方法里进行回调处理:
if (requestCode == FILECHOOSER_TAKEPICTURE && this.mUploadMessage != null){
               //选择文件时的拍照功能
               Uri uri = null;
               if (resultCode == Activity.RESULT_OK && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
                    FileOutputStream fos = null;
                    try{
                         File file = Environment.getExternalStorageDirectory();
                         file = new File(file, "51zhangdan/temp");
                         if (!file.exists()){
                              file.mkdirs();
                         }
                         file = new File(file, "filechoosertmp.png");
                         Bitmap bitmap = (Bitmap)intent.getExtras().get("data");
                         fos = new FileOutputStream(file);
                         bitmap.compress(CompressFormat.PNG, 100, fos);
                         fos.close();
                         fos = null;
                         uri = Uri.fromFile(file);
                    }catch(Exception e){
                         Log.e(TAG, String.format("处理TakePicture图片数据出错.%s", e.getMessage()));
                    }finally{
                         if (fos != null){
                              try {
                                   fos.close();
                              } catch (IOException e) {
                                   Log.e(TAG, String.format("关闭TakePicture图片数据出错.%s", e.getMessage()));
                              }
                         }
                    }
                   
               }
               //这里进行回调,WebView才能获取到你选择了哪个文件
               this.mUploadMessage.onReceiveValue(uri);     
               this.mUploadMessage = null;
          }

4.DownloadListener
      默认情况下,WebView是不能处理下载链接的,一个download url也是以http、https之类开头的,在shouldOverrideUrlLoading()里是不能根据url来判断它是否是一个下载链接。

//该接口可以监听到WebView里打开的下载链接,重写该接口,我们可以得到下载地址的url,进而自己来处理下载。
public class CustomDownloadListener implements android.webkit.DownloadListener {

          private static final String TAG = "CustomDownloadListener";
         
          @Override
          public void onDownloadStart(String url, String userAgent,
                    String contentDisposition, String mimetype, long contentLength) {
               Log.d(TAG, "download:");
               Log.d(TAG, url+ "");
               Log.d(TAG, userAgent + "");
               Log.d(TAG, contentDisposition + "");
               Log.d(TAG, mimetype + "");
               Log.d(TAG, "contentLength: " + contentLength);
               CommonMethod.launchBrowser(BaseWebActivity.this, url);
          }
         
     }
mWebView.setDownloadListener(new CustomDownloadListener());

5.proguard混淆问题
     混淆时,该WebView里的自定义方法可能会被混淆掉,出现莫名其妙的问题,导致我们的设置都不起作用。例如:
-keep class com.example.app.activities.BaseWebActivity$*{*;}
- keepclassmembers  class com. example .app.activities.BaseWebActivity$*{*;}

-keep public class android.net.http.SslError
-keep public class android.webkit.WebViewClient

-dontwarn android.webkit.WebView
-dontwarn android.net.http.SslError
- dontwarn  android.webkit.WebViewClient

#如果有与javascript进行交互的对象,则需要如下处理方式
-keepclassmembers class com.example.app.view.ComJSInterface {
   public *;
}

6.onDestroy()时问题
     在界面退出时,由于WebView自身的bug,可能会出现一些异常,可以做如下处理:
     @Override
     protected void onDestroy() {
          super.onDestroy();
          if(mWebView != null) {
               ((ViewGroup)findViewById(R.id.RelativeLayout_Content)).removeView(mWebView);
               mWebView.removeAllViews();
               mWebView.destroy();
               mWebView = null;
          }
     }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值