网站如何伪静态,知名网站建设加盟合作,文章页模板wordpress,cms 网站模板高版本的android对文件权限的管控抓的很严格,理论上两个应用之间的文件传递现在都应该是用FileProvider去实现,这篇博客来一起了解下它的实现原理。
首先我们要明确一点,FileProvider就是一个ContentProvider,所以需要在AndroidManifest.xml里面对它进行声明:
provideran…高版本的android对文件权限的管控抓的很严格,理论上两个应用之间的文件传递现在都应该是用FileProvider去实现,这篇博客来一起了解下它的实现原理。
首先我们要明确一点,FileProvider就是一个ContentProvider,所以需要在AndroidManifest.xml里面对它进行声明:
providerandroid:nameandroidx.core.content.FileProviderandroid:authoritiesme.demo.fileprovider.providerandroid:exportedfalseandroid:grantUriPermissionstruemeta-dataandroid:nameandroid.support.FILE_PROVIDER_PATHSandroid:resourcexml/file_path /
/provider
和普通的ContentProvider不一样的是他多了一个android.support.FILE_PROVIDER_PATHS的meta-data指定了一个xml资源:
?xml version1.0 encodingutf-8?
paths xmlns:androidhttp://schemas.android.com/apk/res/androidroot-path nameroot path /files-path namefiles pathimages/ /cache-path namecache path /external-path nameexternal path /external-files-path nameexternal-files path /external-cache-path nameexternal-cache path /external-media-path nameexternal-media path /
/paths
文件URI
这个xml的作用在于为文件生成URI,root-path、files-path、cache-path这些标签代表父路径:
root-path : File(/)
files-path : Context.getFilesDir()
cache-path : context.getCacheDir()
external-path : Environment.getExternalStorageDirectory()
external-files-path : ContextCompat.getExternalFilesDirs(context, null)[0]
external-cache-path : ContextCompat.getExternalCacheDirs(context)[0]
external-media-path : context.getExternalMediaDirs()[0]
path属性代表子路径,name代表为父路径/子路径起的名字,
files-path namefiles pathimages/ /
例如上面配置代表的就是我们为 new File(context.getFilesDir(), images/) 这个路径起了个名字叫做files
val filesDir File(context.getFilesDir(), images/)
val uri FileProvider.getUriForFile(this, me.linjw.demo.fileprovider.provider, File(filesDir, test.jpg))
// uri就是把filesDir的路径转换files,然后加上content://me.linjw.demo.fileprovider.provider
// 即 content://me.linjw.demo.fileprovider.provider/files/test.jpg
从FileProvider的源码里面就能看到这部分的转换逻辑:private static final String TAG_ROOT_PATH root-path;
private static final String TAG_FILES_PATH files-path;
private static final String TAG_CACHE_PATH cache-path;
private static final String TAG_EXTERNAL external-path;
private static final String TAG_EXTERNAL_FILES external-files-path;
private static final String TAG_EXTERNAL_CACHE external-cache-path;
private static final String TAG_EXTERNAL_MEDIA external-media-path;...int type;
while ((type in.next()) ! END_DOCUMENT) {if (type START_TAG) {final String tag in.getName();final String name in.getAttributeValue(null, ATTR_NAME);String path in.getAttributeValue(null, ATTR_PATH);File target null;if (TAG_ROOT_PATH.equals(tag)) {target DEVICE_ROOT;} else if (TAG_FILES_PATH.equals(tag)) {target context.getFilesDir();} else if (TAG_CACHE_PATH.equals(tag)) {target context.getCacheDir();} else if (TAG_EXTERNAL.equals(tag)) {target Environment.getExternalStorageDirectory();} else if (TAG_EXTERNAL_FILES.equals(tag)) {File[] externalFilesDirs ContextCompat.getExternalFilesDirs(context, null);if (externalFilesDirs.length 0) {target externalFilesDirs[0];}} else if (TAG_EXTERNAL_CACHE.equals(tag)) {File[] externalCacheDirs ContextCompat.getExternalCacheDirs(context);if (externalCacheDirs.length 0) {target externalCacheDirs[0];}} else if (Build.VERSION.SDK_INT Build.VERSION_CODES.LOLLIPOP TAG_EXTERNAL_MEDIA.equals(tag)) {File[] externalMediaDirs context.getExternalMediaDirs();if (externalMediaDirs.length 0) {target externalMediaDirs[0];}}if (target ! null) {strat.addRoot(name, buildPath(target, path));}}
}...private static File buildPath(File base, String... segments) {File cur base;for (String segment : segments) {if (segment ! null) {cur new File(cur, segment);}}return cur;
}
查询的时候就只需要从strat里面找到文件路径最匹配的name即可。
打开文件
有了这个uri之后我们就能通过Intent将它传给其他应用,并配置Intent.FLAG_GRANT_READ_URI_PERMISSION或者Intent.FLAG_GRANT_WRITE_URI_PERMISSION为其他应用设置读写权限:
val uri FileProvider.getUriForFile(this, me.linjw.demo.fileprovider.provider, file)
val intent Intent()
intent.data uri
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setClassName(me.linjw.demo.fileprovider.recv, me.linjw.demo.fileprovider.recv.MainActivity)
startActivity(intent)
其他应用拿到这个uri就可以通过ContentResolver.openInputStream打开文件流:
val inputStream intent.data?.let { contentResolver.openInputStream(it) }
或者有时候我们希望通过String传递uri的时候可以提前使用Context.grantUriPermission为指定的包名申请权限,然后接收端Uri.parse去解析出Uri来操作文件:
// 发送端
val uri FileProvider.getUriForFile(this, me.linjw.demo.fileprovider.provider, file)
grantUriPermission(me.linjw.demo.fileprovider.recv, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)val intent Intent()
intent.putExtra(uri, uri.toString())
intent.setClassName(me.linjw.demo.fileprovider.recv, me.linjw.demo.fileprovider.recv.MainActivity)
startActivity(intent)// 接收端
val uri Uri.parse(intent.getStringExtra(uri))
val inputStream contentResolver.openInputStream(uri)
Uri操作文件的原理实际上就是通过请求我们之前声明的me.linjw.demo.fileprovider.provider这个ContentProvider,让它给我们去打开文件:
// FileProvider.java
public ParcelFileDescriptor openFile(NonNull Uri uri, NonNull String mode)throws FileNotFoundException {// ContentProvider has already checked granted permissionsfinal File file mStrategy.getFileForUri(uri);final int fileMode modeToMode(mode);return ParcelFileDescriptor.open(file, fileMode);
}
也就是说文件权限的校验实际上只发生在打开的阶段.其他应用虽然没有权限打开我们的文件,但是我们可以在ContentProvider里面帮它打开然后返回文件描述符,给其他应用去读写。 系统应用使用FileProvider的坑
项目中有个系统应用需要向其他应用传的文件,于是把FileProvider加上,然后发现其他应用还是没有权限。从日志里面看是说这个FileProvider并没有从UID 1000里暴露出来:
02-13 06:52:28.921 4292 4292 E AndroidRuntime: Caused by: java.lang.SecurityException:
Permission Denial: opening provider androidx.core.content.FileProvider from ProcessRecord{806d30d 4292:me.linjw.demo.fileprovider.recv/u0a53} (pid4292, uid10053) that is not exported from UID 1000
由于这个UID 1000太显眼所以尝试将系统签名去掉发现权限就正常了,实锤是系统签名的原因。
查看出现异常的时候的日志,发现了下面的打印:
02-13 06:52:28.486 863 1393 W UriGrantsManagerService: For security reasons, the system cannot issue a Uri permission grant to content://me.linjw.demo.fileprovider.provider/root/data/user/0/me.linjw.demo.fileprovider/files/test.txt [user 0]; use startActivityAsCaller() instead
在代码里面搜索关键字,发现系统应用需要在源码里面配置FileProvider的authorities:
// https://cs.android.com/android/platform/superproject//android-13.0.0_r29:frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java// Bail early if system is trying to hand out permissions directly; it
// must always grant permissions on behalf of someone explicit.
final int callingAppId UserHandle.getAppId(callingUid);
if ((callingAppId SYSTEM_UID) || (callingAppId ROOT_UID)) {if (com.android.settings.files.equals(grantUri.uri.getAuthority())|| com.android.settings.module_licenses.equals(grantUri.uri.getAuthority())) {// Exempted authority for// 1. cropping user photos and sharing a generated license html// file in Settings app// 2. sharing a generated license html file in TvSettings app// 3. Sharing module license files from Settings app} else {Slog.w(TAG, For security reasons, the system cannot issue a Uri permission grant to grantUri ; use startActivityAsCaller() instead);return -1;}
}
直接传递ParcelFileDescriptor
从原理上看FileProvider实际就是打开文件的ParcelFileDescriptor传给其他应用使用,那我们能不能直接打开文件然后将ParcelFileDescriptor直接通过Intent传给其他应用呢?
val intent Intent()
intent.putExtra(fd , ParcelFileDescriptor.open(file, MODE_READ_ONLY))
intent.setClassName(me.demo.fileprovider.recv, me.linjw.demo.fileprovider.recv.MainActivity)
startActivity(intent)
答案是不行:
02-15 20:27:24.200 16968 16968 E AndroidRuntime: Process: me.linjw.demo.fileprovider, PID: 16968
02-15 20:27:24.200 16968 16968 E AndroidRuntime: java.lang.RuntimeException: Not allowed to write file descriptors here
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.os.Parcel.nativeWriteFileDescriptor(Native Method)
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.os.Parcel.writeFileDescriptor(Parcel.java:922)
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.os.ParcelFileDescriptor.writeToParcel(ParcelFileDescriptor.java:1110)
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.os.Parcel.writeParcelable(Parcel.java:1953)
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.os.Parcel.writeValue(Parcel.java:1859)
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.os.Parcel.writeArrayMapInternal(Parcel.java:1024)
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1620)
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.os.Bundle.writeToParcel(Bundle.java:1304)
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.os.Parcel.writeBundle(Parcel.java:1093)
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.content.Intent.writeToParcel(Intent.java:11123)
02-15 20:27:24.200 16968 16968 E AndroidRuntime: at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:
2298)
原因在于Instrumentation的execStartActivity启动Activity前会调用Intent.prepareToLeaveProcess最终调用到Bundle.setAllowFds(false)不允许传递ParcelFileDescriptor:
// https://cs.android.com/android/platform/superproject//android-13.0.0_r29:frameworks/base/core/java/android/app/Instrumentation.java
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {...intent.prepareToLeaveProcess(who);...
}// https://cs.android.com/android/platform/superproject//android-13.0.0_r29:frameworks/base/core/java/android/content/Intent.java
public void prepareToLeaveProcess(Context context) {final boolean leavingPackage;if (mComponent ! null) {leavingPackage !Objects.equals(mComponent.getPackageName(), context.getPackageName());} else if (mPackage ! null) {leavingPackage !Objects.equals(mPackage, context.getPackageName());} else {leavingPackage true;}prepareToLeaveProcess(leavingPackage);
}/*** Prepare this {link Intent} to leave an app process.** hide*/
public void prepareToLeaveProcess(boolean leavingPackage) {setAllowFds(false);...
}public void setAllowFds(boolean allowFds) {if (mExtras ! null) {mExtras.setAllowFds(allowFds);}
}
一开始我想通过反射去强行调用setAllowFds(true),但是发现这个方法被限制了,需要系统权限才能调用:
Accessing hidden method Landroid/os/Bundle;-setAllowFds(Z)Z (max-target-o, reflection, denied)
只能另谋出路,由于ParcelFileDescriptor实现了Parcelable,所以我们可以通过传递Binder的方式迂回的去传递:
// aidl
interface IFileDescriptorsProvider {ParcelFileDescriptor get();
}// 发送端
val fileProvider object : IFileDescriptorsProvider.Stub() {override fun get(): ParcelFileDescriptor {return ParcelFileDescriptor.open(file, MODE_READ_ONLY)}
}
val intent Intent()
val bundle Bundle().apply { putBinder(fileProvider, fileProvider) }
intent.putExtras(bundle)
intent.setClassName(me.demo.fileprovider.recv, me.demo.fileprovider.recv.MainActivity)
startActivity(intent)// 接收端
val text intent.extras?.getBinder(fileProvider)?.let { it -val fd IFileDescriptorsProvider.Stub.asInterface(it).get()AssetFileDescriptor(fd, 0, -1).createInputStream().use { it.bufferedReader().readLine() }
}
也可以封装成服务端service提供接口客户端通过aidl连接服务端后调用服务端接口返回ParcelFileDescriptor对象去获取文件。 文章转载自: http://www.morning.zxfr.cn.gov.cn.zxfr.cn http://www.morning.4r5w91.cn.gov.cn.4r5w91.cn http://www.morning.mcndn.cn.gov.cn.mcndn.cn http://www.morning.nzwp.cn.gov.cn.nzwp.cn http://www.morning.rdzlh.cn.gov.cn.rdzlh.cn http://www.morning.swsrb.cn.gov.cn.swsrb.cn http://www.morning.brrxz.cn.gov.cn.brrxz.cn http://www.morning.hbtarq.com.gov.cn.hbtarq.com http://www.morning.fhddr.cn.gov.cn.fhddr.cn http://www.morning.mhmdx.cn.gov.cn.mhmdx.cn http://www.morning.muniubangcaishui.cn.gov.cn.muniubangcaishui.cn http://www.morning.rynq.cn.gov.cn.rynq.cn http://www.morning.kbfzp.cn.gov.cn.kbfzp.cn http://www.morning.mkpkz.cn.gov.cn.mkpkz.cn http://www.morning.hcqpc.cn.gov.cn.hcqpc.cn http://www.morning.bbgn.cn.gov.cn.bbgn.cn http://www.morning.nmfwm.cn.gov.cn.nmfwm.cn http://www.morning.pzqnj.cn.gov.cn.pzqnj.cn http://www.morning.dqcpm.cn.gov.cn.dqcpm.cn http://www.morning.wnkbf.cn.gov.cn.wnkbf.cn http://www.morning.ydhmt.cn.gov.cn.ydhmt.cn http://www.morning.tfzjl.cn.gov.cn.tfzjl.cn http://www.morning.plnry.cn.gov.cn.plnry.cn http://www.morning.jwdys.cn.gov.cn.jwdys.cn http://www.morning.tyjnr.cn.gov.cn.tyjnr.cn http://www.morning.gblrn.cn.gov.cn.gblrn.cn http://www.morning.nqmdc.cn.gov.cn.nqmdc.cn http://www.morning.fjscr.cn.gov.cn.fjscr.cn http://www.morning.swbhq.cn.gov.cn.swbhq.cn http://www.morning.qpqcq.cn.gov.cn.qpqcq.cn http://www.morning.vvbsxm.cn.gov.cn.vvbsxm.cn http://www.morning.bxyzr.cn.gov.cn.bxyzr.cn http://www.morning.wpcfm.cn.gov.cn.wpcfm.cn http://www.morning.lztrt.cn.gov.cn.lztrt.cn http://www.morning.jfgmx.cn.gov.cn.jfgmx.cn http://www.morning.xnkb.cn.gov.cn.xnkb.cn http://www.morning.mmplj.cn.gov.cn.mmplj.cn http://www.morning.mxdhy.cn.gov.cn.mxdhy.cn http://www.morning.dgfpp.cn.gov.cn.dgfpp.cn http://www.morning.kehejia.com.gov.cn.kehejia.com http://www.morning.xnkh.cn.gov.cn.xnkh.cn http://www.morning.wjjxr.cn.gov.cn.wjjxr.cn http://www.morning.ygbq.cn.gov.cn.ygbq.cn http://www.morning.ljtwp.cn.gov.cn.ljtwp.cn http://www.morning.tgyzk.cn.gov.cn.tgyzk.cn http://www.morning.rzcbk.cn.gov.cn.rzcbk.cn http://www.morning.skksz.cn.gov.cn.skksz.cn http://www.morning.rjrlx.cn.gov.cn.rjrlx.cn http://www.morning.spxk.cn.gov.cn.spxk.cn http://www.morning.dkslm.cn.gov.cn.dkslm.cn http://www.morning.txzqf.cn.gov.cn.txzqf.cn http://www.morning.qbgdy.cn.gov.cn.qbgdy.cn http://www.morning.lcbnb.cn.gov.cn.lcbnb.cn http://www.morning.kxsnp.cn.gov.cn.kxsnp.cn http://www.morning.fcrw.cn.gov.cn.fcrw.cn http://www.morning.xyrss.cn.gov.cn.xyrss.cn http://www.morning.jfbrt.cn.gov.cn.jfbrt.cn http://www.morning.djmdk.cn.gov.cn.djmdk.cn http://www.morning.rcjqgy.com.gov.cn.rcjqgy.com http://www.morning.bbtn.cn.gov.cn.bbtn.cn http://www.morning.rbqlw.cn.gov.cn.rbqlw.cn http://www.morning.ssxlt.cn.gov.cn.ssxlt.cn http://www.morning.xjqkh.cn.gov.cn.xjqkh.cn http://www.morning.ksqzd.cn.gov.cn.ksqzd.cn http://www.morning.lsjtq.cn.gov.cn.lsjtq.cn http://www.morning.nmwgd.cn.gov.cn.nmwgd.cn http://www.morning.zmzdx.cn.gov.cn.zmzdx.cn http://www.morning.kgsws.cn.gov.cn.kgsws.cn http://www.morning.xhgcr.cn.gov.cn.xhgcr.cn http://www.morning.znsyn.cn.gov.cn.znsyn.cn http://www.morning.xbnkm.cn.gov.cn.xbnkm.cn http://www.morning.jfbpf.cn.gov.cn.jfbpf.cn http://www.morning.bpmtr.cn.gov.cn.bpmtr.cn http://www.morning.jgncd.cn.gov.cn.jgncd.cn http://www.morning.rbjf.cn.gov.cn.rbjf.cn http://www.morning.jcrlx.cn.gov.cn.jcrlx.cn http://www.morning.flxgx.cn.gov.cn.flxgx.cn http://www.morning.zqzhd.cn.gov.cn.zqzhd.cn http://www.morning.datadragon-auh.cn.gov.cn.datadragon-auh.cn http://www.morning.rbyz.cn.gov.cn.rbyz.cn