Android13适配所有文件管理权限

Android13适配所有文件管理权限

前言:

很早之前在Android11上面就适配过所有文件管理权限,这次是海外版升级到Android13,由于选择相册用的是第三方库,组内的同事没有上架Google的经验直接就提交代码,虽然功能没有问题,但是上架的时候被打回了,于是记录一下适配工作.

1.简介:

绝大多数需要共享存储空间访问权限的应用都可以遵循共享媒体文件和共享非媒体文件方面的最佳做法。然而,某些应用的核心用例需要广泛访问设备上的文件,但无法采用注重隐私保护的存储最佳实践高效地访问这些文件。对于这些情况,Android 提供了一种名为“所有文件访问权”的特殊应用访问权限。

例如,防病毒应用的主要用例可能需要定期扫描不同目录中的许多文件。如果此扫描需要反复的用户交互,让其使用系统文件选择器选择目录,就会带来糟糕的用户体验。其他用例(如文件管理器应用、备份和恢复应用以及文档管理应用)也需要考虑类似情况。

2.Google Play通知:

此部分为在 Google Play 上发布应用的开发者提供通知。

为了限制对共享存储的广泛访问,Google Play 商店已更新其政策,用来评估以 Android 11(API 级别 30)或更高版本为目标平台且通过 MANAGE_EXTERNAL_STORAGE 权限请求“所有文件访问权”的应用。此政策自 2021 年 5 月起生效。

当应用以 Android 11 或更高版本为目标平台并声明了 MANAGE_EXTERNAL_STORAGE 权限时,Android Studio 会显示图 1 中所示的 lint 警告。此警告会提醒您:“Google Play 商店的一项政策限制了对该权限的使用”。
在这里插入图片描述

图 1. Android Studio 中的 Lint 警告,提醒开发者有关 MANAGE_EXTERNAL_STORAGE 权限的 Google Play 政策。

仅当您的应用无法有效利用更有利于保护隐私的 API(如存储访问框架或 Media Store API)时,您才能请求 MANAGE_EXTERNAL_STORAGE 权限。您的应用对该权限的使用必须在允许的使用情形范围内,并且必须与应用的核心功能直接相关。如果您的应用包含与以下任一项类似的用例,可能会请求 MANAGE_EXTERNAL_STORAGE 权限:

  • 文件管理器
  • 备份和恢复应用
  • 防病毒应用
  • 文档管理应用
  • 设备上的文件搜索
  • 磁盘和文件加密
  • 设备到设备数据迁移

3.选择相册图片:

由于使用的是io.github.lucksiege:pictureselector这个库,版本为v3.10.9:

PictureSelector.create(this)
    .openGallery(SelectMimeType.ofImage())
    .setImageEngine(GlideEngine.createGlideEngine())
    .forResult(object : OnResultCallbackListener<LocalMedia?> {
        override fun onResult(result: ArrayList<LocalMedia?>) {
            LogUtils.d("===返回的图片地址为===", result[0]!!.path)
            Glide.with(this@MainActivity).load(result[0]?.path).into(ivBg)
        }

        override fun onCancel() {}
    })

4.使用系统拍照:

PictureSelector.create(this)
    .openCamera(SelectMimeType.ofImage())
    .forResult(object : OnResultCallbackListener<LocalMedia?> {
        override fun onResult(result: ArrayList<LocalMedia?>) {
            LogUtils.d("===返回的图片地址为===", result[0]!!.path)
            Glide.with(this@MainActivity).load(result[0]?.path).into(ivCamera)
        }

        override fun onCancel() {}
    })

5.打开系统相册:

    private fun openPhotoAlbum() {
        val intent = Intent(Intent.ACTION_GET_CONTENT)
        intent.addCategory(Intent.CATEGORY_OPENABLE)
        intent.type = "image/*"
        //1.以startActivityForResult方式打开Activity
/*        startActivityForResult(
            Intent.createChooser(intent, "File Browser"), Constants.FILE_CHOOSER_RESULT_CODE)*/
        //2.以launch方式打开相册
        photoLaunch.launch("image/*")
    }

6.未适配前的效果如下:

在这里插入图片描述

7.去掉本项目的所有文件管理权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    tools:ignore="ScopedStorage" android:maxSdkVersion="32"/>

在这里插入图片描述

8.去掉后权限后再次运行:

在这里插入图片描述

还是提示申请所有文件管理权限,去github查看图片库的版本,发现新版本已经适配了Android13和所有文件管理权限,于是更新一下图片库的依赖版本.

dependencies {
    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.8.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    implementation("io.github.lucksiege:pictureselector:v3.11.1")
    implementation("com.github.bumptech.glide:glide:4.15.1")
    annotationProcessor("com.github.bumptech.glide:compiler:4.15.1")
    implementation("com.blankj:utilcodex:1.31.1")
}

9.新版本ImageEngine:

/**
 * 加载图片
 *
 * @param context   上下文
 * @param url       资源url
 * @param imageView 图片承载控件
 */
@Override
public void loadImage(Context context, String url, ImageView imageView) {
    if (!ActivityCompatHelper.assertValidRequest(context)) {
        return;
    }
    Glide.with(context)
            .load(url)
            .into(imageView);
}

@Override
public void loadImageBitmap(@NonNull Context context, @NonNull String url, int maxWidth, int maxHeight, OnCallbackListener<Bitmap> call) {

}

/**
 * 加载相册目录封面
 *
 * @param context   上下文
 * @param url       图片路径
 * @param imageView 承载图片ImageView
 */
@Override
public void loadAlbumCover(Context context, String url, ImageView imageView) {
    if (!ActivityCompatHelper.assertValidRequest(context)) {
        return;
    }
    Glide.with(context)
            .asBitmap()
            .load(url)
            .override(180, 180)
            .sizeMultiplier(0.5f)
            .transform(new CenterCrop(), new RoundedCorners(8))
            .placeholder(R.drawable.ps_image_placeholder)
            .into(imageView);
}


/**
 * 加载图片列表图片
 *
 * @param context   上下文
 * @param url       图片路径
 * @param imageView 承载图片ImageView
 */
@Override
public void loadGridImage(Context context, String url, ImageView imageView) {
    if (!ActivityCompatHelper.assertValidRequest(context)) {
        return;
    }
    Glide.with(context)
            .load(url)
            .override(200, 200)
            .centerCrop()
            .placeholder(R.drawable.ps_image_placeholder)
            .into(imageView);
}

@Override
public void pauseRequests(Context context) {
    if (!ActivityCompatHelper.assertValidRequest(context)) {
        return;
    }
    Glide.with(context).pauseRequests();
}

@Override
public void resumeRequests(Context context) {
    if (!ActivityCompatHelper.assertValidRequest(context)) {
        return;
    }
    Glide.with(context).resumeRequests();
}

private MyImageGlideEngine() {
}

private static final class InstanceHolder {
    static final MyImageGlideEngine instance = new MyImageGlideEngine();
}

public static MyImageGlideEngine createGlideEngine() {
    return InstanceHolder.instance;
}

在这里插入图片描述

package com.example.allfilemanagerdemo.utils;

import android.content.Context;
import android.graphics.Bitmap;
import android.widget.ImageView;

import androidx.annotation.NonNull;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.example.allfilemanagerdemo.R;
import com.luck.picture.lib.engine.ImageEngine;
import com.luck.picture.lib.interfaces.OnCallbackListener;
import com.luck.picture.lib.utils.ActivityCompatHelper;

/**
 * @author:luck
 * @date:2019-11-13 17:02
 * @describe:Glide加载引擎
 */
public class MyImageGlideEngine implements ImageEngine {

    /**
     * 加载图片
     *
     * @param context   上下文
     * @param url       资源url
     * @param imageView 图片承载控件
     */
    @Override
    public void loadImage(Context context, String url, ImageView imageView) {
        if (!ActivityCompatHelper.assertValidRequest(context)) {
            return;
        }
        Glide.with(context)
                .load(url)
                .into(imageView);
    }

    @Override
    public void loadImage(Context context, ImageView imageView, String url, int maxWidth, int maxHeight) {
        if (!ActivityCompatHelper.assertValidRequest(context)) {
            return;
        }
        Glide.with(context)
                .load(url)
                .override(maxWidth, maxHeight)
                .into(imageView);
    }

