Android 图片处理记录

132 阅读4分钟

1 PictureSelector 选择图片之后转Bitmap 低版本直接 BitmapFactory直接处理PathParcelFileDescriptor 高版本 使用ParcelFileDescriptor对应的api处理。

if (Build.VERSION.SDK_INT >= 29) {
    //高版本
    val imageUri =
        ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            it.id)
    try {
        contentResolver.openFileDescriptor(imageUri, "r")
            .use { pfd ->
                if (pfd != null) {
                    val bitmap =
                        BitmapFactory.decodeFileDescriptor(pfd.fileDescriptor)
                    val code = CodeUtils.parseCode(bitmap)
                }
            }
    } catch (ex: IOException) {

    }
} else {
    //低版本直接就可以转化
    val bitmap =  BitmapFactory.decodeFile(it.realPath)
    val code  = CodeUtils.parseCode(bitmap)

}

blog.csdn.net/tianjf0514/…

// 从 Assets 读取 Bitmap
Bitmap bitmap = null;
try {
    bitmap = BitmapFactory.decodeStream(getAssets().open("test.jpg"));
} catch (IOException e) {
    e.printStackTrace();
}

if (bitmap == null) return;

// 获取保存文件的 Uri
ContentResolver contentResolver = getContentResolver();
ContentValues values = new ContentValues();
Uri insertUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

// 保存图片到 Pictures 目录下
if (insertUri != null) {
    OutputStream os = null;
    try {
        os = contentResolver.openOutputStream(insertUri);
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            if (os != null) {
                os.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

上面的例子直接把图片保存到 Pictures 根目录,如果要在 Pictures 下创建子目录,需要用到 RELATIVE_PATH(Android 版本 >= 10)。

修改上面的例子,把子目录添加进 ContentValues:

ContentValues values = new ContentValues();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    // 指定子目录,否则保存到对应媒体类型文件夹根目录
    values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES +"/test");
}

还可以向 ContentValues 中添加其他信息,如:文件名,MIME 等
继续修改上面的例子:

ContentValues values = new ContentValues();
// 获取保存文件的 Uri
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
// 指定保存的文件名,如果不设置,则系统会取当前的时间戳作为文件名
values.put(MediaStore.Images.Media.DISPLAY_NAME, "test_" + System.currentTimeMillis() + ".png");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    // 指定子目录,否则保存到对应媒体类型文件夹根目录
    values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/test");
}

删除自己应用创建的文件

获取到对应的 Uri 之后 contentResolver.delete(uri,null,null) 即可。

查询自己应用创建的文件
// 查询
ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        new String[]{
                MediaStore.Images.Media._ID,
                MediaStore.Images.Media.WIDTH,
                MediaStore.Images.Media.HEIGHT
        },
        MediaStore.Images.Media._ID + " > ? ", new String[]{"100"},
        MediaStore.Images.Media._ID + " DESC"
);

// 得到所有的 Uri
List<Uri> filesUris = new ArrayList<>();
while (cursor.moveToNext()) {
    int index = cursor.getColumnIndex(MediaStore.Images.Media._ID);
    Uri uri = ContentUris.withAppendedId(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(index)
    );
    filesUris.add(uri);
}
cursor.close();

