Android中儲存圖片到本地功能實現
文章轉載自http://blog.csdn.net/ccpat/article/details/45314175 感謝原作者~
本文描述將一個Bitmap物件儲存為一個圖片檔案的主要步驟。儲存的圖片檔案能夠立刻在系統相簿和相簿中找到。
我使用的是一張drawable中的圖片,構造bitmap如下:
Bitmap icon = BitmapFactory.decodeResource(this.getResources(), R.drawable.we);
主要步驟
這裡只介紹按下“儲存”後如何將一個Bitmap物件儲存為圖片檔案的執行步驟,對圖片的下載,圖片到Bitmap物件的轉換,Bitmap物件的格式轉換和壓縮,以及介面設計部分全部都忽略了。
- 確定儲存路徑
- 獲取外部儲存許可權
- 確定外部儲存狀態
- 確定檔名
- 儲存到檔案中
- 傳送廣播,通知系統掃描儲存後的檔案
在Android中檔案儲存路徑包括內部儲存和外部儲存兩種型別。
對內部儲存,當一個app被安裝到手機後,Android系統會在內部儲存的/data/data/目錄下建立一個以包名稱命名的資料夾。例如/data/data/com.sohu.inputmethod.sogou/。一個應用對內部儲存的所有訪問都被限制在這個資料夾中,也就是說Android應用只能在該目錄中讀取,建立,修改檔案。對該目錄之外的其他內部儲存中的目錄都沒有任何操作的許可權。因此,如果將圖片儲存在內部儲存中,只能被應用自身讀取,其他應用均無法讀取。如果需要讓系統圖庫,相簿或其他應用能夠找到儲存的圖片,必須將圖片儲存到外部儲存中。
對外部儲存,當一個app被安裝到手機後,Android系統會在外部儲存的/Android/data/目錄下建立一個以包名命名的資料夾(這裡第一個/不是根路徑,而是相對外部儲存所掛載路徑的相對路徑)。例如/storage/emulated/0/Android/data/com.sohu.inputmethod/。這個路徑同樣只能被應用自身讀取,其他應用不能訪問。因此,也不能將圖片儲存在這個目錄中。
除外部儲存的/Android目錄之外的其他目錄一般都是可以被其他應用訪問的。目前,大多數應用都會在外部儲存的根路徑下建立一個類似包名的多層目錄,以儲存需要共享的檔案。例如/storage/emulated/0/sogou/image/
由於Android系統的碎片化問題,不同裝置上外部儲存的路徑很可能會不同,因此,不能直接使用/storage/emulated/0/作為外部儲存的根路徑。
Android SDK中 Environment類 提供了getExternalStorageDirectory()方法來獲取外部儲存的根路徑。示例如下:
- String dir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tencent/MicroMsg/WeiXin/"
需要注意的是Environment.getExternalStorageDirectory()返回的路徑中最後一個字元不是/,如果需要建立子目錄,需要在子目錄的前後都加上/。
獲取外部儲存許可權
由於需要在外部儲存中寫檔案,需要在AndroidManifest.xml中增加如下的許可權宣告。
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
確定外部儲存狀態
由於外部儲存需要被掛載,也可以被解除安裝,在寫入檔案之前,需要先判斷外部儲存的狀態是否正常。只有狀態正常情況下才可以執行儲存檔案的操作。獲取外部儲存狀態同樣是通過Environment類,通過Environment.getExternalStorageState()可以得到一個字串,來表示外部儲存的狀態。同時在Environment類中定義了一系列的String常量表示不同的狀態。在所有的狀態中只有內部儲存處於Environment.MEDIA_MOUNTED狀態時才可以讀寫檔案,因此,需要將獲取到的狀態和Environment.MEDIA_MOUNTED做比較,如果不是Environment.MEDIA_MOUNTED狀態,就返回儲存失敗。示例如下。
- //獲取內部儲存狀態
- String state = Environment.getExternalStorageState();
- //如果狀態不是mounted,無法讀寫
- if (!state.equals(Environment.MEDIA_MOUNTED)) {
- return;
- }
確定檔名
儲存的圖片檔名可以由應用根據自身需要自行確定,一般來說需要有一個命名規則,然後根據命名規則計算得到檔名。
這裡列舉幾種常見的命名規則。
-
隨機命名
這種命名規則是隨機生成一個字串或一組數字來對圖片命名。
字串可以通過UUID來生成,數字可以通過Random()類來生成,例如:- //通過UUID生成字串檔名
- String fileName1 = UUID.randomUUID().toString();
- //通過Random()類生成陣列命名
- Random random = new Random();
- String fileName2 = String.valueOf(random.nextInt(Integer.MAX_VALUE));
-
這種命名規則是按照數字從小到大的順序來對圖片命名。
在程式啟動時先獲取圖片檔名中當前最大數字的檔名,之後每儲存一張圖片就將數字加1即可。 -
時間命名
這種命名規則是根據儲存圖片的當前系統時間來對圖片命名。
系統時間可以通過System.currentTimeMillis()來獲取,不過System.currentTimeMillis()獲取到的時間是一個long型的整數,如果用它做檔名,無法通過檔名直接看出檔案的具體儲存時間。可以通過SimpleDateFormat先對當前時間做格式化,然後再將其作為檔名來使用。例如:- Calendar now = new GregorianCalendar();
- SimpleDateFormat simpleDate = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());
- String fileName = simpleDate.format(now.getTime());
使用這種命名規則來命名需要注意的是同一秒鐘可能會有多張圖片需要儲存,在得到當前系統時間對應的檔名後,需要判斷該檔案是否存在。如果檔案已經存在,需要重新生成檔名。重新生成的檔名可以在之前的檔名後加上一個隨機數字尾,或者是用毫秒數做字尾。
-
檔案URL命名
每張網路圖片都有一個對應的圖片URL,可以根據圖片的URL來對圖片命名。
不過URL中會包含一些不能用作檔名的特殊字元,此外直接用URL來命名可能會帶來安全問題。為了避免這兩個問題,可以將圖片URL的MD5值作為檔名來使用。由於MD5是不可逆的,也就無法通過MD5值反向得到圖片URL,同時MD5值對應的字串只包含[0-9A-Z],不包含特殊字元,可是作為檔名使用。
由於每張圖片的URL是唯一的,其對應的檔名也就是唯一的。如果需要每張網路圖片只能生成一個檔案,不允許儲存為多份拷貝,可以用這種命名規則。在得到URL對應的檔名後,先判斷檔案是否已經存在,如果已經存在,直接覆蓋或不處理。
儲存圖片檔案時,通過Bitmap的compress()方法將Bitmap物件壓縮到一個檔案輸出流中,然後flush()即可。示例如下。
- try {
- File file = new File(dir + fileName + ".jpg");
- FileOutputStream out = new FileOutputStream(file);
- mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
- out.flush();
- out.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
傳送廣播,通知系統掃描儲存後的檔案
至此,已經實現將Bitmap物件儲存成外部儲存中的一個jpg格式的檔案。但此時該檔案只是儲存在外部儲存的一個目錄中,必須進入其所在的目錄中才可以看到。在系統圖庫,相簿和其他應用中無法看到新建的圖片檔案。為了讓其他應用能夠知道圖片檔案被建立,必須通知MediaProvider服務將新建的檔案新增到圖片資料庫中。
Android系統中常駐一個MediaProvider服務,對應的程序名為android.process.media,此服務用來管理本機上的媒體檔案,提供媒體管理服務。在系統開機或者收到外部儲存的掛載訊息後,MediaProvider會呼叫MediaScanner,MediaScanner會掃描外部儲存中的所有檔案,根據檔案型別的字尾將檔案資訊儲存到對應的資料庫中,供其他APP使用。
MediaScannerReceiver是一個廣播接收者,當它接收到特定的廣播請求後,就會去掃描指定的檔案,並根據檔案資訊將其新增到資料庫中。當圖片檔案被建立後,就可以傳送廣播給MediaScannerReceiver,通知其掃描新建的圖片檔案。示例如下。
- try {
- File file = new File(dir + fileName + ".jpg");
- FileOutputStream out = new FileOutputStream(file);
- mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
- out.flush();
- out.close();
- //儲存圖片後傳送廣播通知更新資料庫
- Uri uri = Uri.fromFile(file);
- sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
- } catch (Exception e) {
- e.printStackTrace();
- }
圖片的非同步儲存
儲存圖片檔案時,如果圖片很大,或需要同時儲存多張圖片時,就需要較多的時間。為了避免阻塞UI執行緒,出現幀率下降或ANR,通常需要將圖片儲存操作放到執行緒中去執行。當圖片儲存完畢後通過sendMessage()方法通知UI執行緒儲存結果。
將圖片儲存放到後臺執行緒去執行需要增加一些同步機制避免一些多執行緒問題。例如有兩張圖片需要儲存,分別放到兩個執行緒中去執行,儲存圖片時檔名以數字順序增加。第一個執行緒選中檔名為125.jpg,但此時檔案還未建立,第二個執行緒判斷125.jpg不存在,於是也選取125.jpg作為檔名,兩張圖片就儲存到同一個檔案中了。