一手遮天 Android - 存儲: Android 11 通過 MediaStore 管理文件


項目地址 https://github.com/webabcd/AndroidDemo
作者 webabcd

一手遮天 Android - 存儲: Android 11 通過 MediaStore 管理文件

示例如下:

/storage/Android11Demo2.java

/**
 * 本例用於演示 Android 11 通過 MediaStore 管理文件
 *
 * 通過 MediaStore 可以在指定的公共目錄中管理文件,包括圖片目錄,視頻目錄,音頻目錄,下載目錄
 *     文檔目錄等其他公共目錄是不能通過 MediaStore 管理的
 * 通過 MediaStore 保存的數據,卸載 app 后不會被刪除
 * 通過 MediaStore 保存或讀取數據全部由程序管理,不需要用戶選擇(通過 Storage Access Framework 管理文件是需要用戶選擇的)
 * 通過 MediaStore 保存文件時要注意,系統是通過你指定的 mime 來判斷文件類型的
 *     圖片目錄只能保存圖片類型的文件
 *     視頻目錄只能保存視頻類型的文件
 *     音頻目錄只能保存音頻類型的文件
 *     下載目錄可以保存任意類型的文件
 *
 * 注:
 * 1、如果你卸載 app 后再重裝 app,則會失去卸載 app 之前通過 MediaStore 創建的文件的所有權
 *    對於你有所有權的文件可以通過 MediaStore 訪問和刪除
 *    對於你沒有所有權的文件,不可以通過 MediaStore 刪除,但是可以通過 MediaStore 訪問(前提是先要申請 READ_EXTERNAL_STORAGE 權限)
 * 2、各種公共目錄並不是物理文件夾,而是將其他相關文件夾中的相關文件集中管理
 *    比如視頻目錄會包括 DCIM 文件夾, Movies 文件夾, Pictures 文件夾中的視頻文件
 */

package com.webabcd.androiddemo.storage;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import com.webabcd.androiddemo.R;

import java.io.OutputStream;
import java.util.Locale;

public class Android11Demo2 extends AppCompatActivity {

    private final String LOG_TAG = "Android11Demo2";

