Android 使用腾讯X5内核, Webview浏览器拍照或从相册上传图片

2020-11-23 11:24:11 浏览数 (1)

最近钱旺宣布成立QBike,准备进军单车俱乐部,将挑战膜拜,OFO单车! 虽然新产业一直产生,可能走滴滴模式。开发行情还是不太乐观,只能敲敲敲!欢迎一叶飘舟加入本公众号阵营!正参加2016博客之星,点击原文支持一票!

阅读文章需要几分钟,不妨早上听听歌 开启新的一天!Go!

最近在项目开发中,需要使用WebView上传文件。默认情况下情况下,使用Android的WebView是不能够支持上传文件的。经过查找资料,得知需要重新WebChromeClient,根据选择到的文件Uri,传给页面去上传就可以了。

自定义WebChromeClient

先在WebViewActivity里面自定义MyWebChromeClient,代码如下:

代码语言:javascript复制
public class MyWebChromeClient extends WebChromeClient {
   // For Android 3.0 
   public void openFileChooser(ValueCallback<Uri> uploadMsg) {        
CLog.i("UPFILE", "in openFile Uri Callback");        if (mUploadMessage != null) {
           mUploadMessage.onReceiveValue(null);
       }
       mUploadMessage = uploadMsg;       
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
       i.addCategory(Intent.CATEGORY_OPENABLE);
       i.setType("*/*");
       startActivityForResult(
         Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
   }    
         // For Android 3.0 
   public void openFileChooser(ValueCallback uploadMsg, 
   String acceptType) {       
        CLog.i("UPFILE", "in openFile Uri Callback has accept Type"   acceptType);        
        if (mUploadMessage != null) {
           mUploadMessage.onReceiveValue(null);
       }
       mUploadMessage = uploadMsg;        
       Intent i = new Intent(Intent.ACTION_GET_CONTENT);
       i.addCategory(Intent.CATEGORY_OPENABLE);       
        String type = TextUtils.isEmpty(acceptType) ? "*/*" : acceptType;
       i.setType(type);
       startActivityForResult(Intent.createChooser(i, "File Chooser"),                
        FILECHOOSER_RESULTCODE);
   }    // For Android 4.1
   public void openFileChooser(ValueCallback<Uri> uploadMsg, 
        String acceptType, String capture) {       
         CLog.i("UPFILE", "in openFile Uri Callback has accept Type"   acceptType   "has capture"   capture);        
         if (mUploadMessage != null) {
           mUploadMessage.onReceiveValue(null);
       }
       mUploadMessage = uploadMsg;        
       Intent i = new Intent(Intent.ACTION_GET_CONTENT);
       i.addCategory(Intent.CATEGORY_OPENABLE);        
       String type = TextUtils.isEmpty(acceptType) ? "*/*" : acceptType;
       i.setType(type);
       startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
   }//Android 5.0 
   @Override
   @SuppressLint("NewApi")
   public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {        
      if (mUploadMessage != null) {
           mUploadMessage.onReceiveValue(null);
       }       
        CLog.i("UPFILE", "file chooser params:"   fileChooserParams.toString());
        mUploadMessage = filePathCallback;        
       Intent i = new Intent(Intent.ACTION_GET_CONTENT);
       i.addCategory(Intent.CATEGORY_OPENABLE);        
        if (fileChooserParams != null && fileChooserParams.getAcceptTypes() != null
               && fileChooserParams.getAcceptTypes().length > 0) {
           i.setType(fileChooserParams.getAcceptTypes()[0]);
       } else {
           i.setType("*/*");
       }
       startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);        return true;
   }
}

上面openFileChooser是系统未暴露的接口,因此不需要加Override的注解,同时不同版本有不同的参数,其中的参数,第一个ValueCallback用于我们在选择完文件后,接收文件回调到网页内处理,acceptType为接受的文件mime type。在Android 5.0之后,系统提供了onShowFileChooser来让我们实现选择文件的方法,仍然有ValueCallback,在FileChooserParams参数中,同样包括acceptType。我们可以根据acceptType,来打开系统的或者我们自己创建文件选择器。当然如果需要打开相机拍照,也可以自己去使用打开相机拍照的Intent去打开即可。

处理选择的文件

因为我们前面是使用startActivityForResult来打开的选择页面,我们会在onActivityResult中接收到选择的结果。代码如下:

代码语言:javascript复制
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {    
super.onActivityResult(requestCode, resultCode, data);    
if (requestCode == FILECHOOSER_RESULTCODE) {       
 if (null == mUploadMessage)
  return;
   Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();        if (result == null) {
           mUploadMessage.onReceiveValue(null);
           mUploadMessage = null;           
       return;
     }
       String path =  FileUtils.getPath(this, result);        
      if (TextUtils.isEmpty(path)) {
           mUploadMessage.onReceiveValue(null);
           mUploadMessage = null;            
          return;
       }
       Uri uri = Uri.fromFile(new File(path));        
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
           mUploadMessage.onReceiveValue(new Uri[]{uri});
       } else {
           mUploadMessage.onReceiveValue(uri);
       }
       mUploadMessage = null;
   }
}

