Tencent / Shadow

零反射全动态Android插件框架
BSD 3-Clause "New" or "Revised" License
7.46k stars 1.3k forks source link

关于 FileProvider 拍照中 URI 的疑问 #454

Closed 790396054 closed 6 months ago

790396054 commented 3 years ago

我们的插件使用了 takePhoto 这个拍照库,https://github.com/crazycodeboy/TakePhoto。 过程是这样的: 1.宿主注册了一个 provider

<provider
            android:authorities="com.tencent.shadow.sample.host.dynamic.fileprovider"
            android:name="com.tencent.shadow.core.runtime.container.PluginContainerContentProvider"
            android:grantUriPermissions="true"
            android:process=":plugin"
            />
  1. SampleComponentManager onBindContainerContentProvider 方法配置了注册信息
    /**
     * 配置对应宿主中预注册的壳子contentProvider的信息
     */
    @Override
    public ContainerProviderInfo onBindContainerContentProvider(ComponentName pluginContentProvider) {
        return new ContainerProviderInfo(
                "com.tencent.shadow.core.runtime.container.PluginContainerContentProvider",
                "com.tencent.shadow.sample.host.dynamic.fileprovider");
    }

    3.插件注册了provider

    <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.tencent.shadow.sample.host.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

4.打开系统相机的代码:

  String PATH_PHOTO_TEMP = getCacheDir().getPath() + File.separator;
  String fileName = String.valueOf(System.currentTimeMillis());
   filePath = PATH_PHOTO_TEMP + fileName + ".jpg";
   mFile = new File(filePath);
  Uri contentUri = FileProvider.getUriForFile(TestFileProviderActivity.this, "com.tencent.shadow.sample.host.fileprovider", mFile);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
startActivityForResult(intent, REQUEST_CODE);

打开拍照,能正常拍照。

5.得到照片时,发现 第 4 步的 contentUri 值是

content://com.tencent.shadow.sample.host.dynamic.fileprovider/com.tencent.shadow.sample.host.fileprovider/camera_photos/data/data/com.tencent.shadow.sample.host/cache/ShadowPlugin_sample-plugin-app/1607936694405.jpg

就是 authority 值是 com.tencent.shadow.sample.host.dynamic.fileprovider 这个是宿主中的 authority,现在的问题是我想在插件中把这个值变为

content://com.tencent.shadow.sample.host.fileprovider/camera_photos/data/data/com.tencent.shadow.sample.host/cache/ShadowPlugin_sample-plugin-app/1607936694405.jpg

就是把 authority 变为 插件中注册是 com.tencent.shadow.sample.host.fileprovider 这个值。 我的写法如下:

 String plugin = uri.toString().replace(uri.getAuthority() + "/", ""); // 1
 Uri pluginUri = Uri.parse(plugin); // 2

结果是第一行代码是

content://com.tencent.shadow.sample.host.fileprovider/camera_photos/data/data/com.tencent.shadow.sample.host/cache/ShadowPlugin_sample-plugin-app/1607936694405.jpg

符合预期。

而第二行代码结果是

content://com.tencent.shadow.sample.host.dynamic.fileprovider/com.tencent.shadow.sample.host.fileprovider/camera_photos/data/data/com.tencent.shadow.sample.host/cache/ShadowPlugin_sample-plugin-app/1607936694405.jpg

前面的 authority 又变为宿主的值了。 不知道为什么 Uri.parse 这个方法执行完,值就变化了。

我在插件中看 ShadowContentProviderDelegate 类的 openFile 方法,99 行

@TargetApi(Build.VERSION_CODES.KITKAT)
    override fun openFile(uri: Uri, mode: String, signal: CancellationSignal?): ParcelFileDescriptor? {
        val pluginUri = mProviderManager.convert2PluginUri(uri)
        return mProviderManager.getPluginContentProvider(pluginUri.authority!!)!!.openFile(pluginUri, mode, signal)
    }

调用了 val pluginUri = mProviderManager.convert2PluginUri(uri) 行。而这个值就能把宿主的 Authority 变为插件的 Authority。 而我看 convert2PluginUri 这个方法的实现也是 Uri.Parse 方法。

最后,我的期望是 我也想把宿主的 Authority 变为插件的 Authority。这样我的业务修改最小。 感谢阅读

shifujun commented 3 years ago

虽然你写了好多字,但是很难看懂啊。因为我们平常不在同一个工作的上下文里。为什么宁可写这么多字描述,也不把代码放上来呢?你就说clone这个分支,跑起来点哪,断点第几行,这个变量的值预期是什么,实际是什么。

更好的是直接扩展一个自动化测试用例,然后就什么也不用说了。代码里现有一个TestFileProviderActivity,可以基于它写一个。

jychenX commented 2 years ago

大神好,其实楼主说的就是7.0以上FileProvider的适配和替换问题:

1、没有看到需要在宿主里面注册FileProvider的操作,以及不知道res>xm>filepath.xml文件指定的存储目录结构是怎样的; 如果插件里面用到了uri怎么办,比如调用安装页面,存储照片; 目前测试下来是都找不到对应的authority和存储路径;

2、第二个问题就是楼主描述不清楚的问题,如下源码所示,为啥框架要把沙盒的存储目录都改成:”原路径+插件名字“ 的路径,这样会导致filepath.xml里面写的路径和实际存储的对不上;

此处采自框架源码: com.tencent.shadow.core.runtime.SubDirContextThemeWrapper.java

@Override
public File getDataDir() {
    if (getSubDirName() == null) {
        return super.getDataDir();
    }
    synchronized (mSync) {
        if (mDataDir == null) {
            mDataDir = new File(super.getDataDir(), getSubDirName());
        }
        return ensurePrivateDirExists(mDataDir);
    }
}

@Override
public File getFilesDir() {
    if (getSubDirName() == null) {
        return super.getFilesDir();
    }
    synchronized (mSync) {
        if (mFilesDir == null) {
            mFilesDir = new File(super.getFilesDir(), getSubDirName());
        }
        return ensurePrivateDirExists(mFilesDir);
    }
}

备注:getSubDirName()为获取到的插件名字

shifujun commented 2 years ago

为啥框架要把沙盒的存储目录都改成:”原路径+插件名字“ 的路径

这是因为要避免宿主以及多个插件之间文件路径冲突。这个路径应该是由businessName字段决定的。如果为空,应该就不会修改路径了,相当于插件复用宿主的根目录。

https://github.com/Tencent/Shadow/blob/15c0cd90fc7657856f51e49ad256fef2509670a9/projects/sdk/core/runtime/src/main/java/com/tencent/shadow/core/runtime/ShadowContext.java#L239-L246

shifujun commented 2 years ago

另外如果确实有问题,建议修改sample中的TestFileProviderActivity复现问题,然后把代码push上来看看。

jychenX commented 2 years ago

TestFileProviderActivity的使用方式是没有问题的,因为它注册的是:android.support.v4.content.FileProvider 注册的路径是:包名 + general_cases.fileprovider,在调用的时候找的也是:包名 + general_cases.fileprovider <provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.general_cases.fileprovider"

问题: 如果我插件里面有一个:a.b.c.FileProvider继承了support包的FileProvider,使用了其它的路径(android:authorities) 例子:比如插件的清单包含这样的一个provider: <provider android:name="a.b.c.FileProvider" android:authorities="${applicationId}.a.b.c"

这个时候宿主里面并没有注册这个,实际运行会找不到这个contentUri;

猜测的结论: 我是不是可以理解为:如果插件要使用自定义的FileProvider,只能使用support或者androidX包里面的,而且只能使用一个路径(事先和宿主约定好:android:authorities),动态编译并没有帮我替换这些东西

shifujun commented 2 years ago

问题: 如果我插件里面有一个:a.b.c.FileProvider继承了support包的FileProvider,使用了其它的路径(android:authorities) 例子:比如插件的清单包含这样的一个provider: <provider android:name="a.b.c.FileProvider" android:authorities="${applicationId}.a.b.c"

看不懂你的a.b.c.FileProviderandroid.support.v4.content.FileProvider有什么区别。你不用说“如果”怎么样,你就直接把复现代码push上来,能复现就行了。