    /**
     * 加载相册目录封面
     *
     * @param context   上下文
     * @param url       图片路径
     * @param imageView 承载图片ImageView
     */
    @Override
    public void loadAlbumCover(Context context, String url, ImageView imageView) {
        if (!ActivityCompatHelper.assertValidRequest(context)) {
            return;
        }
        Glide.with(context)
                .asBitmap()
                .load(url)
                .override(180, 180)
                .sizeMultiplier(0.5f)
                .transform(new CenterCrop(), new RoundedCorners(8))
                .placeholder(R.drawable.ps_image_placeholder)
                .into(imageView);
    }


    /**
     * 加载图片列表图片
     *
     * @param context   上下文
     * @param url       图片路径
     * @param imageView 承载图片ImageView
     */
    @Override
    public void loadGridImage(Context context, String url, ImageView imageView) {
        if (!ActivityCompatHelper.assertValidRequest(context)) {
            return;
        }
        Glide.with(context)
                .load(url)
                .override(200, 200)
                .centerCrop()
                .placeholder(R.drawable.ps_image_placeholder)
                .into(imageView);
    }

    @Override
    public void pauseRequests(Context context) {
        if (!ActivityCompatHelper.assertValidRequest(context)) {
            return;
        }
        Glide.with(context).pauseRequests();
    }

    @Override
    public void resumeRequests(Context context) {
        if (!ActivityCompatHelper.assertValidRequest(context)) {
            return;
        }
        Glide.with(context).resumeRequests();
    }

    private MyImageGlideEngine() {
    }

    private static final class InstanceHolder {
        static final MyImageGlideEngine instance = new MyImageGlideEngine();
    }

    public static MyImageGlideEngine createGlideEngine() {
        return InstanceHolder.instance;
    }
}

10.Android13文件读写权限适配:

private fun initPermission() {
    if (checkPermissions()) {
        takePhoto()
    } else {
        requestPermission()
    }
}
private fun requestPermission() {
    when {
        Build.VERSION.SDK_INT >= 33 -> {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(
                    Manifest.permission.READ_MEDIA_IMAGES,
                    Manifest.permission.READ_MEDIA_AUDIO,
                    Manifest.permission.READ_MEDIA_VIDEO,
                    Manifest.permission.CAMERA,
                ),
                Constants.REQUEST_CODE_PERMISSIONS
            )
        }

        else -> {
            ActivityCompat.requestPermissions(
                this,
                REQUIRED_PERMISSIONS,
                Constants.REQUEST_CODE_PERMISSIONS
            )
        }
    }
}
private fun checkPermissions(): Boolean {
    when {
        Build.VERSION.SDK_INT >= 33 -> {
            val permissions = arrayOf(
                Manifest.permission.READ_MEDIA_IMAGES,
                Manifest.permission.READ_MEDIA_AUDIO,
                Manifest.permission.READ_MEDIA_VIDEO,
                Manifest.permission.CAMERA,
            )
            for (permission in permissions) {
                return Environment.isExternalStorageManager()
            }
        }

        else -> {
            for (permission in REQUIRED_PERMISSIONS) {
                if (ContextCompat.checkSelfPermission(
                        this,
                        permission
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    return false
                }
            }
        }
    }
    return true
}
override fun onRequestPermissionsResult(
    requestCode: Int, permissions: Array<String>, grantResults:
    IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    when (requestCode) {
        Constants.REQUEST_CODE_PERMISSIONS -> {
            var allPermissionsGranted = true
            for (result in grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allPermissionsGranted = false
                    break
                }
            }
            when {
                allPermissionsGranted -> {
                    // 权限已授予,执行文件读写操作
                    takePhoto()
                }

                else -> {
                    // 权限被拒绝,处理权限请求失败的情况
                    ToastUtils.showShort("请您打开必要权限")
                    requestPermission()
                }
            }
        }

    }
}

11.打开相册:

这里有两种方式:

11.1 以startActivityForResult方式打开

    private fun openPhotoAlbum() {
        val intent = Intent(Intent.ACTION_GET_CONTENT)
        intent.addCategory(Intent.CATEGORY_OPENABLE)
        intent.type = "image/*"
        //以startActivityForResult方式打开Activity
       startActivityForResult(
            Intent.createChooser(intent, "File Browser"), Constants.FILE_CHOOSER_RESULT_CODE)
    }

11.2 以launch方式打开

private fun openPhotoAlbum() {
     //以launch方式打开相册
      photoLaunch.launch("image/*")
}

12.打开相册结果回调:

