Android 使用騰訊X5內核, Webview瀏覽器拍照或從相冊上傳圖片

2021-02-25 開發者技術前線

最近錢旺宣布成立QBike,準備進軍單車俱樂部,將挑戰膜拜,OFO單車! 雖然新產業一直產生,可能走滴滴模式。開發行情還是不太樂觀,只能敲敲敲!歡迎一葉飄舟加入本公眾號陣營!正參加2016博客之星,點擊原文支持一票!

閱讀文章需要幾分鐘,不妨早上聽聽歌 開啟新的一天!Go!

最近在項目開發中,需要使用WebView上傳文件。默認情況下情況下,使用Android的WebView是不能夠支持上傳文件的。經過查找資料,得知需要重新WebChromeClient,根據選擇到的文件Uri,傳給頁面去上傳就可以了。

自定義WebChromeClient

先在WebViewActivity裡面自定義MyWebChromeClient,代碼如下:

public class MyWebChromeClient extends WebChromeClient {        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);    }    
           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);    }        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);    }    @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中接收到選擇的結果。代碼如下:

@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工具類如下:

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;        
                if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {          
                     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];                }              
                      }            
                      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);            }                        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);            }        }                else if ("content".equalsIgnoreCase(uri.getScheme())) {          
                  return getDataColumn(context, uri, null, null);        }        
                         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

環境調好後,我們就可以愉快的開始調試了。

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);            }                        public void openFileChooser(ValueCallback<Uri> uploadMsg,                                        String acceptType, String capture) {                mUploadMessage = uploadMsg;                choosePicture();            }                        public void openFileChooser(ValueCallback<Uri> uploadMsg,                                        String acceptType) {                mUploadMessage = uploadMsg;                choosePicture();            }                        public void openFileChooser(ValueCallback<Uri> uploadMsg) {                mUploadMessage = uploadMsg;                choosePicture();            }}

這裡選擇圖片使用了三方圖片選擇組件:PhotoPicker,項目地址:https://github.com/donglua/PhotoPicker

其中choosePicture方法如下

private void choosePicture() {        PhotoPicker.builder()                .setPhotoCount(1)                .setShowCamera(true)                .setShowGif(true)                .setPreviewEnabled(false)                .start(TBSWebActivity.this, PhotoPicker.REQUEST_CODE);    }

在onActivityResult中接收到選擇的結果,處理如下:

@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前端調用。

<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

---我是分割線---

