zoukankan      html  css  js  c++  java
  • 一手遮天 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

  • 相关阅读:
    LeetCode第1题(two sum) 暴力法性能优化
    java.sql.SQLException: Column 'id' not found.
    数据分页显示 之 确定总页码数(优化)
    IntelliJ IDEA 提示"Form input without an associated label or title attribute"
    在JSP中图片上传到服务器后无法读取(已解决)
    The maximum-subarray problem
    Chapter 2:Getting Started
    Merge Sort in Java, C,C++ and Python
    Chapter 9 :Further Applications of Integration
    gSoap 中文传输
  • 原文地址:https://www.cnblogs.com/webabcd/p/android_storage_Android11Demo2.html
Copyright © 2011-2022 走看看