12.1 startActivityResult方式结果回调:

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode != RESULT_OK) {
            return
        }
        when (requestCode) {
            Constants.FILE_CHOOSER_RESULT_CODE -> {
                if (data?.data == null) {
                    return
                }
                val imgStr: String = getImageAbsolutePath(this, data.data).toString()
                if (!TextUtils.isEmpty(imgStr)) {
                    val imgUri: Uri? = FileUtils.getUriFromPath(imgStr, application)
                    if (imgUri != null) {
                        Glide.with(this@MainActivity).load(imgUri.toString()).into(ivPhoto)
                    }
                }
            }
        }
    }
}

12.2 launch方式结果回调:

private val photoLaunch =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
    Glide.with(this@MainActivity).load(uri.toString()).into(ivPhoto)
}

13.实现的效果如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.项目源码如下:

https://gitee.com/jackning_admin/all-file-manager-demo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/250261.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3.qml 3D-Node类学习

Node类是在View3D 中的对象基础组件&#xff0c;用于表示3D空间中的对象&#xff0c;类似于Qt Quick 2D场景中的Item&#xff0c;介绍如下所示&#xff1a; 如上图可以看到&#xff0c;Node类的子类非常多&#xff0c;比如Model类(显示3D模型)、ParticleSystem3D粒子系统类、Li…

Guardrails for Amazon Bedrock 基于具体使用案例与负责任 AI 政策实现定制式安全保障(预览版)

作为负责任的人工智能&#xff08;AI&#xff09;战略的一部分&#xff0c;您现在可以使用 Guardrails for Amazon Bedrock&#xff08;预览版&#xff09;&#xff0c;实施专为您的用例和负责任的人工智能政策而定制的保障措施&#xff0c;以此促进用户与生成式人工智能应用程…

C#爬虫1688以图搜图API接口功能的实现

背景 在1688有个功能&#xff0c;就是上传图片&#xff0c;就可以找到类似的商品。如下 网址 &#xff1a;https://www.1688.com/ 这时候&#xff0c;我们可以使用程序来代替&#xff0c;大批量的完成图片上传功能。 实现思路 1、找到图片上传接口 post请求&#xff0c;for…

R、python读取空间转录组的8种方式

“ 空间转录组测序主要包括5个步骤&#xff0c;我们着重下游分析部分&#xff1a;空转数据分析和可视化。本篇主分享如何使用python和R读取空转数据&#xff0c;主要使用scanpy stlearn seurat包” 引言 在正式开始之前&#xff0c;我们先看看cellranger流程跑完之后&#xff0…

杰卡德的故事

三个男人分别是杰卡德距离 杰卡德相似系数和杰卡德系数 杰卡德相似系数和杰卡德距离是互为相反数的。 杰卡德系数和杰卡德距离是不是一回事 感觉是一回事

【论文阅读】Uncertainty-aware Self-training for Text Classification with Few Label

