Android文件系统文件及其目录的基础知识

最近反馈了一个问题,APP存储在相册中的图片,自己上传了华为系统整了一个弹窗,说我们删除了图片,问题就是业务诉求就是同一个文件名的图片认为是同一张图片,然后下载之前先把存在的文件删除了,下载到时候直接整了一个过度文件,下载成功后改名就好,所以我们首先定位的是这个业务诉求里面的删除逻辑。

因为是相册目录,所以解决方式也很简单,将之前成删除代码修改为通过绝对路径在ContentResolver中查询到当前文件的id.然后通过id去查找到文件的uri,最后通过ContentResolver删除查询到的uri即可。通过fileProvider转化出来的Uri和file.delete() 都会被系统检测出来,并且将图片移动到最近删除目录。

我尝试过将图片下载到到download 目录,然后刷新系统图库,系统图库可以识别到这个图片,同时删除的时候也没有被移动到最近删除的目录,通过结果分析过程,应该是系统检测到图片删除,然后备份了一份到最近删除里面,只要最近删除没有,是不是说明就不提示了。那么方案就多种多样了:

  • 放到APP自己的目录下系统图库就识别不到了
  • 图片放外置卡,不放相册目录和图片目录、视频、音频等目录也行。
  • 图片不存为图片,用其他后缀代替,或者无后缀,这个看手机,有的系统即使是这样,放media 等目录下,他也会识别出来是图片,通常可以识别的。
  • 图片存到一个.xxx 这种隐藏文件夹下面。
  • 过度文件不要放media目录,当下载完成后直接拷贝一次。
  • 比如说,现在APP的问题,老板让重命名文件了。直接改业务诉求。

当然,这些结论是如何得出来的呢?当然是Android文件系统的相关知识。因为在这个过程中,逻辑存在卡顿,没有那么当丝滑,所以总结一下。

正文

Android 中的目录

在Android系统中,/system、/storage/emulated/0和/data这三个目录代表了系统目录、外部存储和用户数据目录的不同部分。

/system是Android系统的核心文件目录,其中包含各种系统级别的应用程序和文件。这个目录通常包含系统级别的应用程序、库、字体和其他关键文件。作为系统管理员或高级用户,您可以在这个目录下进行一些系统级的操作,但普通用户通常不需要访问这个目录。

APP私有目录:/data/user/0/包名

/data是Android系统中的用户数据目录,也称为内部存储。这个目录通常包含用户安装的应用程序和它们的数据文件。与外部存储不同,/data目录对于每个用户来说都是私有的,其他用户无法直接访问。这意味着每个用户只能访问自己的应用程序和数据文件。这个目录通常是在用户设备上存储应用程序数据的最主要位置之一。 app私有目录就是在用户数据目录中:/data/user/0/包名。逻辑上,除非你通过contentprovider 提供出去,一般情况下,别的APP是无法访问到你的文件的,同时往这个里面读写文件往往是不需要文件的读写权限的。

外部存储

SD卡或内部存储

/storage/emulated/0是Android系统中的外部存储目录之一。在Android系统中,外部存储通常是指设备上的存储卡或其他可移动存储设备。/storage/emulated/0目录表示外部存储设备的根目录,这个目录下通常包含设备的公共目录(如图片、音乐、下载等)和其他应用程序的数据文件。对于应用程序来说,这个目录通常是它们共享数据的默认位置之一。

在Android 10(API级别29)之前,使用MediaStore.Files.getContentUri("external")可以访问外部存储设备上的文件。但是,从Android 10开始,由于隐私和安全性的考虑,此API已被弃用,并且无法再访问外部存储设备上的文件。所以用下面的这个:

MediaStore.Files.getContentUri("external_primary")

对于公有目录的读写,则必须使用MediaStore提供的API或是SAF(存储访问框架),意味着我们不能再使用File那一套来随意操作公有目录了。

在Android 11中,没有了之前的兼容模式,不能用File的那一套,意味着以前访问 和读取App外置卡的目录失效了。使用分区存储的应用对自己创建的文件始终拥有读/写权限,无论文件是否位于应用的私有目录内。因此,如果您的应用仅保存和访问自己创建的文件,则无需请求获得 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限。若要访问其他应用创建的文件,则需要READ_EXTERNAL_STORAGE权限。并且仍然只能使用MediaStore提供的API或是SAF(存储访问框架)访问。

需要注意的是,MediaStore提供的API只能访问:图片、视频、音频,如果需要访问其它任意格式的文件,需要使用SAF(存储访问框架),它会调用系统内置的文件浏览器供用户自主选择文件。

外部SD卡和USB驱动

这是是系统支持的。

MediaStore.Files.getContentUri("external_secondary")

总结

总结起来,这三个目录的区别在于:

  • /system是Android系统的核心文件目录,包含各种系统级的应用程序和文件。
  • /storage/emulated/0是外部存储的根目录,包含公共文件和其他应用程序的数据文件。
  • /data是用户数据的目录,也称为内部存储,只供当前用户访问和使用。

文件

在Android中,对于一些文件的读写,随着系统的限制,就出现了3种类型的定位表达方式:

  • /external/images/media/201 像这种URI 类型的。
  • /storage/emulated/0/DCIM/Homate/iButler-26862-1694506242593.jpg 这种绝对路径类型的。
  • content:// 这种通过fileProvider 提供出去的。

