【问题标题】:Hello-World of FileProviderFileProvider 的 Hello-World
【发布时间】:2013-09-25 15:19:27
【问题描述】:

这个问题包含几个子问题。我正在分叉这些,从this question 开始。我最终会通过删除此问题来进行清理。

以下程序理论上将共享一个 hello-world 文本文件。代码运行,但共享到 Dropbox 或 Gmail(仅通过两个具体示例)失败。

public class MainActivity extends Activity {
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String filename = "hellow.txt";
        String fileContents = "Hello, World!\n";
        byte[] bytes = fileContents.getBytes();
        FileOutputStream fos = null;
        try {
            fos = this.openFileOutput(filename, MODE_PRIVATE);
            fos.write(bytes);
        } catch (IOException e) {                       
            e.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e) {                       
                e.printStackTrace();
            }
        } 

        File file = new File(filename);
        Intent shareIntent = new Intent();
        shareIntent.setAction(Intent.ACTION_SEND);
        shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
        shareIntent.setType("application/txt");
        startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));

        file.delete();
    }
}

除了在 r​​es/values/strings.xml 中为 send_to 添加一个值之外,我对 Eclipse 创建的通用 Hello, World 所做的唯一另一对更改是在 AndroidManifest.xml 中添加以下 <provider> 标记:

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.mycorp.helloworldtxtfileprovider.MainActivity"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/my_paths" />
    </provider>

    <activity
        android:name="com.mycorp.helloworldtxtfileprovider.MainActivity"
        ...

...并在 res/xml/my_paths.xml 中添加以下内容

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files" path="." />
</paths>

我的主要问题是第一个,但是当你在这个话题上时,讨论问题 2-4 也会很有趣。

  1. 为什么上面的程序会失败?
  2. 如果需要自定义ContentProvider,是否需要扩展该类,但如果只需要FileProvider,则无需派生即可使用该类?
  3. 在这段代码中,我需要使用两次filename——一次使用openFileOutput,另一次使用new File()。有没有办法避免这种重复(保证引用的是同一个文件)?
  4. 在调用startActivity(..) 后立即删除文件是否安全,或者是否有必要设计回调以等待得知文件已上传/共享。 (实际文件可能需要一些时间才能共享/上传。)

编辑

代码运行良好,并显示了要发送到的应用列表。

如果我选择 Dropbox,我可以很好地选择位置。 Dropbox 发送通知“上传到 Dropbox”,然后是“上传失败:my_file.txt”。

如果我选择 Gmail,我可以填写收件人并且文件似乎已附加,但在“发送消息..”之后我收到“无法发送附件”。

【问题讨论】:

  • 我们希望您不要检查并删除其他人花时间回答的问题。问题不仅是为了您的利益,也是为了将来可能正在寻找类似问题的许多人。
  • @BradLarson 我已经违反了一个问题/一个帖子的规则并且正在付出代价。我这样做让一些善良的灵魂敞开心扉,比如那些回答试图通过对不太重要的问题给出明智的答案来提供帮助的人,但没有解决问题的症结(第 1 部分)。正如我在 cmets 中提到的,我会将问题分解为子部分。我尝试了一个侧重于第 1 部分的问题。没有人敢于回答。我宁愿删除它。最终,自从提出这个问题后,我意识到问题要复杂得多,例如当用户处于飞行模式时。待定。

标签: android android-fileprovider


【解决方案1】:

1.

使用FileProvider.getUriForFile(...) 构造URI。这会将启动的活动定向到您的 FileProvider(然后它可以从您的应用程序的私有文件目录中提供文件)。 Uri.fromFile(...) 不起作用,因为启动的活动将尝试直接访问私有目录。

设置FLAG_GRANT_READ_URI_PERMISSION,以便启动的活动被授予对FileProvider构造的URI的读取权限。

最后,作为 MIME 类型,“text/plain”可能比“application/txt”更好。

我遇到了一些问题,无法使其在不同设备上始终如一地工作。这是迄今为止我最好的选择(如果我改进它会编辑):

    final String auth = "org.volley.sndcard_android.fileprovider";
    final Uri uri = FileProvider.getUriForFile(activity, auth, file);
    final String type = activity.getContentResolver().getType(uriForFile);

    final Intent shareIntent = new Intent(Intent.ACTION_SEND);
    shareIntent.setDataAndType(uri, type);
    shareIntent.putExtra(Intent.EXTRA_STREAM, uriForFile);
    shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    final Intent chooser = Intent.createChooser(shareIntent, "<title>");
    activity.startActivity(chooser);

仅设置类型适用于我的 Nexus 5,但不适用于我的三星平板电脑。三星平板电脑似乎需要 URI 作为数据才能授予权限。另请注意,intent.getData() 会取消之前对intent.setType() 的任何调用,反之亦然,因此您必须使用组合方法,如上所述。

Gmail 似乎将附加数据解释为默认收件人地址。非常烦人!如果有人有更好的解决方案,请分享(双关语,我来自哥德堡)。

【讨论】:

  • 我能够使用 FileProvider 从我的应用程序的安全存储中附加文件的唯一方法是手动授予和撤销我的文件的权限,如这个答案stackoverflow.com/a/18332000/1082344 (在三星 Galaxy Tab 10.1 和 android 版本上工作) 3.1、Android 2.3.4 的 Galaxy Ace、Android 4.1.2 的 Galaxy S3)
【解决方案2】:

2.是的,确实如此。您会看到ContentProvider 是一个抽象类,因此要使用自定义内容提供程序,必须对其进行扩展。由于FileProviderContentProvider 的子类(不是抽象的),程序员可以使用FileProvider 而无需子类化。

3.为了确保相同的文件,您可以按照下面的示例代码 -

String fileName = "my_file.txt";
String fileData = "sample file content";

// Get the file
File file = new File(this.getFilesDir(), fileName);
if (!file.exists()) {
    file.createNewFile();
}

// Write data to file
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(fileData);
fileWriter.close();

// Get the uri
Uri uri = Uri.fromFile(file);

// Share the file with corresponding uri
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("application/txt");
startActivity(Intent.createChooser(shareIntent, "Send To"));

4.不,在您致电startActivity() 后立即删除文件是不安全的。因为startActivity() 是非阻塞函数调用,它会立即返回。您必须等待文件被共享的时间。您可以使用 startActivityForResult() 来做到这一点。看看它是否达到目的。

【讨论】:

  • 我是如何设法以这种形式询问第 2 部分的,这真是太棒了。无论如何,用你的代码替换我的代码并没有帮助。你能在你身边运行它并确认它有效吗?你错过了一个 try-catch 块,所以我猜你没有运行它。
  • 这段代码是为了让你知道你可以使用同一个文件对象。你很接近,我成功编译了代码。我运行了代码,它显示了我可以选择与之共享文件的应用程序列表。我在发帖时删除了 try-catch 块。
  • 我发布的代码也运行良好,并提供了要发送到的应用程序列表。我使用从 Dropbox 和 Gmail 收到的通知更新了问题。你能确认你可以上传到你身边的一个或两个吗?
  • 我无法通过上面的代码上传成功。但是我使用了Luke Sleeman's answer,现在将java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider 用于gmail,而对于Dropbox,这也是另一个权限错误。
  • 在发布此问题之前,我对 Luke Sleeman 的答案进行了多种变体试验。感谢您对 2、3 和 4 的贡献,但我真正关注的是问题 1。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-04
  • 2014-10-15
  • 2023-04-11
  • 2011-09-12
  • 2018-09-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多