問題
前幾天接到的一個需求莺掠,是關(guān)于第三方理財產(chǎn)品的H5上傳照片問題。
對方說他們的新的需求牍汹,需要接入方配合上傳資產(chǎn)照片的需求,測試之后發(fā)現(xiàn)我們這邊的app端柬泽,IOS端上傳沒有問題慎菲,而Android端則點擊沒有任何反應(yīng)。
對方H5調(diào)用的方式是通過<input type='file' accept='image/*'/>
的方式調(diào)用锨并,本來以為這個問題很簡單露该,就是app端沒有設(shè)置相機權(quán)限,造成的點擊無反應(yīng)情況第煮,而實際上加了之后發(fā)現(xiàn)解幼,并非簡單的權(quán)限問題抑党。
解決問題
因為Android的版本碎片問題,很多版本的WebView都對喚起函數(shù)有不同的支持撵摆。
我們需要重寫WebChromeClient
下的openFileChooser()
(5.0及以上系統(tǒng)回調(diào)onShowFileChooser()
)底靠。我們通過Intent在openFileChooser()
中喚起系統(tǒng)相機和支持Intent的相關(guān)app。
在系統(tǒng)相機或者相關(guān)app中一頓操作之后特铝,當返回app的時候暑中,我們在onActivityResult()
中將選擇好的圖片通過ValueCallback
的onReceiveValue
方法返回給WebView。
附上代碼:
-
首先是重寫各個版本的
WebChromeClient
的支持webView.setWebChromeClient(new WebChromeClient() { //For Android 3.0+ public void openFileChooser(ValueCallback<Uri> uploadMsg) { selectImage(); mUM = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); MyBaseWebViewActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), FCR); } // For Android 3.0+, above method not supported in some android 3+ versions, in such case we use this public void openFileChooser(ValueCallback uploadMsg, String acceptType) { selectImage(); mUM = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); MyBaseWebViewActivity.this.startActivityForResult( Intent.createChooser(i, "File Browser"), FCR); } //For Android 4.1+ public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { selectImage(); mUM = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); MyBaseWebViewActivity.this.startActivityForResult(Intent.createChooser(i, "File Chooser"), MyBaseWebViewActivity.FCR); } //For Android 5.0+ public boolean onShowFileChooser( WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { selectImage(); if (mUMA != null) { mUMA.onReceiveValue(null); } mUMA = filePathCallback; Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(MyBaseWebViewActivity.this.getPackageManager()) != null) { File photoFile = null; try { photoFile = createImageFile(); takePictureIntent.putExtra("PhotoPath", mCM); } catch (IOException ex) { Log.e(TAG, "Image file creation failed", ex); } if (photoFile != null) { mCM = "file:" + photoFile.getAbsolutePath(); filePath = photoFile.getAbsolutePath(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); } else { takePictureIntent = null; } } Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); contentSelectionIntent.setType("*/*"); Intent[] intentArray; if (takePictureIntent != null) { intentArray = new Intent[]{takePictureIntent}; } else { intentArray = new Intent[0]; } Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser"); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); startActivityForResult(chooserIntent, FCR); return true; } });
-
選完照片之后
/** * 打開圖庫,同時處理圖片 */ private void selectImage() { compressPath = Environment.getExternalStorageDirectory().getPath() + "/QWB/temp"; File file = new File(compressPath); if (!file.exists()) { file.mkdirs(); } compressPath = compressPath + File.separator + "compress.png"; File image = new File(compressPath); if (image.exists()) { image.delete(); } } // Create an image file private File createImageFile() throws IOException { @SuppressLint("SimpleDateFormat") String timeStamp = DateUtils.nowTimeDetail(); String imageFileName = "img_" + timeStamp + "_"; File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); return File.createTempFile(imageFileName, ".jpg", storageDir); } private String mCM; private String filePath = ""; private ValueCallback<Uri> mUM; private ValueCallback<Uri[]> mUMA; private final static int FCR = 1; String compressPath = ""; @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if (Build.VERSION.SDK_INT >= 21) { Uri[] results = null; //Check if response is positive if (resultCode == Activity.RESULT_OK) { if (requestCode == FCR) { if (null == mUMA) { return; } if (intent == null) { //Capture Photo if no image available if (mCM != null) { // results = new Uri[]{Uri.parse(mCM)}; results = new Uri[]{afterChosePic(filePath, compressPath)}; } } else { String dataString = intent.getDataString(); if (dataString != null) { results = new Uri[]{Uri.parse(dataString)}; LogUtil.d("tag", intent.toString()); // String realFilePath = getRealFilePath(Uri.parse(dataString)); // results = new Uri[]{afterChosePic(realFilePath, compressPath)}; } } } } mUMA.onReceiveValue(results); mUMA = null; } else { if (requestCode == FCR) { if (null == mUM) return; Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); mUM.onReceiveValue(result); mUM = null; } } } /** * 選擇照片后結(jié)束 */ private Uri afterChosePic(String oldPath, String newPath) { File newFile; try { newFile = FileUtils.compressFile(oldPath, newPath); } catch (Exception e) { e.printStackTrace(); newFile = null; } return Uri.fromFile(newFile); }
-
工具類
public class FileUtils { /** * 把圖片壓縮到200K * * @param oldpath * 壓縮前的圖片路徑 * @param newPath * 壓縮后的圖片路徑 * @return */ public static File compressFile(String oldpath, String newPath) { Bitmap compressBitmap = FileUtils.decodeFile(oldpath); Bitmap newBitmap = ratingImage(oldpath, compressBitmap); ByteArrayOutputStream os = new ByteArrayOutputStream(); newBitmap.compress(Bitmap.CompressFormat.PNG, 100, os); byte[] bytes = os.toByteArray(); File file = null ; try { file = FileUtils.getFileFromBytes(bytes, newPath); } catch (Exception e) { e.printStackTrace(); }finally{ if(newBitmap != null ){ if(!newBitmap.isRecycled()){ newBitmap.recycle(); } newBitmap = null; } if(compressBitmap != null ){ if(!compressBitmap.isRecycled()){ compressBitmap.recycle(); } compressBitmap = null; } } return file; } private static Bitmap ratingImage(String filePath,Bitmap bitmap){ int degree = readPictureDegree(filePath); return rotaingImageView(degree, bitmap); } /** * 旋轉(zhuǎn)圖片 * @param angle * @param bitmap * @return Bitmap */ public static Bitmap rotaingImageView(int angle , Bitmap bitmap) { //旋轉(zhuǎn)圖片 動作 Matrix matrix = new Matrix();; matrix.postRotate(angle); System.out.println("angle2=" + angle); // 創(chuàng)建新的圖片 Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); return resizedBitmap; } /** * 讀取圖片屬性:旋轉(zhuǎn)的角度 * @param path 圖片絕對路徑 * @return degree旋轉(zhuǎn)的角度 */ public static int readPictureDegree(String path) { int degree = 0; try { ExifInterface exifInterface = new ExifInterface(path); int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90: degree = 90; break; case ExifInterface.ORIENTATION_ROTATE_180: degree = 180; break; case ExifInterface.ORIENTATION_ROTATE_270: degree = 270; break; } } catch (IOException e) { e.printStackTrace(); } return degree; } /** * 把字節(jié)數(shù)組保存為一個文件 * * @param b * @param outputFile * @return */ public static File getFileFromBytes(byte[] b, String outputFile) { File ret = null; BufferedOutputStream stream = null; try { ret = new File(outputFile); FileOutputStream fstream = new FileOutputStream(ret); stream = new BufferedOutputStream(fstream); stream.write(b); } catch (Exception e) { // log.error("helper:get file from byte process error!"); e.printStackTrace(); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // log.error("helper:get file from byte process error!"); e.printStackTrace(); } } } return ret; } /** * 圖片壓縮 * * @param fPath * @return */ public static Bitmap decodeFile(String fPath) { BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; opts.inDither = false; // Disable Dithering mode opts.inPurgeable = true; // Tell to gc that whether it needs free opts.inInputShareable = true; // Which kind of reference will be used to BitmapFactory.decodeFile(fPath, opts); final int REQUIRED_SIZE = 400; int scale = 1; if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) { final int heightRatio = Math.round((float) opts.outHeight / (float) REQUIRED_SIZE); final int widthRatio = Math.round((float) opts.outWidth / (float) REQUIRED_SIZE); scale = heightRatio < widthRatio ? heightRatio : widthRatio;// } Log.i("scale", "scal ="+ scale); opts.inJustDecodeBounds = false; opts.inSampleSize = scale; Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(Bitmap.Config.ARGB_8888, false); return bm; } /** * 創(chuàng)建目錄 * @param path */ public static void setMkdir(String path) { File file = new File(path); if(!file.exists()) { file.mkdirs(); Log.e("file", "目錄不存在 創(chuàng)建目錄 "); }else{ Log.e("file", "目錄存在"); } } /** * 獲取目錄名稱 * @param url * @return FileName */ public static String getFileName(String url) { int lastIndexStart = url.lastIndexOf("/"); if(lastIndexStart!=-1) { return url.substring(lastIndexStart+1, url.length()); }else{ return null; } } /** * 刪除該目錄下的文件 * * @param path */ public static void delFile(String path) { if (!TextUtils.isEmpty(path)) { File file = new File(path); if (file.exists()) { file.delete(); } } } }
-
需要注意的問題
-
在打release包的時候鲫剿,因為混淆的問題鳄逾,點擊又會沒有反應(yīng),這是因為
openFileChooser()
是系統(tǒng)api牵素,所以需要在混淆是不混淆該方法严衬。-keepclassmembers class * extends android.webkit.WebChromeClient{ public void openFileChooser(...); }
-
當點擊拍照之后,如果相機是橫屏拍照的話笆呆,當拍照結(jié)束之后跳回app的時候请琳,會導致app端當前的webView頁面銷毀并重新打開,需要在
androidManifest.xml
中當前Activity添加:android:configChanges="orientation|keyboardHidden|screenSize"
-