2012-08-29 15 views
40

Mi objetivo es crear un archivo XML en de almacenamiento interno y luego enviarlo a través del recurso compartido Compartir.Crear y compartir un archivo desde el almacenamiento interno

soy capaz de crear un archivo XML que utiliza 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 atascado tratando de recuperar un Uri al archivo de salida con el fin de compartirlo. Probé por primera vez para acceder al archivo al convertir el archivo a un Uri

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

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

Si verifico 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 e intenté hacer referencia al archivo y no es un identificador válido para el archivo. El ContentProvider nunca parece llamarse un punto.

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

Esto devuelve contenido: //com.my.package.provider/myfile.xml pero puedo comprobar el archivo y es de longitud cero.

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

actualización

Aquí está el código que estoy usando para compartir. Si selecciono Gmail, se muestra como un archivo adjunto, pero cuando lo envío arroja un error No se pudo mostrar el archivo adjunto y el correo electrónico que llega no tiene datos adjuntos.

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 tirado aquí desde el interior createChooser (...), pero no puedo entender por qué se ha tirado.

E/ActivityThread (572): Actividad com.android.internal.app.ChooserActivity ha filtrado IntentReceiver [email protected] que fue registrado inicialmente aquí. ¿Te estás perdiendo una llamada al unregisterReceiver()?

He investigado este error y no encuentro nada obvio. Ambos enlaces sugieren que necesito anular el registro de un receptor.

que tienen una configuración del receptor, pero es para un AlarmManager que se establece en otro lugar y no requiere la aplicación para registrar/anular 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; 
} 
+0

Creo que el primer método debería funcionar, en lugar de crear un contentProvider. ContentProvider se utiliza para devolver datos en lugar de un archivo completo, no servirá a sus necesidades. Supongo que no hay aplicaciones para manejar tu xml. ¿Puede publicar el código donde se está creando el intento de compartir – nandeesh

+0

Se agregó más código en caso de que sea más obvio, así como una excepción que no entiendo si está relacionada o no. – Kirk

+0

¿Has creado un ContentProvider? Si tiene, puede publicar su código para ello. Si no lo has hecho, necesitas crear un ContentProvider y anular el método openFile. Ese método lo llamará gmail cuando intente abrir el archivo asociado con el contenido: //com.my.package.provider/myfile.xml uri. – Rob

Respuesta

35

Es posible exponer un archivo almacenado en su directorio privado de aplicaciones a través de ContentProvider. Aquí hay un código de ejemplo que hice que muestra 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 abrirArchivo anulación ContentProvider 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 que ha 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 otro las aplicaciones deberían poder obtener un InputStream para su archivo privado usando usin g el contenido uri (contenido: //com.example.prov/myfile.xml)

Para una prueba simple, llamar al proveedor de contenido de una aplicación separada similar al 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); 
    } 
} 
+1

Gracias por la respuesta. Espero utilizar un ContentProvider si es posible para acceder a los datos privados. getExternalCacheDir() no es compatible con la API 7 a la que debo dirigirme. – Kirk

+0

En API 7 puede usar getExternalStorageDirectory() para obtener un directorio público. No estoy seguro de que sea posible proporcionar acceso público a la parte privada de aplicaciones del sistema de archivos, incluso con un ContentProvider. – Rob

+0

Gracias por la sugerencia. Estoy buscando escribir en el almacenamiento interno en lugar de la tarjeta SD. ¿Alguna idea sobre cómo lograr eso? – Kirk

3

Si necesita para permitir que otras aplicaciones vean los archivos privados de su aplicación (para Compartir, o de otro modo) usted podría ahorrar algo de tiempo y simplemente usar la biblioteca de compatibilidad de v4 FileProvider

13

La respuesta de Rob es correcta, supongo, pero lo hice de manera diferente . Por lo que yo entiendo, con la configuración en el proveedor:

android:exported="true" 

¿está dando acceso público a todos sus archivos? De todos modos, una manera de dar sólo el acceso a algunos archivos es definir permisos ruta del 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 el directorio de XML se define file_paths.xml archivo de la siguiente manera:

<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: exported = "true" pero realmente no lo quiero, supongo que definir un subdirectorio es la próxima línea. Entonces, todo lo que tienes que hacer es almacenar los archivos que deseas compartir, allí en ese directorio.

Luego, como dice Rob, obtenga un URI para este archivo. Esto es cómo lo hice:

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

Entonces, cuando tengo este URI, tuve que agregar a dicho permisos para otra aplicación usarlo. Estaba usando o enviando este archivo URI a la aplicación de la cámara. De todos modos esta es la manera cómo llegué la otra información del paquete de aplicaciones y permisos concedidos al 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); 

me dejó el código para la cámara ya que no quiero tomar algún otro ejemplo que no funcionó sucesivamente. Pero de esta forma puede ver que puede adjuntar permisos al URI mismo.

Lo que ocurre con la cámara es que puedo configurarlo a través de ClipData y luego configurar permisos adicionales. Supongo que en su caso solo necesita FLAG_GRANT_READ_URI_PERMISSION porque está adjuntando un archivo a un correo electrónico.

Aquí está el link para ayudar en FileProvider, ya que basé toda mi publicación en la información que encontré allí. Tuve algunos problemas para encontrar un paquete de información para la aplicación de cámara.

Espero que ayude.

Cuestiones relacionadas