Android 从内部存储创建和共享文件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12170386/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Create and Share a File from Internal Storage
提问by Kirk
My goal is to create a XML file on internal storageand then send it through the share Intent.
我的目标是在内部存储上创建一个 XML 文件,然后通过共享 Intent 发送它。
I'm able to create a XML file using this code
我可以使用此代码创建 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();
I'm stuck trying to retrieve a Uri to the output file in order to share it. I first tried to access the file by converting the file to a Uri
我被困在试图将 Uri 检索到输出文件以共享它。我首先尝试通过将文件转换为 Uri 来访问该文件
File outFile = context.getFileStreamPath(fileName);
return Uri.fromFile(outFile);
This returns file:///data/data/com.my.package/files/myfile.xmlbut I cannot appear to attach this to an email, upload, etc.
这将返回file:///data/data/com.my.package/files/myfile.xml但我似乎无法将其附加到电子邮件、上传等。
If I manually check the file length, it's proper and shows there is a reasonable file size.
如果我手动检查文件长度,它是正确的,并显示文件大小合理。
Next I created a content provider and tried to reference the file and it isn't a valid handle to the file. The ContentProvider
doesn't ever seem to be called a any point.
接下来,我创建了一个内容提供程序并尝试引用该文件,但它不是该文件的有效句柄。在ContentProvider
没有以往任何时候都似乎被称为任意点。
Uri uri = Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + fileName);
return uri;
This returns content://com.my.package.provider/myfile.xmlbut I check the file and it's zero length.
这将返回content://com.my.package.provider/myfile.xml但我检查了文件,它的长度为零。
How do I access files properly? Do I need to create the file with the content provider? If so, how?
如何正确访问文件?我需要使用内容提供程序创建文件吗?如果是这样,如何?
Update
更新
Here is the code I'm using to share. If I select Gmail, it does show as an attachment but when I send it gives an error Couldn't show attachmentand the email that arrives has no attachment.
这是我用来分享的代码。如果我选择 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;
}
}
I noticed that there is an Exception
thrown here from inside createChooser(...), but I can't figure out why it's thrown.
我注意到Exception
这里从createChooser(...)内部抛出了一个,但我不知道为什么它被抛出。
E/ActivityThread(572): Activity com.android.internal.app.ChooserActivity has leaked IntentReceiver com.android.internal.app.ResolverActivity$1@4148d658 that was originally registered here. Are you missing a call to unregisterReceiver()?
E/ActivityThread(572): Activity com.android.internal.app.ChooserActivity 泄露了原先在这里注册的 IntentReceiver com.android.internal.app.ResolverActivity$1@4148d658。您是否错过了对 unregisterReceiver() 的调用?
I've researched this error and can't find anything obvious. Both of these links suggest that I need to unregister a receiver.
我已经研究了这个错误,但找不到任何明显的东西。这两个链接都表明我需要注销接收器。
- ChooserActivity has leaked IntentReceiver
- Why does Intent.createChooser() need a BroadcastReceiver and how to implement?
I have a receiver setup, but it's for an AlarmManager
that is set elsewhere and doesn't require the app to register / unregister.
我有一个接收器设置,但它用于在AlarmManager
别处设置的并且不需要应用程序注册/取消注册。
Code for openFile(...)
openFile(...) 的代码
In case it's needed, here is the content provider I've created.
如果需要,这里是我创建的内容提供程序。
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;
}
采纳答案by Rob
It is possible to expose a file stored in your apps private directory via a ContentProvider. Here is some example code I made showing how to create a content provider that can do this.
可以通过 ContentProvider 公开存储在您的应用程序私有目录中的文件。这是我制作的一些示例代码,展示了如何创建可以执行此操作的内容提供程序。
Manifest
显现
<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>
In your ContentProvider override openFile to return the ParcelFileDescriptor
在您的 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);
}
Make sure you have copied your xml file to the cache directory
确保您已将 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
}
}
Now any other apps should be able to get an InputStream for your private file by using the content uri (content://com.example.prov/myfile.xml)
现在,任何其他应用程序都应该能够使用内容 uri (content://com.example.prov/myfile.xml) 为您的私有文件获取 InputStream
For a simple test, call the content provider from a seperate app similar to the following
对于简单的测试,从类似于以下的单独应用程序调用内容提供程序
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);
}
}
回答by Dusko
So Rob's answer is correct I assume but I did it a bit differently. As far as I understand, with the setting in in provider:
所以我认为罗布的回答是正确的,但我的做法有点不同。据我了解,在 provider 中设置:
android:exported="true"
you are giving public access to all your files?! Anyway, a way to give only access to some files is to define file path permissions in the following way:
您是否允许公开访问您的所有文件?!无论如何,只允许访问某些文件的一种方法是按以下方式定义文件路径权限:
<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>
and then in your XML directory you define file_paths.xmlfile as follows:
然后在您的 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>
now, the "allfiles" gives the same kind of public permission I guess as the option android:exported="true"but you don't really want that I guess so to define a subdirectory is the next line. Then all you have to do is store the files you want to share, there in that dir.
现在,“allfiles”提供了与选项android:exported="true"相同类型的公共权限,但我猜你并不真正想要这样定义子目录是下一行。然后您所要做的就是将要共享的文件存储在该目录中。
Next what you have to do is, as also Rob says, obtain a URI for this file. This is how I did it:
接下来您要做的是,正如 Rob 所说,获取此文件的 URI。我是这样做的:
Uri contentUri = FileProvider.getUriForFile(context, "com.your.app.package", sharedFile);
Then, when I have this URI, I had to attach to it permissions for other app to use it. I was using or sending this file URI to camera app. Anyway this is the way how I got the other app package info and granted permissions to the URI:
然后,当我拥有这个 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);
I left the code for camera as I did not want to take some other example I did not work on. But this way you see that you can attach permissions to URI itself.
我留下了相机的代码,因为我不想采取其他一些我没有研究过的例子。但是通过这种方式,您可以看到可以将权限附加到 URI 本身。
The camera's thing is that I can set it via ClipData and then additionally set permissions. I guess in your case you only need FLAG_GRANT_READ_URI_PERMISSION as you are attaching a file to an email.
相机的事情是我可以通过 ClipData 设置它,然后另外设置权限。我想在您的情况下,您只需要 FLAG_GRANT_READ_URI_PERMISSION 就可以将文件附加到电子邮件中。
Here is the linkto help on FileProvider as I based all of my post on the info I found there. Had some trouble finding a package info for camera app though.
这是有关 FileProvider 的帮助链接,因为我所有的帖子都基于我在那里找到的信息。但是在查找相机应用程序的包信息时遇到了一些麻烦。
Hope it helps.
希望能帮助到你。
回答by Splash
If you need to permission other apps to see your app's private files (for Share, or otherwise) you might be able to save some time and just use v4 compat library's FileProvider
如果您需要允许其他应用程序查看您应用程序的私有文件(用于共享或其他),您可能可以节省一些时间,只需使用 v4 兼容库的FileProvider