URI 类型

这个对象可以用于网络,但是这里主要用于描述本地文件,随着系统管控,我们处理相册、视频、音频等等media文件的时候,包括增删改查等操作,都需要使用这个对象。

uri 转绝对路径或转file
public File getFileFromUri(Context context, Uri uri) {
    String filePath = null;
    Cursor cursor = null;
    try {
        cursor = context.getContentResolver().query(uri, new String[] { MediaStore.Images.Media.DATA }, null, null, null);
        if (cursor != null && cursor.moveToFirst()) {
            filePath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
        }
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return new File(filePath);
}

uri 转bitmap
public Bitmap getBitmapFromUri(Context context, Uri uri) throws IOException {
    ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r");
    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
    Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
    parcelFileDescriptor.close();
    return bitmap;
}

Uri 输入流
val contentResolver = applicationContext.contentResolver

@Throws(IOException::class)
private fun readTextFromUri(uri: Uri): String {
    val stringBuilder = StringBuilder()
    contentResolver.openInputStream(uri)?.use { inputStream ->
        BufferedReader(InputStreamReader(inputStream)).use { reader ->
            var line: String? = reader.readLine()
            while (line != null) {
                stringBuilder.append(line)
                line = reader.readLine()
            }
        }
    }
    return stringBuilder.toString()
}

uri 输出流
val contentResolver = applicationContext.contentResolver

private fun alterDocument(uri: Uri) {
    try {
        contentResolver.openFileDescriptor(uri, "w")?.use {
            FileOutputStream(it.fileDescriptor).use {
                it.write(
                    ("Overwritten at ${System.currentTimeMillis()}\n")
                        .toByteArray()
                )
            }
        }
    } catch (e: FileNotFoundException) {
        e.printStackTrace()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

uri 转 content://
  1. 从Uri对象中获取原始文件路径:
String filePath = uri.getPath();

  1. 如果原始文件路径以"/“开头,则将其替换为"content://”。否则,将原始文件路径与"content://"连接起来:
if (filePath.startsWith("/")) {
    filePath = "content://" + filePath;
} else {
    filePath = "content://" + filePath;
}

请注意,以"content://"开头的路径是表示通过Content Provider访问文件的标准方式。通过这种方式,您的应用程序可以安全地访问和操作文件,而不必关心文件的实际存储位置和访问权限。

绝对路径转URI

在 Android 中,可以使用 FileProvider 将文件的绝对路径转换为 Uri。以下是一个示例:

  1. 首先,在你的 AndroidManifest.xml 文件中添加 FileProvider 的声明:
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

  1. 在 res/xml/ 下创建一个名为 file_paths.xml 的文件,并添加你的外部存储路径:
<paths>
    <external-path name="external_files" path="." />
</paths>

  1. 然后,你可以使用以下代码将文件的绝对路径转换为 Uri
public Uri getUriFromFilePath(Context context, String filePath) {
    File file = new File(filePath);
    return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file);
}

注意:在 Android 10 (API级别 29) 及更高版本中,由于 Scoped Storage 的限制,你无法直接访问设备的外部存储。所以,你可能需要请求用户授予你访问外部存储的权限,或者将文件保存在应用的内部存储中。 这种方式获取到的uri,可以删除文件,但是会被华为系统提醒,然后把图片整到一个最近删除里面。

ContentResolver来查找图片的URI

在Android中,您可以使用ContentResolver来查找图片的URI。给定图片的路径,您可以使用以下代码来查找其对应的URI:

public Uri getImageUriFromPath(Context context, String imagePath) {
    Cursor cursor = context.getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            new String[] { MediaStore.Images.Media._ID },
            MediaStore.Images.Media.DATA + "=? ",
            new String[] { imagePath },
            null);

    if (cursor == null) {
        return null;
    }

    try {
        if (cursor.moveToFirst()) {
            int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media._ID));
            return MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()
                    .appendPath(String.valueOf(id))
                    .build();
        }
    } finally {
        cursor.close();
    }

    return null;
}

而且这种找到的uri 删除的时候,不会被华为系统提醒。

content://

在Android中,文件路径以"content://"开头是因为它使用了Content Provider。Content Provider是Android中的一种机制,用于在应用程序之间共享数据。它提供了一种标准接口,允许不同的应用程序访问和操作同一份数据。

使用Content Provider可以确保数据的安全性和一致性,因为它允许对数据进行访问控制、事务处理和冲突解决。它还提供了一种统一的方式来访问不同类型的数据,包括文件、数据库、网络资源等。

当您在Android中使用文件路径时,使用"content://"前缀表示您正在通过Content Provider访问文件。这样可以确保您的应用程序能够正确地获取和操作文件,而不必关心文件的实际存储位置和访问权限。

转URI

在Android中,文件路径以"content://"开头,表示通过Content Provider访问文件。要将"content://"开头的路径转换为Uri对象,可以使用Uri.parse()方法。例如:

String filePath = "content://com.example.app.provider/files/example.txt";
Uri uri = Uri.parse(filePath);

转绝对路径

要将Uri对象转换为绝对路径,可以使用Uri.getPath()方法。例如:

String absolutePath = uri.getPath();

如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料


在这里插入图片描述
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

  • 12
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值