📜 童话:FileProvider之魔法快递公司的秘密

在Android王国,每个App精灵的​​文件仓库​ ​都藏在加密结界里(私有目录)。当图图精灵想给文文管家寄送"水晶画卷"(大文件)时,普通快递员(file://)会被王国护卫队(Android 7.0+的StrictMode)拦截,并大喊:

​"禁止暴露仓库地址!违者重罚!"​ ​(抛出FileUriExposedException

这时,​​FileProvider魔法快递公司​​挺身而出:

"别慌!我能把仓库地址加密成​​魔法运单号(Content URI)​​,只有持临时钥匙的人才能取货!"


🧙‍♂️ ​​第一章:绘制魔法地图(路径映射配置)​

📜 童话剧情

快递公司有一张​​魔法羊皮纸(file_paths.xml)​​,记录着哪些仓库可开放:

xml 复制代码
xml
Copy
<!-- res/xml/file_paths.xml -->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 把 /sdcard/Android/data/com.tutu/files 映射为代号 "tutu_warehouse" -->
    <external-files-path name="tutu_warehouse" path="." />
</paths>

​源码解析​ ​(FileProvider.java的路径解析逻辑):

ini 复制代码
java
Copy
// 1. 解析XML中的路径配置
XmlResourceParser parser = loadXmlResource(context, authority);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
    String tag = parser.getName(); // 如external-files-path
    String name = parser.getAttributeValue(null, "name"); // 如tutu_warehouse
    String path = parser.getAttributeValue(null, "path"); // 如.

    // 2. 将标签转为真实目录(核心代码!)
    File targetDir = parsePathTag(context, tag, path); 
    // 例如:external-files-path -> context.getExternalFilesDir(null)
    
    // 3. 存入映射表:代号 -> 真实路径
    strategy.addRoot(name, targetDir); // HashMap<String, File>
}

​为什么是加密?​

真实路径/sdcard/...被替换为虚拟代号tutu_warehouse,外界无法反向推导。


🚚 ​​第二章:生成魔法运单(URI加密)​

📜 童话剧情

图图精灵要把文件/sdcard/.../files/video.mp4寄出。快递公司:

"用代号tutu_warehouse替换仓库地址,生成运单号:​content://com.tutu.fileprovider/tutu_warehouse/video.mp4​"

⚙️ ​​源码解析​ ​(getUriForFile()方法)

scss 复制代码
java
Copy
public static Uri getUriForFile(Context ctx, String authority, File file) {
    PathStrategy strategy = getPathStrategy(ctx, authority);
    // 1. 匹配文件路径 -> 提取虚拟路径
    String virtualPath = strategy.getPathForFile(file); 
    // 返回 "tutu_warehouse/video.mp4"
    
    // 2. 拼接为Content URI
    return new Uri.Builder()
            .scheme("content")
            .authority(authority) // com.tutu.fileprovider
            .encodedPath(virtualPath) // 加密后的路径
            .build();
}

​路径匹配的魔法规则​​:

  1. 遍历所有仓库目录,找到​​最长匹配前缀​ ​(如文件/a/b/c.txt匹配仓库/a/b/

  2. 用仓库代号替换匹配部分(tutu_warehouse替换/a/b/

  3. 保留子路径(c.txt


🔑 ​​第三章:凭运单取货(URI还原路径)​

📜 童话剧情

文文管家收到运单号content://com.tutu.fileprovider/tutu_warehouse/video.mp4和临时钥匙(Intent.FLAG_GRANT_READ_URI_PERMISSION)。快递公司:

"扫描运单代号tutu_warehouse → 查魔法地图 → 还原真实路径/sdcard/.../video.mp4!"

⚙️ ​​源码解析​ ​(openFile()方法)

typescript 复制代码
java
Copy
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) {
    // 1. 拆分运单号:获取虚拟路径
    String virtualPath = uri.getPath(); // "tutu_warehouse/video.mp4"
    
    // 2. 解析虚拟路径 -> 还原真实文件
    File file = strategy.getFileForUri(uri);
    // 内部逻辑:拆分 virtualPath → 查表获取仓库真实路径 → 拼接子路径
    
    // 3. 防御邪恶巫师的路径篡改!
    if (file.getCanonicalPath().contains("../")) {
        throw new SecurityException("非法路径穿越!");
    }
    
    // 4. 打开文件返回描述符
    return ParcelFileDescriptor.open(file, mode);
}

​安全防御机制​​:

  • 检查路径是否包含..(防止穿越目录攻击)

  • 临时权限由系统自动回收,运单号过期失效


🛡️ ​​第四章:魔法公司的防御系统(源码安全设计)​

  1. ​路径隔离​ ​:不同App的authority不同(包名隔离),避免运单号冲突

  2. ​写入防御​ ​:禁用insert/update方法,防止外部篡改文件

    typescript 复制代码
    java
    Copy
    public Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException("禁止外部写入!");
    }
  3. ​运单号校验​ ​:URI格式错误时抛出IllegalArgumentException


⚠️ ​​魔法使用避坑指南​

xml 复制代码
kotlin
Copy
// 错误!文件不在配置路径内 → 加密失败
val file = File("/system/secret.conf")
FileProvider.getUriForFile(context, authority, file) // 抛异常!

// 正确:配置需覆盖文件父目录
<external-path name="root" path="/" /> <!-- 开放根目录 -->
  1. ​仓库门没关​ ​:

    忘记InputStream.close() → 文件描述符泄漏(王国驿站堆满未回收的运单)

    ​解法​ ​:用.use { }自动关流

  2. ​忘带临时钥匙​ ​:

    漏写intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION) → 收件方无法取货


📜 ​​终极魔法手册(技术总结)​

​魔法道具​ ​技术本质​ ​安全作用​
魔法羊皮纸 file_paths.xml 隐藏真实路径,建立虚拟代号
运单生成器 getUriForFile() 路径加密:真实路径 → 虚拟路径
运单解析镜 openFile() 路径还原:虚拟路径 → 真实路径
临时钥匙 FLAG_GRANT_READ_URI_PERMISSION 动态权限控制,超时自动失效

​魔法口诀​ ​:

"文件路径是机密,虚拟代号来加密。

运单生成查地图,还原路径靠解析。

临时钥匙防偷窃,安全共享零压力!"

------ Android王国·FileProvider魔法手册

现在,图图和文文已经学会了用魔法安全传输文件。打开Android Studio,用这段代码试试魔法吧!

相关推荐
Kapaseker1 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴2 小时前
Android17 为什么重写 MessageQueue
android
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android