注意事项:

1 由于不同版本的差别,Android 5.0以下的版本,ValueCallback 的onReceiveValue接收的参数类型是Uri, 5.0及以上版本接收的是Uri数组,在传值的时候需要注意。 2 选择文件会使用系统提供的组件或者其他支持的app,返回的uri有的直接是文件的url,有的是contentprovider的uri,因此我们需要统一处理一下,转成文件的uri,可参考以下代码(获取文件的路径)。 3 <font color="red">即使获取的结果为null,也要传给webview,即直接调用mUploadMessage.onReceiveValue(null),否则网页会阻塞。</font> 4 在打release包的时候,因为我们会混淆,要特别设置不要混淆WebChromeClient子类里面的openFileChooser方法,由于不是继承的方法,所以默认会被混淆,然后就无法选择文件了。

FileUtils工具类如下:

代码语言:javascript复制
public class FileUtils {  
   public static boolean isExternalStorageDocument(Uri uri) {        return "com.android.externalstorage.documents".equals(uri.getAuthority());
   }    
   public static boolean isDownloadsDocument(Uri uri) {       
       return "com.android.providers.downloads.documents".equals(uri.getAuthority());
   }  
   public static boolean isMediaDocument(Uri uri) {        
       return "com.android.providers.media.documents".equals(uri.getAuthority());
   }  
       
   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;
   }  
   @SuppressLint("NewApi") 
     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;
   }
}

看了上面的代码,你是不是感觉有点复杂呢?下面我们将介绍怎么通过使用腾讯X5 Webview浏览器实现拍照或从相册上传图片功能。

使用腾讯X5 Webview浏览器

TBS腾讯浏览器服务官网:http://x5.tencent.comjar包下载:http://x5.tencent.com/doc?id=1004

集成教程:

http://www.jianshu.com/p/8a7224ff371a http://blog.csdn.net/qq_17387361/article/details/52396338 http://www.jianshu.com/p/e4009688119b

环境调好后,我们就可以愉快的开始调试了。

代码语言:javascript复制
public class MyWebChromeClient extends WebChromeClient {           @Override
           public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> valueCallback, FileChooserParams fileChooserParams) {
               TLog.error("onShowFileChooser");                return super.onShowFileChooser(webView, valueCallback, fileChooserParams);
           }            // Android > 4.1.1 调用这个方法
           public void openFileChooser(ValueCallback<Uri> uploadMsg,
                                       String acceptType, String capture) {
               mUploadMessage = uploadMsg;
               choosePicture();           }            // 3.0   调用这个方法
           public void openFileChooser(ValueCallback<Uri> uploadMsg,
                                       String acceptType) {
               mUploadMessage = uploadMsg;
               choosePicture();           }            // Android < 3.0 调用这个方法
           public void openFileChooser(ValueCallback<Uri> uploadMsg) {
               mUploadMessage = uploadMsg;
               choosePicture();
           }
}

这里选择图片使用了三方图片选择组件:PhotoPicker,项目地址:https://github.com/donglua/PhotoPicker

其中choosePicture方法如下

代码语言:javascript复制
private void choosePicture() {
       PhotoPicker.builder()
               .setPhotoCount(1)
               .setShowCamera(true)
               .setShowGif(true)
               .setPreviewEnabled(false)
               .start(TBSWebActivity.this, PhotoPicker.REQUEST_CODE);
   }

在onActivityResult中接收到选择的结果,处理如下:

代码语言:javascript复制
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {        if (null == mUploadMessage) {            return;
       }        
    if (resultCode == RESULT_OK && requestCode == PhotoPicker.REQUEST_CODE) {
           ArrayList<String> photos = 
      intent.getStringArrayListExtra(PhotoPicker.KEY_SELECTED_PHOTOS);           Uri result = Uri.parse(photos.get(0));
           mUploadMessage.onReceiveValue(result);
           mUploadMessage = null;
       } else {
           mUploadMessage.onReceiveValue(null);
       }
   }

这里值得强调的是,即使获取的结果为null(比如按back键取消了),也要传给webview,即直接调用mUploadMessage.onReceiveValue(null),否则网页会阻塞。

H5前端

最后简单再说一下H5前端调用。

代码语言:javascript复制
<div class="btn1">上传照片
   <input type="file"  accept="image/*" id="uploadImage" capture="camera" onchange="selectFileImage(this);">
</div>

上传相关的js代码由于微信字数限制请点击原文查看。

http://blog.csdn.net/jdsjlzx/article/details/53546260

---我是分割线---

Tamic开发社区

非专业的移动社区

不只是干货,还有人生

长按二维码关注我们

0 人点赞