Crear y compartir un archivo desde el almacenamiento interno

Mi objetivo es crear un archivo XML en el almacenamiento interno y luego enviarlo a través de la intención de compartir.

Puedo crear un archivo XML usando este código

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(); 

Estoy atrapado tratando de recuperar un Uri en el archivo de salida con el fin de compartirlo. Primero intenté tener acceso al archivo convirtiendo el archivo a un Uri

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

Esto devuelve el archivo: ///data/data/com.my.package/files/myfile.xml pero no puedo adjuntarlo a un correo electrónico, cargar, etc.

Si compruebo manualmente la longitud del archivo, es correcto y muestra que hay un tamaño de archivo razonable.

A continuación, creé un proveedor de contenido y trató de hacer referencia al archivo y no es un identificador válido para el archivo. El ContentProvider nunca parece ser llamado un punto cualquiera.

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

Esto devuelve el contenido: //com.my.package.provider/myfile.xml pero compruebo el archivo y su longitud cero.

¿Cómo accedo a los archivos correctamente? ¿Necesito crear el archivo con el proveedor de contenido? ¿Si es así, cómo?

Actualizar

Aquí está el código que estoy usando para compartir. Si selecciono Gmail, se mostrará como un archivo adjunto, pero cuando lo envíe da un error No se pudo mostrar el archivo adjunto y el correo electrónico que llega no tiene ningún archivo adjunto.

 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; } } 

Me di cuenta de que hay una Exception lanzada aquí desde dentro de createChooser (…) , pero no puedo entender por qué se ha lanzado.

E / ActivityThread (572): Actividad com.android.internal.app.ChooserActivity ha filtrado IntentReceiver com.android.internal.app.ResolverActivity$1@4148d658 que fue registrado originalmente aquí. ¿Le falta una llamada para anular el registroReceiver ()?

He investigado este error y no puedo encontrar nada obvio. Estos dos enlaces sugieren que necesito anular el registro de un receptor.

  • ChooserActivity ha filtrado IntentReceiver
  • ¿Por qué Intent.createChooser () necesita un BroadcastReceiver y cómo implementarlo?

Tengo una configuración de receptor, pero es para un AlarmManager que se establece en otro lugar y no requiere que la aplicación se registre o cancele el registro.

Código para openFile (…)

En caso de que sea necesario, aquí está el proveedor de contenido que he creado.

 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; } 

Es posible exponer un archivo almacenado en su directorio privado de aplicaciones a través de un ContentProvider. He aquí un ejemplo de código que hice mostrando cómo crear un proveedor de contenido que puede hacer esto.

Manifiesto

 <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> 

En su ContentProvider anule openFile para devolver el 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); } 

Asegúrese de haber copiado el archivo xml en el directorio de caché

  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 } } 

Ahora cualquier otra aplicación debe ser capaz de obtener un InputStream para su archivo privado utilizando el contenido uri (content: //com.example.prov/myfile.xml)

Para una prueba sencilla, llame al proveedor de contenido desde una aplicación independiente similar a la siguiente

  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); } } 

Así que la respuesta de Rob es correcta supongo pero lo hice un poco diferente. Por lo que entiendo, con la configuración en el proveedor:

 android:exported="true" 

Usted está dando acceso público a todos sus archivos ?! De todos modos, una manera de dar sólo acceso a algunos archivos es definir permisos de ruta de archivo de la siguiente manera:

 <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> 

Y luego en su directorio XML se define el archivo file_paths.xml como sigue:

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

Ahora, el "allfiles" da el mismo tipo de permiso público, supongo que como la opción android: exportado = "true", pero en realidad no quieres que supongo que para definir un subdirectorio es la siguiente línea. Entonces todo lo que tienes que hacer es almacenar los archivos que quieres compartir, allí en ese directorio.

A continuación, lo que tienes que hacer es, como también dice Rob, obtener un URI para este archivo. Así es como lo hice:

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

Entonces, cuando tengo este URI, tuve que adjuntar a él permisos para que otra aplicación lo utilizara. Yo estaba usando o enviando este archivo URI a la aplicación de cámara. De todos modos esta es la forma en que obtuve la información del paquete de otra aplicación y concedió permisos para el 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); 

Dejé el código para la cámara, ya que no quería tomar algún otro ejemplo que no funcionó. Pero esta manera usted ve que usted puede atar permisos al URI sí mismo.

La cosa de la cámara es que puedo fijarla vía ClipData y después fijar también permisos. Supongo que en su caso sólo necesita FLAG_GRANT_READ_URI_PERMISSION al adjuntar un archivo a un correo electrónico.

Aquí está el enlace para ayudar en FileProvider como base de todos mis mensajes en la información que encontré allí. Tuve algunos problemas para encontrar un paquete de información para la aplicación de la cámara sin embargo.

Espero eso ayude.

Si necesitas permiso para que otras aplicaciones vean los archivos privados de tu aplicación (para compartir o de otra forma), podrías ahorrar algo de tiempo y usar FileProvider de la biblioteca v4 compat

  • Cómo implementar un ContentObserver para registros de llamadas
  • CursorLoader no se actualiza después del cambio de datos
  • Especificación de límite / desplazamiento para consultas de ContentProvider
  • ¿Cuáles son la semántica de withValueBackReference?
  • Cómo agregar una cláusula de límite utilizando el proveedor de contenido
  • Leer todos los contactos del teléfono y sim Android
  • Manejo de tablas y cursores SQLite en Android
  • IllegalStateException "intenta volver a abrir un objeto ya cerrado" en SimpleCursorAdapter de ContentProvider
  • ¿Cuál es el uso de proveedores de contenido privados?
  • Compartir una imagen en el directorio de datos de la aplicación a través de un ContentProvider en Android
  • E / ActivityThread: No se pudo encontrar información del proveedor para com.example.YPLContentProvider
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.