在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();
}
路径匹配的魔法规则:
-
遍历所有仓库目录,找到最长匹配前缀 (如文件
/a/b/c.txt
匹配仓库/a/b/
) -
用仓库代号替换匹配部分(
tutu_warehouse
替换/a/b/
) -
保留子路径(
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);
}
安全防御机制:
-
检查路径是否包含
..
(防止穿越目录攻击) -
临时权限由系统自动回收,运单号过期失效
🛡️ 第四章:魔法公司的防御系统(源码安全设计)
-
路径隔离 :不同App的
authority
不同(包名隔离),避免运单号冲突 -
写入防御 :禁用
insert/update
方法,防止外部篡改文件typescriptjava Copy public Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException("禁止外部写入!"); }
-
运单号校验 :URI格式错误时抛出
IllegalArgumentException
⚠️ 魔法使用避坑指南
xml
kotlin
Copy
// 错误!文件不在配置路径内 → 加密失败
val file = File("/system/secret.conf")
FileProvider.getUriForFile(context, authority, file) // 抛异常!
// 正确:配置需覆盖文件父目录
<external-path name="root" path="/" /> <!-- 开放根目录 -->
-
仓库门没关 :
忘记
InputStream.close()
→ 文件描述符泄漏(王国驿站堆满未回收的运单)解法 :用
.use { }
自动关流 -
忘带临时钥匙 :
漏写
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION)
→ 收件方无法取货
📜 终极魔法手册(技术总结)
魔法道具 | 技术本质 | 安全作用 |
---|---|---|
魔法羊皮纸 | file_paths.xml | 隐藏真实路径,建立虚拟代号 |
运单生成器 | getUriForFile() | 路径加密:真实路径 → 虚拟路径 |
运单解析镜 | openFile() | 路径还原:虚拟路径 → 真实路径 |
临时钥匙 | FLAG_GRANT_READ_URI_PERMISSION | 动态权限控制,超时自动失效 |
魔法口诀 :
"文件路径是机密,虚拟代号来加密。
运单生成查地图,还原路径靠解析。
临时钥匙防偷窃,安全共享零压力!"
------ Android王国·FileProvider魔法手册
现在,图图和文文已经学会了用魔法安全传输文件。打开Android Studio,用这段代码试试魔法吧!