    private Button _button0;
    private Button _button1;
    private Button _button2;
    private Button _button3;
    private Button _button4;
    private ImageView _imageView1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_storage_android11demo2);

        _button0 = findViewById(R.id.button0);
        _button1 = findViewById(R.id.button1);
        _button2 = findViewById(R.id.button2);
        _button3 = findViewById(R.id.button3);
        _button4 = findViewById(R.id.button4);
        _imageView1 = findViewById(R.id.imageView1);

        sample();
    }

    private void sample() {
        _button0.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                requestPermission();
            }
        });

        _button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                createFile();
            }
        });

        _button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                queryFileFirst();
            }
        });

        _button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showFile();
            }
        });

        _button4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                deleteFileFirst();
            }
        });
    }

    // 請求“通過 MediaStore 讀取非本 app 創建的文件”的權限
    // 先在 AndroidManifest.xml 中配置 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    // 然后按照如下方式請求(注:WRITE_EXTERNAL_STORAGE 權限已經無效了)
    private void requestPermission() {
        String[] storagePermissions = { Manifest.permission.READ_EXTERNAL_STORAGE };
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, storagePermissions, 123);
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        for (int i = 0; i < permissions.length; i++) {
            Log.i(LOG_TAG, "申請的權限為:" + permissions[i] + ",申請結果:" + grantResults[i]);
        }
    }

    // 通過 MediaStore 在圖片目錄新建一個圖片文件
    private void createFile() {

        // 需要創建的圖片對象
        BitmapDrawable bitmapDrawable = (BitmapDrawable) getResources().getDrawable(R.drawable.son01, null);
        Bitmap bitmap = bitmapDrawable.getBitmap();

        ContentValues contentValues = new ContentValues();
        // 指定文件保存的文件夾名稱
        // Environment.DIRECTORY_PICTURES 值為 Pictures
        // Environment.DIRECTORY_DCIM 值為 DCIM
        // Environment.DIRECTORY_MOVIES 值為 Movies
        // Environment.DIRECTORY_MUSIC 值為 Music
        // Environment.DIRECTORY_DOWNLOADS 值為 Download
        // 如果想獲取上述文件夾的真實地址可以通過這樣的方式 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() 獲取,他返回的值類似 /storage/emulated/0/Pictures
        contentValues.put(MediaStore.Images.ImageColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/wanglei_test/");
        // 指定文件名
        contentValues.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, "son");
        // 指定文件的 mime(比如 image/jpeg, application/vnd.android.package-archive 之類的)
        contentValues.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/jpeg");
        contentValues.put(MediaStore.Images.ImageColumns.WIDTH, bitmap.getWidth());
        contentValues.put(MediaStore.Images.ImageColumns.HEIGHT, bitmap.getHeight());

        ContentResolver contentResolver = this.getContentResolver();
        // 通過 ContentResolver 在指定的公共目錄下按照指定的 ContentValues 創建文件,會返回文件的 content uri(類似這樣的地址 content://media/external/images/media/102)
        // 可以通過 MediaStore 保存文件的公共目錄有:
        // MediaStore.Images.Media.EXTERNAL_CONTENT_URI - 圖片目錄,只能保存 mime 為圖片類型的文件
        //     圖片目錄包括 Environment.DIRECTORY_PICTURES, Environment.DIRECTORY_DCIM 文件夾
        // MediaStore.Audio.Media.EXTERNAL_CONTENT_URI - 音頻目錄,只能保存 mime 為音頻類型的文件
        //     音頻目錄包括 Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_RINGTONES 等等文件夾
        // MediaStore.Video.Media.EXTERNAL_CONTENT_URI - 視頻目錄,只能保存 mime 為視頻類型的文件
        //     視頻目錄包括 DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES, Environment.DIRECTORY_DCIM 文件夾
        // MediaStore.Downloads.EXTERNAL_CONTENT_URI - 下載目錄,可以保存任意類型的文件
        //     下載目錄包括 Environment.DIRECTORY_DOWNLOADS 文件夾
        Uri uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);

        if (uri == null) {
            Log.d(LOG_TAG, "創建文件失敗");
        } else {
            Log.d(LOG_TAG, "創建文件成功:" + uri.toString());
        }

        // 寫入圖片數據
        OutputStream outputStream = null;
        try {
            outputStream = contentResolver.openOutputStream(uri);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 85, outputStream);
        } catch (Exception ex) {
            Log.d(LOG_TAG, "寫入數據失敗:" + ex.toString());
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.flush();
                    outputStream.close();
                }
            } catch (Exception ex) {

            }
        }
    }

    // 通過 MediaStore 在圖片目錄遍歷圖片文件
    // 注:
    // 1、如果你想遍歷出非本 app 創建的文件,則需要先申請 READ_EXTERNAL_STORAGE 權限
    // 2、如果你的 app 卸載后再重裝的話系統不會認為是同一個 app(也就是你卸載之前創建的文件,再次安裝 app 后必須先申請 READ_EXTERNAL_STORAGE 權限后才能獲取到)
    private int queryFileFirst() {

        int contentId = -1;

        // 通過 ContentResolver 遍歷指定公共目錄中的文件
        ContentResolver contentResolver = this.getContentResolver();
        // 第 2 個參數(projection):指定需要返回的字段,null 會返回所有字段
        // 第 3 個參數(selection):查詢的 where 語句,類似 xxx = ?
        // 第 4 個參數(selectionArgs):查詢的 where 語句的值,類似 new String[] { xxx }
        // 第 5 個參數(selectionArgs):排序語句,類似 xxx DESC
        Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null);

        Log.d(LOG_TAG, "count:" + cursor.getCount());
        while (cursor.moveToNext()) {
            // 比如這個地址 content://media/external/images/media/102 它的后面的 102 就是它的 id
            int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
            // 獲取文件名
            String title = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.TITLE));
            // 獲取文件的真實路徑,類似 /storage/emulated/0/Pictures/wanglei_test/son.jpg
            String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));

            Log.d(LOG_TAG, String.format("id:%d, title:%s, path:%s", id, title, path));
            contentId = id;
        }
        cursor.close();

        // 返回指定的公共目錄中的第一個文件的 id
        return contentId;
    }

    // 通過 MediaStore 讀取圖片目錄中的圖片,並顯示
    private void showFile() {
        // 先拿到需要顯示的圖片的 content uri(類似這樣的地址 content://media/external/images/media/102)
        int contentId = queryFileFirst();
        Uri contentUri = Uri.parse(MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "/" + contentId);
        Log.d(LOG_TAG, "show uri:" + contentUri);

        Cursor cursor = null;
        try {
            String[] projection = { MediaStore.Images.Media.DATA };
            cursor = this.getContentResolver().query(contentUri, projection, null, null, null);
            cursor.moveToFirst();
            // 拿到指定的 content uri 的真實路徑
            String path =  cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
            Log.d(LOG_TAG, String.format(Locale.ENGLISH, "contentUri:%s, path:%s", contentUri, path));

            // 顯示指定路徑的圖片
            Bitmap bitmap = BitmapFactory.decodeFile(path);
            _imageView1.setImageBitmap(bitmap);
        } catch (Exception ex) {
            Log.e(LOG_TAG, ex.toString());
        }
        finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    // 通過 MediaStore 刪除圖片目錄中的圖片文件(只能刪除本 app 創建的文件)
    // 注:如果你的 app 卸載后再重裝的話系統不會認為是同一個 app(也就是你卸載之前創建的文件,再次安裝 app 后是無法通過這種方式刪除它的)
    private void deleteFileFirst() {

        try {
            // 先拿到需要刪除的圖片的 content uri(類似這樣的地址 content://media/external/images/media/102)
            int contentId = queryFileFirst();
            Uri contentUri = Uri.parse(MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "/" + contentId);
            Log.d(LOG_TAG, "delete uri:" + contentUri);

            // 通過 ContentResolver 刪除指定的 content uri 的文件
            ContentResolver contentResolver = this.getContentResolver();
            int deleteCount = contentResolver.delete(contentUri, null);
            Log.d(LOG_TAG, "delete count:" + deleteCount);
        } catch (Exception ex) {
            // 如果你想刪除非本 app 創建的文件,就會收到類似這樣的異常 android.app.RecoverableSecurityException: com.webabcd.androiddemo has no access to content://media/external/images/media/102
            Log.e(LOG_TAG, "delete error:" + ex.toString());
        }
    }
}

/layout/activity_storage_android11demo2.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button0"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="請求“通過 MediaStore 讀取非本 app 創建的文件”的權限" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="通過 MediaStore 在圖片目錄新建一個圖片文件" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="通過 MediaStore 在圖片目錄遍歷圖片文件" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="通過 MediaStore 讀取圖片目錄中的圖片,並顯示" />

    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="通過 MediaStore 刪除圖片目錄中的圖片文件" />

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp" />

</LinearLayout>

項目地址 https://github.com/webabcd/AndroidDemo
作者 webabcd


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM