• 一手遮天 Android


    项目地址 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

  • 相关阅读:
    【DFS】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem D. Divisibility Game
    【二分】【三分】【计算几何】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem L. Lines and Polygon
    【线段树】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem J. Jedi Training
    【贪心】【后缀自动机】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem E. Enter the Word
    【转载】随机生成k个范围为1-n的随机数,其中有多少个不同的随机数?
    【推导】【贪心】XVII Open Cup named after E.V. Pankratiev Stage 4: Grand Prix of SPb, Sunday, Octorber 9, 2016 Problem H. Path or Coloring
    【枚举】XVII Open Cup named after E.V. Pankratiev Stage 4: Grand Prix of SPb, Sunday, Octorber 9, 2016 Problem D. Cutting Potatoes
    【找规律】【递归】XVII Open Cup named after E.V. Pankratiev Stage 4: Grand Prix of SPb, Sunday, Octorber 9, 2016 Problem F. Doubling
    【贪心】Codeforces Round #436 (Div. 2) D. Make a Permutation!
    【计算几何】【圆反演】计蒜客17314 2017 ACM-ICPC 亚洲区(南宁赛区)网络赛 G. Finding the Radius for an Inserted Circle
  • 原文地址:https://www.cnblogs.com/webabcd/p/android_storage_Android11Demo2.html
Copyright © 2020-2023  润新知