相關焦點

  • Android WebView那些坑之上傳文件
    作者丨張磊 來源丨BaronTalk(BaronTalk)最近公司項目需要在WebView上調用手機系統相冊來上傳圖片
  • 還在用Android自帶的WebView組件?太Out了!
    Android中的WebView組件,在4.4以前的版本是WebKit的內核,4.4以後才換成chromium的內核,同時鑑於Google版本帝的風格,因此也導致各個版本之間的運行效率參差不齊。而且即使是chromium內核的版本,也因為要考慮兼容以前的版本,而變得不是那麼美好。
  • 全面總結WebView遇到的坑及優化
    SslErrorHandler handler, SslError error) {                                    handler.proceed();                    }});(5) WebView調用手機系統相冊來上傳圖片
  • Android最全面的 Webview 詳解
    Android的Webview在低版本和高版本採用了不同的webkit版本內核,4.4後直接使用了Chrome。2. 作用WebView控制項功能強大,除了具有一般View的屬性和設置外,還可以對url請求、頁面加載、渲染、頁面交互進行強大的處理。
  • Android中X5WebView詳解
    騎小豬看流星 的博客地址:https://www.jianshu.com/u/0111a7da544b這一篇的目標就是怎麼樣快速封裝X5WebView,如何有效的同步以及管理Cookie,使用IntentService優化預加載,如何監聽進度條等一些在項目中使用的常用功能。
  • Android爬坑之旅之WebView
    web頁面,因此使用起來相當於一個小型瀏覽器,即使頁面內容不複雜,只要使用WebView也會佔用大量的內存。/index.html")來顯示web頁面)對於這樣的三種加載本地內容的方式,我們可以使用多種方式來傳遞路徑供web頁面傳遞,這裡以圖片為例(相冊目錄下test/IMG_20170105_093405.jpg):<img src = 'file:///storage/emulated/0/dcim/test/IMG_20170105_093405.jpg' /&
  • Android WebView:性能優化不得不說的事
    瀏覽器緩存機制:主要前端負責,Android 端不需要進行特別的配置。Dom Storage(Web Storage)存儲機制:配合前端使用,使用時需要打開 DomStorage 開關。它的緩存機制類似於瀏覽器的緩存(Cache-Control 和 Last-Modified)機制,都是以文件為單位進行緩存,且文件有一定更新機制。但 AppCache 是對瀏覽器緩存機制的補充,不是替代。不過根據官方文檔,AppCache 已經不推薦使用了,標準也不會再支持。現在主流的瀏覽器都是還支持 AppCache的,以後就不太確定了。
  • 微信升級到X5內核意味著什麼?
    微信安卓版客戶端 webview 即日起100%放量,全面升級至 X5 Blink 內核,將具有更好的 HTML5/CSS3 支持,更強大的渲染能力。注意:X5將逐步在各客戶端中靜默升級,無需更新微信、手機QQ、QQ空間!一、先看下更新日誌1.
  • 瀏覽器(內核)發展史
    在沒有出現雙核瀏覽器之前,用戶的使用習慣是大多情況要使用一個基於 WebKit 內核的瀏覽器 Chrome、Safari、Firefox 或 Opera ,然後在碰到兼容性問題時再切換至兼顧兼容性的Trident內核IE瀏覽器上。很顯然,這個「縫」給用戶使用帶來一定的不便。更有甚者,很多用戶從技術層面並不知道Chrome其實比IE做得更好,沒有機會享受更好的上網體驗。
  • 調用系統相機、相冊、剪裁圖片並上傳(常用於上傳頭像,兼容Android7.0)
    StrictMode API政策禁中的應用間共享文件就是對上述限制的應對方法,它指明了我們在在應用間共享文件可以發送 content:// URI類型的Uri,並授予 URI 臨時訪問權限,即使用FileProvider接下來,我們使用FileProvider實現調用系統相機、相冊、剪裁圖片的功能兼容Android 7.0第一步:FileProvider
  • appium+python自動化43-切換webview時候報chromedriver版本問題
    not created exception: Chrome version must be >= 58.0.3029.0(Driver info: chromedriver=2.30.477700 )運行環境:android 7.0appium 1.7.1appium裡面chromedriver 2.28webview版本 57.0
  • 圖片上傳系統在淘系中的實踐
    webview上快速上傳圖片,並保證圖片安全下載這一直是一個挑戰。,通過http接口傳輸,直接把文件流打到後端服務,然後後端去把文件流存儲到阿里雲的oss上,這套方案在pc端實現良好,但是到了移動端,為了更好的樣式和體驗,通過喚起相冊或者拍照的方式,只能獲取到圖片的base64,而無法直接獲取文件流(標準input標籤也能打開相冊或拍照,但是是系統級別的行為,樣式和交互方式不可控)。
  • Android Webview使用和遇到過的坑總結
    WebKit渲染引擎顯示web頁面,可以加載在線的或者本地的html頁面,WebView可以對頁面進行一系列操作,如歷史頁面的向前、向後,放大和縮小,執行文本搜索,與JS交互等等;在使用Webview時,請記得在AndroidManifest.xml文件中聲明INTERNET權限:<uses-permission android:name="android.permission.INTERNET
  • Android中使用WebView與JS交互全解析
    ➤如何使用WebView使用WebView控制項 與其他控制項的使用方法相同 在layout中使用一個」WebView」標籤,WebView不包括導航欄,地址欄等完整瀏覽器功能,只用於顯示一個網頁,在WebView中加載Web頁面,使用loadUrl() 注意在manifest文件中加入訪問網際網路的權限:
  • Android WebView 研究筆記
    chrome_devtools_remote應該對應的是瀏覽器。/chrome/devtools_http_client.cc#L92有個比較簡單的接口獲取瀏覽器的版本號,以此來確定該使用的chromedriver版本。
  • Android WebView使用
    前言Android內置webkit內核的高性能瀏覽器,而WebView則是在這個基礎上進行封裝後的一個 控制項,WebView直譯網頁視圖,我們可以簡單的看作一個可以嵌套到界面上的一個瀏覽器控制項簡單使用使用一:加載一個簡單的網頁效果圖
  • Android Webview知識點和遇到過的坑全總結
    渲染引擎顯示web頁面,可以加載在線的或者本地的html頁面,WebView可以對頁面進行一系列操作,如歷史頁面的向前、向後,放大和縮小,執行文本搜索,與JS交互等等;在使用Webview時,請記得在AndroidManifest.xml文件中聲明INTERNET權限:<uses-permission android:name="android.permission.INTERNET
  • App自動化測試 | Android WebView測試
    WebView測試環境準備手機端PC端查看手機瀏覽器版本adb shell pm list package | grep webviewadb shell pm dump com.android.browser
  • 通過JavaScript實現在Android WebView中點擊查看圖片,長按識別二維碼
    但是原有的圖片已經有點擊查看圖片功能。要不破壞原有的功能,還能添加長按事件。這是第一次遇到這種需求。最後我還是完成了這個功能。但是在完成的過程中也遇到一些坑。在此記錄一下,先看一下我實現的效果。1.原有的點擊查看圖片功能1.gif2.長按識別二維碼
  • Android之WebView用法
    這可以讓我們去處理一些特殊的需求,比如像微信那樣在應用程式裡展示網頁,或者說使用 WebView 來為 UI界面布局。WebView 的基本使用WebView的使用非常簡單,新建一個項目 internet,修改 activity_main.xml 中的代碼,加入一個WebView 控制項。