// 通过 Uri 获取具体内容并显示到界面上
ParcelFileDescriptor pfd = null;
try {
    pfd = contentResolver.openFileDescriptor(filesUris.get(0), "r");
    if (pfd != null) {
        Bitmap bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
        ((ImageView) findViewById(R.id.image)).setImageBitmap(bitmap);
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if (pfd != null) {
        try {
            pfd.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

查询其他应用创建的文件 如上文所诉,访问自己应用创建的文件不需要 READ_EXTERNAL_STORAGE 权限。以上代码获取到的 filesUris 只包含本应用之前创建的文件。 如果需要连其他应用的文件一起获取,则申请下 READ_EXTERNAL_STORAGE 权限即可。

修改其他应用创建的文件

同理,需要申请 WRITE_EXTERNAL_STORAGE 权限。
但是,即便申请了 WRITE_EXTERNAL_STORAGE 权限之后,还是会报如下异常:

android.app.RecoverableSecurityException: xxx has no access to content://media/external/images/media/100

这是因为还需要向用户申请修改的权限。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    try {
        delete();
    } catch (RecoverableSecurityException e) {
        e.printStackTrace();
        // 弹出对话框,向用户申请修改其他应用文件的权限
        requestConfirmDialog(e);
    }
}

private void delete() {
    Uri uri = Uri.parse("content://media/external/images/media/100");
    getContentResolver().delete(uri, null, null);
}

@RequiresApi(api = Build.VERSION_CODES.Q)
private void requestConfirmDialog(RecoverableSecurityException e) {
    try {
        startIntentSenderForResult(
                e.getUserAction().getActionIntent().getIntentSender()
                , 0, null, 0, 0, 0, null);
    } catch (IntentSender.SendIntentException ex) {
        ex.printStackTrace();
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == RESULT_OK){
        delete();
    }
}

将文件下载到 Download 目录
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

    private void downloadApkAndInstall(String downloadUrl, String apkName) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            // 使用原始方式
        } else {
            new Thread(() -> {
                BufferedInputStream bis = null;
                BufferedOutputStream bos = null;
                try {
                    URL url = new URL(downloadUrl);
                    URLConnection urlConnection = url.openConnection();
                    InputStream is = urlConnection.getInputStream();
                    bis = new BufferedInputStream(is);
                    ContentValues values = new ContentValues();
                    values.put(MediaStore.MediaColumns.DISPLAY_NAME, apkName);
                    values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
                    ContentResolver contentResolver = getContentResolver();
                    Uri uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
                    OutputStream os = contentResolver.openOutputStream(uri);
                    bos = new BufferedOutputStream(os);
                    byte[] buffer = new byte[1024];
                    int bytes = bis.read(buffer);
                    while (bytes >= 0) {
                        bos.write(buffer, 0, bytes);
                        bos.flush();
                        bytes = bis.read(buffer);
                    }
                    runOnUiThread(() -> installAPK(uri));
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (bis != null) bis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    try {
                        if (bos != null) bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    private void installAPK(Uri uri) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
        startActivity(intent);
    }

SAF

SAF 在 Android 4.4 就支持了。
SAF 通过系统提供的标准化 UI 浏览和修改手机中的文件。

ACTION_CREATE_DOCUMENT 创建文件
    private void createFile() {
        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("image/*");
        intent.putExtra(Intent.EXTRA_TITLE, "test_create.png");
        startActivityForResult(intent, WRITE_REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (data == null || resultCode != RESULT_OK) return;
        if (requestCode == WRITE_REQUEST_CODE) {
            Log.d("tianjf", "write uri : " + data.getData());
        }
    }

运行之后,会启动标准文件管理器 UI 保存文件。
写文件不需要申请写权限。

CTION_OPEN_DOCUMENT 读文件

因为有可能读取其他应用创建的文件,所以需要申请读权限。

    protected void readFiles() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("image/*");
        startActivityForResult(intent, READ_REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (data == null || resultCode != RESULT_OK) return;
        if (requestCode == READ_REQUEST_CODE) {
            Log.d("tianjf", "read uri : " + data.getData());
            process(data.getData());
        }
    }

    private void process(Uri uri) {
        String[] selectionArgs = new String[]{DocumentsContract.getDocumentId(uri).split(":")[1]};
        Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                null, MediaStore.Images.Media._ID + "=?",
                selectionArgs, null);
        if (null != cursor) {
            if (cursor.moveToFirst()) {
                int index = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
                if (index > -1) {
                    String path = cursor.getString(index);
                    Log.d("tianjf", "onActivityResult path=" + path + ";id=" + selectionArgs[0]);
                }
            }
            cursor.close();
        }
    }

ACTION_OPEN_DOCUMENT_TREE 读取文件夹
    protected void readFolder() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        startActivityForResult(intent, READ_FOLDER_REQUEST_CODE);
    }

    // 选取文件夹然后在文件夹中创建子文件夹和文件
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (data == null || resultCode != RESULT_OK) return;
        if (requestCode == READ_FOLDER_REQUEST_CODE) {
            Log.d("tianjf", "read folder uri : " + data.getData());
            DocumentFile selectedFolder = DocumentFile.fromTreeUri(this, data.getData());
            DocumentFile newFolder = selectedFolder.createDirectory("newFolder");
            DocumentFile newFile = newFolder.createFile("text/plain", "test.txt");
            try {
                getContentResolver().openOutputStream(newFile.getUri()).write("Hello".getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

查询相册中的图片并转成bitmap

String sortOrder = MediaStore.Images.Media.DATE_MODIFIED + " DESC ";
Cursor cursor = MyApp.app.getContentResolver().query(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, sortOrder);

if (null != cursor && cursor.moveToFirst()) {
    do {
        int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
        long id = cursor.getLong(idColumn);
        Uri contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
        try {
            ParcelFileDescriptor pfd =  MyApp.app.getContentResolver().openFileDescriptor(contentUri, "r");
            if (pfd != null) {
                Bitmap bitmap =
                        BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
            }
        } catch (IOException exception) {

        }
    } while (cursor.moveToNext());
    cursor.close();
}