選取及複製Android手機圖片-使用MediaStore
練習使用MediaStore.Images選取1張圖片,並複製到PICTURE目錄。
MediaStore是Android上的多媒體內容供應程式,可用於管理影片、音訊、圖片等資料。提供5種類別:
MediaStore.Audio | 影片 |
MediaStore.Downloads | 下載項目 |
MediaStore.Files | 文件 |
MediaStore.Image | 圖片 |
MediaStore.Vedio | 音訊 |
參考資訊:Android Developers>Docs>Reference>MediaStore(https://developer.android.com/reference/android/provider/MediaStore)
Android已經把整個多媒體檔案資訊都存到資料庫中,多媒體資料庫存放目錄在 /data/data/com.android.providers.media/databases/,有2個資料庫:external.db與internal.db,external為SD卡中多媒體檔案;internal為系統多媒體檔案,EXTERNAL_CONTENT_UR與INTERNAL_CONTENT_URI分別對應到多媒體資料庫external.db與internal.db。圖片存放的URI為MediaStore.Images.Media.INTERNAL_CONTENT_URI 和 MediaStore.Images.Media.EXTERNAL_CONTENT_URI 兩個基礎URI,content內容分別為 content://media/internal/images/media 和 content://media/external/images/media。
Android 10或更高版本不建議直接使用真實路徑處理檔案,需用MediaStore存取多媒體,而預設已有存取多媒體檔案權限,不需再設定READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE。假設一個圖檔真實路徑位於 /storage/0/DCIM/IMG0001.JPG,使用MediaStore獲得系統設定的ID,例如為30,則該圖片的URI是基礎URI加上ID,也就是content://media/external/images/media/30,運用這個URI就可以操作這張圖片。
手機APP常會需要選擇圖片或視訊,使用內容供應程式(Content Provider)就可以管理多媒體資料庫,不必自行開發相關程式。
使用選取圖片的內容供應程式,取得回傳的圖片訊息步驟如下:
步驟1:使用Intent()啟動MediaStore.Images.Media,應用行為選取圖片
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
步驟2:呼叫startActivityForResult(),啟動內容供應程式,並等待接收傳回的結果。
startActivityForResult(Intent, CONTACT_REQUEST);
CONTACT_REQUEST要求代碼,是使用者設定的整數引數,接收Intent結果時,會回傳同一要求代碼,以便識別是哪一個Intent回傳的結果,呼叫多個Intent時需要用來判斷。
參考資訊:Android 開發人員>Docs>指南>從應用行為顯示取得結果(https://developer.android.com/training/basics/intents/result?hl=zh-tw)
步驟3:在onActivityResult()處理回傳結果:
(1):使用getData()取得已被選取的URI。
(2):使用getContentResolver().query取得圖片資訊(ID、檔名),參數如下:
getContentResolver().query(
Uri, //供應程式的URI
projection, //欄位1,欄位2,欄位3,...每個擷取資料列的資料欄位
selection, //SQL陳述式,指定資料列選取條件
selectionArgs, //指定資料列選取參數
sortOrder //指定以欄位1,欄位2,... 排序,傳回Cursor中的顯示順序
Cursor是一份資料欄「清單」,使用Cursor逐一查看結果中的資料列,取得欄位輸出資料。
參考資訊:Android 開發人員>Docs>指南>內容供應程式基本概念(https://developer.android.com/guide/topics/providers/content-provider-basics?hl=zh-tw)
常用的圖片欄位名稱:
欄位名稱 | 說明 | 備註 |
MediaStore.Images.Media._ID | 每個資料列唯一ID | |
MediaStore.Images.Media.BUCKET_DISPLAY_NAME | 包含該圖片的目錄名稱 | |
MediaStore.Images.Media.DATA | 圖片真實路徑 |
Deprecated (不建議使用) in API level 29 |
MediaStore.Images.Media.DATE_MODIFIED | 圖片修改時間 | |
MediaStore.Images.Media.DISPLAY_NAME | 圖片檔名 | |
MediaStore.Images.Media.RELATIVE_PATH | 圖片相對路徑 |
假設圖檔真實路徑MediaStore.Images.Media.DATA是 /storage/emulated/0/Pictures/MY_TEST/IMG0001.JPG,則它的所在目錄BUCKET_DISPLAY_NAME是"MY_TEST",檔名DISPLAY_NAME是"IMG0001.JPG",相對路徑RELATIVE_PATH是"Pictures/MY_TEST"。
參考資訊:Android Developers>Docs>Reference>BaseColumns(https://developer.android.com/reference/android/provider/BaseColumns)
MediaStore.MediaColumns(https://developer.android.com/reference/android/provider/MediaStore.MediaColumns)
練習使用MediaStore.Images選取1張圖片,並讀取它的資訊
新增project,種類選擇[Empty Activity],組態Minimum API level 選擇[API 29:Andorid 10.0(Q)],MainActivity.java如下:
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Environment;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.ContentResolver;
import android.provider.MediaStore;
import android.util.Log;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
private static final String LOG_TAG1 = "MainActivity";
private static final int REQUEST_IMAGE_SELECT = 100;
//request Code
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent iImages = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(iImages, REQUEST_IMAGE_SELECT);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_SELECT && resultCode == RESULT_OK) {
Uri selectedImage = data.getData();
Log.d(LOG_TAG1, selectedImage.toString());
ContentResolver resolver = getApplicationContext().getContentResolver();
String[] mProjection = {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DISPLAY_NAME
};
Cursor cursor = resolver.query(selectedImage, mProjection,
null, null, null);
int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
int nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME);
if (cursor != null && cursor.moveToFirst()) {
long id = cursor.getLong(idColumn);
String imgPath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
String fileName = cursor.getString(nameColumn);
Log.d(LOG_TAG1, String.valueOf(id));
Log.d(LOG_TAG1, fileName);
Log.d(LOG_TAG1, imgPath);
}
}
}
}
|
執行程式並選取圖片後,log顯示如下,可用來檢視URI和各欄位的值:
D/MainActivity: content://com.google.android.apps.photos.contentprovider/-1/1/content%3A%2F%2Fmedia%2Fexternal%2Fimages%2Fmedia%2F33/ORIGINAL/NONE/1342150485
D/MainActivity: 30
D/MainActivity: IMG0001.JPG
D/MainActivity: /storage/emulated/0/DCIM/IMG0001.JPG
需要注意的是有些選取圖片的內容供應者並沒有回傳RELATIVE_PATH等資料,它的值會是null,顯示不出值。
取得檔案ID跟基礎的URI結合後,可以得到檔案的URI,可使用withAppendedId進行結合:
Uri contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
檔案的URI得到是 content://media/external/images/media/30
想要刪除此檔案,將檔案URI帶入delete即可,例如:
resolver.delete(contentUri,null,null);
讀取檔案,使用內容供應者getData()取得的URI就能運作,如上例InputStream inputStream = resolver.openInputStream(selectedImage)。如果使用contentUri(content://media/external/images/media/30),InputStream inputStream = resolver.openInputStream(contentUri)會出現java.lang.SecurityException: com.example.myapplication has no access to content://media/external/images/media/30,不能存取檔案。
使用MediaStore.Images選取1張圖片,並複製到PICTURE/MY_TEST目錄
(1)以取得的檔案URI使用openInputStream為InputStream,並讀取檔案內容。
(2)新增ContentValues,放入相對路徑、檔名等欄位資訊。
(3)使用OutputStream將檔案內容寫入。
其中圖片RELATIVE_PATH的一級目錄必須是"DCIM"或"Pictures",參數為Environment.DIRECTORY_DCIM及Environment.DIRECTORY_PICTURES,二級目錄或二級以上目錄則可自行創建。上面例子使用update新增圖片時,二級目錄"MY_TEST"不存在,並不需要先使用mkdirs建立目錄,系統會自動產生。
修改上例onActivityResult如下:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_SELECT && resultCode == RESULT_OK) {
Uri selectedImage = data.getData();
ContentResolver resolver = getApplicationContext().getContentResolver();
String[] mProjection = {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME
};
Cursor cursor = resolver.query(selectedImage, mProjection, null, null, null);
int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
int nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME);
try {
if (cursor != null && cursor.moveToFirst()) {
//long id = cursor.getLong(idColumn);
String fileName = cursor.getString(nameColumn);
InputStream inputStream = resolver.openInputStream(selectedImage);
byte[] image;
image = new byte[inputStream.available()];
inputStream.read(image);
inputStream.close();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH,
Environment.DIRECTORY_PICTURES + File.separator + "MY_TEST");
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
contentValues.put(MediaStore.Audio.Media.IS_PENDING, true);
Uri wuri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues);
OutputStream outputStream = resolver.openOutputStream(wuri);
if (outputStream != null) {
outputStream.write(image);
outputStream.flush();
outputStream.close();
}
contentValues.put(MediaStore.Audio.Media.IS_PENDING, false);
resolver.update(wuri, contentValues, null, null);
}
} catch (IOException e) {
e.printStackTrace();
}
finally {
if (cursor != null)
cursor.close();
}
}
}
|
多媒體檔案存取參考資訊:Android Developers>Docs>Guides>Access media files from shared storage(https://developer.android.com/training/data-storage/shared/media)
多媒體檔案儲存、載入參考:Android10 檔案儲存
使用文件供應程式,選取多張圖片可參考:選取Android手機多張圖片-使用SAF@ KOEI的旅行
留言列表