close

選取及複製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的旅行

 

 

 

arrow
arrow

    KOEI 發表在 痞客邦 留言(0) 人氣()