从内部存储创build和共享文件
我的目标是在内部存储上创build一个XML文件,然后通过共享意图发送。
我能够使用此代码创build一个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,但我不能将其附加到电子邮件,上传等。
如果我手动检查文件的长度,这是正确的,并显示有一个合理的文件大小。
接下来,我创build了一个内容提供者,并试图引用该文件,而不是该文件的有效句柄。 ContentProvider
似乎从未被称为任何一点。
Uri uri = Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + fileName); return uri;
这将返回content://com.my.package.provider/myfile.xml,但是我检查文件,它的长度为零。
如何正确访问文件? 我是否需要与内容提供者创build文件? 如果是这样,怎么样?
更新
这是我用来分享的代码。 如果我selectGmail,它显示为附件,但是当我发送给出错误时无法显示附件,并且到达的电子邮件没有附件。
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()的调用?
我研究过这个错误,找不到任何明显的东西。 这两个链接都表明我需要注销一个接收器。
- ChooserActivity泄露了IntentReceiver
- 为什么Intent.createChooser()需要一个BroadcastReceiver以及如何实现?
我有一个接收器设置,但它是设置在其他地方,不需要应用程序注册/取消注册的AlarmManager
。
openFile代码(…)
如果需要,这里是我创build的内容提供者。
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; }
可以通过ContentProvider公开存储在应用程序私人目录中的文件。 以下是我所做的一些示例代码,展示了如何创build一个可以执行此操作的内容提供程序。
performance
<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文件复制到caching目录
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。
对于简单的testing,请从类似于以下内容的独立应用程序调用内容提供程序
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); } }
所以罗布的答案是正确的,我假设,但我做了一点不同。 据我所知,在提供商的设置:
android:exported="true"
您是否允许公众访问您的所有文件? 无论如何,只允许访问某些文件的方法是按照以下方式定义文件path权限:
<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>
现在,“所有文件”给出了相同types的公共权限,我猜测作为选项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的帮助链接 ,因为我基于我在那里find的信息的所有post。 尽pipefind相机应用程序的包信息有一些麻烦。
希望能帮助到你。
如果您需要许可其他应用程序来查看您的应用程序的私人文件(对于共享或其他),您可能可以节省一些时间,只需使用v4 compat库的FileProvider