系统应用使用FileProvider的坑( 三 )


直接传递
从原理上看实际就是打开文件的传给其他应用使用,那我们能不能直接打开文件然后将直接通过传给其他应用呢?
val intent = Intent()intent.putExtra("fd" , ParcelFileDescriptor.open(file, MODE_READ_ONLY))intent.setClassName("me.linjw.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: 1696802-15 20:27:24.200 16968 16968 E AndroidRuntime: java.lang.RuntimeException: Not allowed to write file descriptors here02-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)
原因在于的启动前会调用.s最终调用到.(false)不允许传递:
// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r29:frameworks/base/core/java/android/app/Instrumentation.javapublic 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.javapublic 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);}}
一开始我想通过反射去强行调用(true),但是发现这个方法被限制了,需要系统权限才能调用:
Accessing hidden method Landroid/os/Bundle;->setAllowFds(Z)Z (max-target-o, reflection, denied)
只能另谋出路,由于ParcelFileDescriptor实现了Parcelable,所以我们可以通过传递Binder的方式迂回的去传递:
// aidlinterface 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.linjw.demo.fileprovider.recv", "me.linjw.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() }}