標籤:
<span style="font-size:18px;">在一個項目裡面,有一個需求是讓使用者自己選擇圖片,然後上傳到伺服器。看似一個很簡單的需求,就是選擇圖片,把圖片裝好,然後通過網路請求上傳到後台,OK。但是事實並非如此,因為我們可以android項目,他是open的,他有更多的可能性,當然你也會遇到更多古靈精怪的問題。</span>
擷取圖片有3種方法,一是自己用surface控制項,利用鏡頭來擷取圖片;二是調用系統相機,並且返回拍到的圖片;三是直接在利用圖庫擷取本地圖片;這裡我只使用後面兩種方法來擷取圖片(系統提供了方法,為什麼不用呢,還要費那麼大勁去開發一個新的已經有了的功能,不重複造輪子)。在這過程中,我遇到了好幾個很奇葩和讓人難以理解的問題。弄了我大半天時間,現在做一下記錄。
我所遇到的問題大概有以下:
1、利用系統圖庫擷取圖片,返回的圖片地址,因系統不同而不同;
2、利用拍照返回的照片是經過壓縮的,解析度很低,壓根看不清楚;
3、
第一個問題:返回的圖片地址不同。為什麼這樣講呢,因為我們知道android已經有很多的深度定製的系統,像是小米,華為,魅族,鎚子...等等,都對原生的android系統作了修改最佳化,至少我不知道它們能在底層改了什麼東西,我們只有直面底層的返回,去適應它。在這裡,我們利用顯示intent方式開啟本地圖庫,代碼如下
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);//設定動作
<span style="font-size:18px;"><span style="white-space:pre"></span>intent.setType("image/*");//開啟Pictures畫面Type設定為image<span style="white-space:pre"></span>startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE1);</span>這樣就可以開啟系統的圖庫來選去需要的圖片了。
那麼,問題就來了。我在魅藍2上面開啟後,系統跳到的是最近的拍照照片,而在小米2s上面也是跳到最近的拍攝的圖片集合裡面去,在華為honor6 ?上面則是跳到最近編輯過的圖片集合裡面去了。最後在onactivityresult()回調方法裡面利用以下代碼得到圖片地址:
<span style="font-size:18px;"><span style="white-space:pre"></span>Uri uri = data.getData(); String pathImg = uri.getPath();</span>
魅藍擷取到2擷取到的圖片路徑是:/external/images/media/640543,小米2s的路徑是:/storage/sdcard0/DCIM/Camera/IMG_20160217_142658.jpg,華為的類似魅藍2的路徑,這裡沒有列印出來。在這裡我要的是選取圖片的完整路徑,如果不用不需要完整路徑,也是可以擷取到圖片的,代碼如下:
Bitmap bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri)); 這個方法返回來的是一個bitmap,一看到bitmap應該立即想到的是oom,這是一個程式猿又愛又恨的東西啊。現在的手機隨便拍個照,都有1000(+)*1000(+)的像素,在android裡面一個像素點,用4個位元組存放。所以,讀入一個1000*1000像素的圖片,讀入來,佔用的位元組數有1000*1000*4.這對一個程式來說,幾乎是致命的。這樣的做法顯然不可取。
在上面返回的路徑看來,小米2s返回的路徑是對的,但是My Code還要在魅藍上運行,顯然也是不通過的。所以我又找了另外一個辦法來擷取返回的圖片路徑:
String picturePath = ""; String[] proj = {MediaStore.Images.Media.DATA}; Cursor cursor = getContentResolver().query(uri, proj, null, null, null); if (cursor != null && cursor.moveToFirst()) { int columnIndex = cursor.getColumnIndex(proj[0]); picturePath = cursor.getString(columnIndex); System.out.println("-=-==->>picturePath = " + picturePath); }此方法,在華為honor6?上面返回的的cursor為null,魅藍上是OK的,小米的也是null。這就讓人很費解了。同樣是顯示調用系統的方法,但是返回來的卻是截然不同的東西。而我的目標是要不oom和完整的路徑。
所以在這裡,我用了一個猥瑣的方法來繞過這個坑。就是我同時用兩種方法來擷取兩個路徑,然後判斷哪一個路徑是不為null,並且帶有圖片格式尾碼的路徑,就拿來用。
這裡的用,是壓縮圖片後,再將壓縮圖片讀進記憶體來。以下是我的方法:
</pre>
/** * 根據圖片路徑,得到壓縮過的位元影像 * * @param path * @param width * @param height * @return */ public Bitmap getPressedBitmap(String path, int width, int height) { BitmapFactory.Options options = new BitmapFactory.Options();//new一個options options.inJustDecodeBounds = true;//先設定為true,即不讀入圖片到記憶體,先擷取圖片的資訊,比如長寬等資訊 Bitmap bitmap = BitmapFactory.decodeFile(path, options);//此句代碼是真正的去讀取圖片的長寬等資訊,並且儲存在options裡面,在後面的代碼中我們可以看到options.outWidth 和 options.outHeight得到的是圖片的寬和高。這句代碼所以不能沒有,否則無法壓縮圖片。這裡得到的bitmap是為null options.inSampleSize = getBitmapSampleSize(options, width, height);//根據給定的寬高來壓縮圖片的比例 options.inJustDecodeBounds = false;//設定為false,是要將圖片以一定比例壓縮後讀入記憶體中 <span style="white-space:pre"></span>Bitmap bitmap1 = BitmapFactory.decodeFile(path, options);//這裡得到的bitmap才是不為null return bitmap1;}
<pre name="code" class="java">/** * 根據要去的寬高,壓縮圖片 * * @param options * @param reqWidth * @param reqHeight * @return */ public int getBitmapSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { int imgWidth = options.outWidth; int imgHeight = options.outHeight; int inSimpleSize = 1; if (imgWidth > imgHeight || imgWidth < imgHeight) { final int heightRatio = imgWidth / reqWidth; final int widthRatio = imgHeight / reqHeight; inSimpleSize = widthRatio < heightRatio ? widthRatio : heightRatio; } return inSimpleSize; }
最後,我將圖片選取好,上傳到後台,發現圖片的像素很低,幾乎看不清上傳的是什麼,這個也是不符合需求的。所以要修改。我發現把options.inSampleSize = 5;//getBitmapSampleSize(options, width, height)改成這樣子,inSampleSize改為你覺得可以看清楚的int就OK了。(這裡提醒一下下,也要注意讀取進來的bitmap的大小,圖片也不能太大,因為上傳是耗費流量的,我們一定要站在使用者的角度去想問題)
最後,問題實際上解決了,但是理論上還沒有,想不通,怎麼會我也沒有時間去一一對看源碼。等找個空時間,再去扒源碼。
對於第二個問題:利用拍照返回的照片是經過壓縮的,解析度很低,壓根看不清楚。我們用顯式intent來開啟系統網路攝影機,然後回傳圖片資訊,代碼如下:
bundle = data.getExtras();Bitmap bitmap = (Bitmap) bundle.get("data");// 擷取相機返回的資料,並轉換為Bitmap圖片格式可以看到,調用系統相機拍照所返回的圖片資料,是放到bundle裡面的。我將bitmap拿出來,發現像素也是低到不行,根本沒辦法看,原來返回的是壓縮得不行不行的圖片。這當然也不行了。發現拍照的圖片也沒有儲存在本地,不能重複使用那張圖片。
既然這樣,我就查資料,想著系統應該會有方法設定可以返回原圖的,但是我暫時沒有找到。然後看到另外一個方法,先把拍照的照片存在指定的位置,然後利用指定位置去擷取原圖,壓縮後,在讀取進來。
一開始在啟動調用系統相機的時候,指定照片儲存的位置。
// 利用系統內建的相機應用:拍照 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);File filePath = getTakePhotoPath();//得到圖片檔案儲存體的路徑Uri imgUri = null;imgUri = Uri.fromFile(filePath);cacheImgPath = filePath.getAbsolutePath();//將圖片的路徑儲存起來intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);//把路徑傳給系統,系統會自動儲存到你指定的路徑下getParent().startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE2);
/** * 返回一個儲存拍照的路徑 * * @return path */ private File getTakePhotoPath() { SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = new Date(System.currentTimeMillis()); String fileName = format.format(date); File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); String imgPath = file.getAbsolutePath() + "/test/"; File fileDcim = new File(imgPath); if (!fileDcim.exists()) { fileDcim.mkdirs(); } File filePicPath = new File(fileDcim, "SBD_" + fileName + ".jpg"); if (!filePicPath.exists()) { try { filePicPath.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } String path = filePicPath.getAbsolutePath(); if (path == null) { return null; } else { return filePicPath; } }在onactivityresult()回調中,利用儲存起來的的路徑,像上面一樣擷取本地圖片的壓縮圖就OK了。
當然,在做以上操作時,千萬別忘了要添加相應的許可權。
以上就是我個人對這個問題的看法和解決方案,如果發現有何不對,或者有更好的方法解決問題,請賜教!
android選取本地圖片及關於圖片壓縮上傳問題