论文下载 GitHub bib: INPROCEEDINGS{mukherjee-awadallah-2020-ust,title "Uncertainty-aware Self-training for Few-shot Text Classification",author "Subhabrata Mukherjee and Ahmed Hassan Awadallah",booktitle "NeurIPS",yea…

mybatis高级扩展-插件和分页插件PageHelper

1、建库建表 create database mybatis-example; use mybatis-example; create table emp (empNo varchar(40),empName varchar(100),sal int,deptno varchar(10) ); insert into emp values(e001,张三,8000,d001); insert into emp values(e002,李四,9000,d001); insert into…

OpenHarmony应用开发——创建第一个OpenHarmonry工程

一、前言 本文主要介绍DevEco Studio的相关配置&#xff0c;以及创建第一个OpenHarmony应用程序。 二、详细步骤 打开DevEco Studio. 进入Settings. 随后SDK选择OpenHarmony&#xff0c;并完成下述API的选择与下载. 等待下载完成后&#xff0c;创建第一个Project. 此处选择Emp…

在React中实现好看的动画Framer Motion(案例:跨DOM元素平滑过渡)

前言 介绍 Framer Motion 是一个适用于 React 网页开发的动画库&#xff0c;它可以让开发者轻松地在他们的项目中添加复杂和高性能的动画效果。该库提供了一整套针对 React 组件的动画、过渡和手势处理功能&#xff0c;使得通过声明式的 API 来创建动画变得简单直观。 接下来…

ChatGPT4 Excel 高级组合函数用法index+match完成实际需求

在Excel 函数用法中有一对组合函数使用是非常多的,那就是Index+match组合函数。 接下来我们用一个实际的需求让ChatGPT来帮我们实现一下。 我们给ChatGPT4发送一个prompt:有一个表格A2至A14为业务员B列至H列为1月至7月的销售额,请根据J2单元格的业务员与K2单元格的月份查找出…

DevOps搭建(二)-阿里云镜像仓库的使用详解

博主介绍&#xff1a;Java领域优质创作者,博客之星城市赛道TOP20、专注于前端流行技术框架、Java后端技术领域、项目实战运维以及GIS地理信息领域。 &#x1f345;文末获取源码下载地址&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb;…

使用令牌桶和漏桶实现请求限流逻辑

实现请求限流 令牌桶算法原理实现案例案例目的:实例demo运行结果: 漏桶算法原理:实现案例:案例目的:案例代码运行结果: 令牌桶算法和漏桶算法是两种常用的限流算法&#xff0c;用于控制系统对请求或数据的访问速率。下面分别详细解释这两种算法的原理. 令牌桶算法 原理 令牌桶…

前端传递参数,后端如何接收

目录 简单参数 传递方式 获取方式一 获取方式二 相关注解 实体参数 数组集合参数 传递方式 相关注解 获取方式一 获取方式二 日期参数 传递方式 相关注解 获取方式 json参数 传递方式 相关注解 获取方式 路径参数 传递方式 相关注解 获取方式 传递多个…

DHCP最全讲解!(原理+配置)

一、概述 随着网络规模的不断扩大&#xff0c;网络复杂度不断提升&#xff0c;网络中的终端设备例如主机、手机、平板等&#xff0c;位置经常变化。终端设备访问网络时需要配置IP地址、网关地址、DNS服务器地址等。采用手工方式为终端配置这些参数非常低效且不够灵活。IETF于19…

day04-报表技术PDF

1 EasyPOI导出word 需求&#xff1a;使用easyPOI方式导出合同word文档 Word模板和Excel模板用法基本一致&#xff0c;支持的标签也是一致的&#xff0c;仅仅支持07版本的word也是只能生成后缀是docx的文档&#xff0c;poi对doc支持不好所以easyPOI中就没有支持doc&#xff0c…

【Linux】内核结构

一、Linux内核结构介绍 Linux内核结构框图 二、图解Linux系统架构 三、驱动认知 1、为什么要学习写驱动2、文件名与设备号3、open函数打通上层到底层硬件的详细过程 四、Shell Shell脚本 一、Linux内核结构介绍 Linux 内核是操作系统的核心部分&#xff0c;它负责管理系…

数据结构 之map/set练习

文章目录 1. 只出现一次的数字算法原理&#xff1a;代码&#xff1a; 2. 随机链表的复制算法原理&#xff1a;代码&#xff1a; 3. 宝石与石头算法原理&#xff1a;代码&#xff1a; 4. 坏键盘打字算法原理&#xff1a;代码&#xff1a; 5. 前K个高频单词算法原理&#xff1a;代…

UGUI 鼠标悬浮UI出现弹框,鼠标在图片边缘出现闪烁

1、背景&#xff1a;鼠标悬浮在UI上出现提示框 public class SpecialParam_list : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler {public void OnPointerEnter(PointerEventData eventData){TipBox.Instance.ShowBox(Input.mousePosition, value);}public void …

【从零开始学习--设计模式--代理模式】

返回首页 前言 感谢各位同学的关注与支持&#xff0c;我会一直更新此专题&#xff0c;竭尽所能整理出更为详细的内容分享给大家&#xff0c;但碍于时间及精力有限&#xff0c;代码分享较少&#xff0c;后续会把所有代码示例整理到github&#xff0c;敬请期待。 此章节介绍建…

基于中小微企业_个体工商户的信贷评分卡模型和用户画像(论文_专利_银行调研建模使用)

背景介绍 信用贷款是指由银行或其他金融机构向中小微企业和个体工商户提供的一种贷款产品。该贷款的特点是无需提供抵押品或担保&#xff0c;主要依据借款人的信用状况来进行评估和审批。 中小微企业和个体工商户信用贷款的申请流程相对简单&#xff0c;申请人只需要提供个人…