42

我的目标是在内部存储上创建一个 XML 文件,然后通过共享 Intent 发送它。

我可以使用此代码创建 XML 文件

FileOutputStream outputStream = context.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
PrintStream printStream = new PrintStream(outputStream);
String xml = this.writeXml(); // get XML here
printStream.println(xml);
printStream.close();

我一直在尝试将 Uri 检索到输出文件以共享它。我首先尝试通过将文件转换为 Uri 来访问该文件

File outFile = context.getFileStreamPath(fileName);
return Uri.fromFile(outFile);

这将返回file:///data/data/com.my.package/files/myfile.xml但我似乎无法将其附加到电子邮件、上传等。

如果我手动检查文件长度,它是正确的,并显示有一个合理的文件大小。

接下来,我创建了一个内容提供程序并尝试引用该文件,但它不是该文件的有效句柄。ContentProvider似乎从来没有被称为任何点。

Uri uri = Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + fileName);
return uri;

这将返回content://com.my.package.provider/myfile.xml但我检查了文件,它的长度为零。

如何正确访问文件?我需要使用内容提供商创建文件吗?如果是这样,怎么做?

更新

这是我用来分享的代码。如果我选择 Gmail,它会显示为附件,但是当我发送它时会出现错误无法显示附件并且到达的电子邮件没有附件。

public void onClick(View view) {
    Log.d(TAG, "onClick " + view.getId());

    switch (view.getId()) {
        case R.id.share_cancel:
            setResult(RESULT_CANCELED, getIntent());
            finish();
            break;

        case R.id.share_share:

            MyXml xml = new MyXml();
            Uri uri;
            try {
                uri = xml.writeXmlToFile(getApplicationContext(), "myfile.xml");
                //uri is  "file:///data/data/com.my.package/files/myfile.xml"
                Log.d(TAG, "Share URI: " + uri.toString() + " path: " + uri.getPath());

                File f = new File(uri.getPath());
                Log.d(TAG, "File length: " + f.length());
                // shows a valid file size

                Intent shareIntent = new Intent();
                shareIntent.setAction(Intent.ACTION_SEND);
                shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
                shareIntent.setType("text/plain");
                startActivity(Intent.createChooser(shareIntent, "Share"));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

            break;
    }
}

我注意到createChooser(...)Exception里面有一个抛出,但我不知道为什么抛出它。

E/ActivityThread(572): Activity com.android.internal.app.ChooserActivity 泄露了最初在这里注册的 IntentReceiver com.android.internal.app.ResolverActivity$1@4148d658。您是否错过了对 unregisterReceiver() 的调用?

我研究了这个错误,找不到任何明显的东西。这两个链接都表明我需要注销接收器。

我有一个接收器设置,但它AlarmManager是在其他地方设置的,不需要应用程序注册/取消注册。

openFile(...) 的代码

如果需要,这里是我创建的内容提供者。

public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    String fileLocation = getContext().getCacheDir() + "/" + uri.getLastPathSegment();

    ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
    return pfd;
}
4

5 回答 5

42

可以通过 ContentProvider 公开存储在您的应用程序私有目录中的文件。这是我制作的一些示例代码,展示了如何创建可以执行此操作的内容提供程序。

显现

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.providertest"
  android:versionCode="1"
  android:versionName="1.0">

  <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="15" />

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

    <activity
        android:name=".MainActivity"
        android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <provider
        android:name="MyProvider"
        android:authorities="com.example.prov"
        android:exported="true"
        />        
  </application>
</manifest>

在您的 ContentProvider 覆盖 openFile 以返回 ParcelFileDescriptor

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {       
     File cacheDir = getContext().getCacheDir();
     File privateFile = new File(cacheDir, "file.xml");

     return ParcelFileDescriptor.open(privateFile, ParcelFileDescriptor.MODE_READ_ONLY);
}

确保已将 xml 文件复制到缓存目录

    private void copyFileToInternal() {
    try {
        InputStream is = getAssets().open("file.xml");

        File cacheDir = getCacheDir();
        File outFile = new File(cacheDir, "file.xml");

        OutputStream os = new FileOutputStream(outFile.getAbsolutePath());

        byte[] buff = new byte[1024];
        int len;
        while ((len = is.read(buff)) > 0) {
            os.write(buff, 0, len);
        }
        os.flush();
        os.close();
        is.close();

    } catch (IOException e) {
        e.printStackTrace(); // TODO: should close streams properly here
    }
}

现在任何其他应用程序都应该能够通过使用内容 uri (content://com.example.prov/myfile.xml) 为您的私有文件获取 InputStream

对于一个简单的测试,从一个单独的应用程序调用内容提供程序,类似于以下内容

    private class MyTask extends AsyncTask<String, Integer, String> {

    @Override
    protected String doInBackground(String... params) {

        Uri uri = Uri.parse("content://com.example.prov/myfile.xml");
        InputStream is = null;          
        StringBuilder result = new StringBuilder();
        try {
            is = getApplicationContext().getContentResolver().openInputStream(uri);
            BufferedReader r = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = r.readLine()) != null) {
                result.append(line);
            }               
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try { if (is != null) is.close(); } catch (IOException e) { }
        }

        return result.toString();
    }

    @Override
    protected void onPostExecute(String result) {
        Toast.makeText(CallerActivity.this, result, Toast.LENGTH_LONG).show();
        super.onPostExecute(result);
    }
}
于 2012-08-30T02:13:37.607 回答
15

所以我认为 Rob 的回答是正确的,但我做的有点不同。据我了解,在提供程序中设置:

android:exported="true"

您正在公开访问您的所有文件?!无论如何,仅授予对某些文件的访问权限的一种方法是通过以下方式定义文件路径权限:

<provider
    android:authorities="com.your.app.package"
    android:name="android.support.v4.content.FileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

然后在您的 XML 目录中定义file_paths.xml文件,如下所示:

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

现在,“allfiles”提供了与选项android:exported="true"相同的公共权限,但我猜你并不真正想要这样定义子目录是下一行。然后,您所要做的就是将要共享的文件存储在该目录中。

接下来你要做的是,正如 Rob 所说,为这个文件获取一个 URI。我是这样做的:

Uri contentUri = FileProvider.getUriForFile(context, "com.your.app.package", sharedFile);

然后,当我拥有这个 URI 时,我必须为其附加权限,以便其他应用程序使用它。我正在使用或将此文件 URI 发送到相机应用程序。无论如何,这是我获取其他应用程序包信息并授予对 URI 权限的方式:

PackageManager packageManager = getPackageManager();
List<ResolveInfo> list = packageManager.queryIntentActivities(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() < 1) {
    return;
}
String packageName = list.get(0).activityInfo.packageName;
grantUriPermission(packageName, sharedFileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

ClipData clipData = ClipData.newRawUri("CAMFILE", sharedFileUri);
cameraIntent.setClipData(clipData);
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cameraIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(cameraIntent, GET_FROM_CAMERA);

我留下了相机的代码,因为我不想举其他一些我没有研究过的例子。但是通过这种方式,您可以看到可以将权限附加到 URI 本身。

相机的事情是我可以通过 ClipData 设置它,然后另外设置权限。我想在您的情况下,您只需要 FLAG_GRANT_READ_URI_PERMISSION ,因为您将文件附加到电子邮件中。

这是关于 FileProvider 的帮助链接,因为我的所有帖子都基于我在那里找到的信息。不过,在查找相机应用程序的包信息时遇到了一些麻烦。

希望能帮助到你。

于 2015-11-06T14:44:16.443 回答
4

如果您需要允许其他应用程序查看您的应用程序的私有文件(用于共享或其他),您可能可以节省一些时间,只需使用 v4 兼容库的FileProvider

于 2015-04-14T23:02:38.427 回答
2

以上答案都没有帮助。我的问题是传递意图附加内容,但我将引导您完成共享文件的所有步骤。

第 1 步:创建内容提供程序这将使您想要与之共享的任何应用程序都可以访问该文件。将以下内容粘贴到<application></applicatio>标签内的 Manifest.xml 文件中

            <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="{YOUR_PACKAGE_NAME}.fileprovider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/provider_paths" />
            </provider>

第 2 步:定义内容提供者可访问的路径 通过在 res/xml 下创建一个名为 provider_paths.xml(或您选择的名称)的文件来执行此操作。将以下代码放入文件中

    <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <external-path
            name="external"
            path="." />
        <external-files-path
            name="external_files"
            path="." />
        <cache-path
            name="cache"
            path="." />
        <external-cache-path
            name="external_cache"
            path="." />
        <files-path
            name="files"
            path="." />
    </paths>

第 3 步:创建 Intent 以共享文件

Intent intentShareFile = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".fileprovider", fileToShare);
            
intentShareFile.setDataAndType(uri, URLConnection.guessContentTypeFromName(fileToShare.getName()));
//Allow sharing apps to read the file Uri
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//Pass the file Uri instead of the path
intentShareFile.putExtra(Intent.EXTRA_STREAM,
                    uri);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
于 2021-07-15T22:12:46.757 回答
1

这就是我正在使用的:

我结合了一些答案并使用了当前的AndroidX Doku: Sharing files Android Development

基本流程:您更改清单以使其他应用程序可以访问您的本地文件。允许从外部访问的文件路径位于 res/xml/filepaths.xml 中。共享时,您创建共享意图并设置一个标志,暂时允许其他应用访问您的本地文件。Android 文档声称这是共享文件的安全方式

Step1:将 FileProvider 添加到 Manifest

    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.YOUR.APP.PACKAGE.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
    </provider>

Step2:将filepaths.xml添加到res/xml(如果XML文件夹不存在,自己创建)

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path path="share/" name="share" />
</paths>

第三步:使用这样的功能来启动文件共享。此函数将文件移动到预定义的共享文件夹并为其创建一个 Url。ShareDir 是指向 files/share/ 目录的 File。copy_File 函数将给定文件复制到共享目录,以便从外部访问。该功能还可以使用给定的标题和正文将文件作为电子邮件发送。如果不需要,只需将其设置为空字符串

public void ShareFiles(Activity activity, List<File> files, String header, String body) {

    ArrayList<Uri> uriList = new ArrayList<>();
    if(files != null) {
        for (File f : files) {
            if (f.exists()) {


                File file_in_share = copy_File(f, ShareDir);
                if(file_in_share == null)
                    continue;
                // Use the FileProvider to get a content URI

                try {
                    Uri fileUri = FileProvider.getUriForFile(
                            activity,
                            "com.YOUR.APP.PACKAGE.fileprovider",
                            file_in_share);

                    uriList.add(fileUri);
                } catch (IllegalArgumentException e) {
                    Log.e("File Selector",
                            "The selected file can't be shared: " + f.toString());
                }
            }
        }
    }
    if(uriList.size() == 0)
    {
        Log.w("StorageModule", "ShareFiles: no files to share");
        return;
    }
    Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.setType("text/html");
    intent.putExtra(Intent.EXTRA_SUBJECT, header);
    intent.putExtra(Intent.EXTRA_TEXT, body);
    intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);

    activity.startActivity(Intent.createChooser(intent, "Share Files"));
}
于 2020-12-09T09:53:56.110 回答