web-dev-qa-db-fra.com

Créer et partager un fichier à partir du stockage interne

Mon objectif est de créer un fichier XML sur le stockage interne puis de l'envoyer via le partage Intent.

Je peux créer un fichier XML en utilisant ce code

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

Je suis coincé à essayer de récupérer un Uri dans le fichier de sortie afin de le partager. J'ai d'abord essayé d'accéder au fichier en convertissant le fichier en Uri

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

Cela renvoie file: ///data/data/com.my.package/files/myfile.xml mais je n'arrive pas à le joindre à un e-mail, à un téléchargement, etc.

Si je vérifie manuellement la longueur du fichier, c'est correct et montre qu'il y a une taille de fichier raisonnable.

Ensuite, j'ai créé un fournisseur de contenu et essayé de référencer le fichier et ce n'est pas un descripteur valide pour le fichier. Le ContentProvider ne semble jamais être appelé un point quelconque.

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

Cela renvoie content: //com.my.package.provider/myfile.xml mais je vérifie le fichier et sa longueur est nulle.

Comment accéder correctement aux fichiers? Dois-je créer le fichier avec le fournisseur de contenu? Si c'est le cas, comment?

Mise à jour

Voici le code que j'utilise pour partager. Si je sélectionne Gmail, il apparaît en tant que pièce jointe mais lorsque j'envoie, il donne une erreur Impossible d'afficher la pièce jointe et l'e-mail qui arrive n'a pas de pièce jointe .

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

J'ai remarqué qu'il y a un Exception jeté ici depuis l'intérieur createChooser (...), mais je ne peux pas comprendre pourquoi il est jeté.

E/ActivityThread (572): L'activité com.Android.internal.app.ChooserActivity a divulgué IntentReceiver com.Android.internal.app.ResolverActivity$1@4148d658 qui était initialement enregistré ici. Vous manquez un appel à unregisterReceiver ()?

J'ai recherché cette erreur et je ne trouve rien d'évident. Ces deux liens suggèrent que je dois désinscrire un récepteur.

J'ai une configuration de récepteur, mais c'est pour un AlarmManager qui est défini ailleurs et ne nécessite pas l'application pour s'inscrire/se désinscrire.

Code pour openFile (...)

Au cas où cela serait nécessaire, voici le fournisseur de contenu que j'ai créé.

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

Il est possible d'exposer un fichier stocké dans le répertoire privé de vos applications via un ContentProvider. Voici un exemple de code que j'ai créé montrant comment créer un fournisseur de contenu qui peut le faire.

Manifeste

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

Dans votre ContentProvider, remplacez openFile pour renvoyer le 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);
}

Assurez-vous d'avoir copié votre fichier xml dans le répertoire cache

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

Désormais, toutes les autres applications devraient pouvoir obtenir un InputStream pour votre fichier privé en utilisant l'URI de contenu (content: //com.example.prov/myfile.xml)

Pour un test simple, appelez le fournisseur de contenu à partir d'une application distincte similaire à la suivante

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

Donc, la réponse de Rob est correcte, je suppose, mais je l'ai fait un peu différemment. Pour autant que je comprends, avec la mise en fournisseur:

Android:exported="true"

vous donnez un accès public à tous vos fichiers?! Quoi qu'il en soit, un moyen de n'accorder qu'un accès à certains fichiers consiste à définir les autorisations de chemin d'accès aux fichiers de la manière suivante:

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

puis dans votre répertoire XML vous définissez le fichier file_paths.xml comme suit:

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

maintenant, le "allfiles" donne le même type d'autorisation publique que je suppose que l'option Android: exports = "true" mais vous ne voulez pas vraiment que je suppose que pour définir un sous-répertoire est la ligne suivante. Ensuite, tout ce que vous avez à faire est de stocker les fichiers que vous souhaitez partager, dans ce répertoire.

Ce que vous devez ensuite faire est, comme le dit également Rob, obtenir un URI pour ce fichier. Voici comment je l'ai fait:

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

Ensuite, lorsque j'ai cet URI, j'ai dû y attacher des autorisations pour qu'une autre application puisse l'utiliser. J'utilisais ou envoyais ce fichier URI à l'application appareil photo. Quoi qu'il en soit, c'est ainsi que j'ai obtenu les autres informations sur le package d'application et accordé des autorisations à l'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);

J'ai laissé le code de la caméra car je ne voulais pas prendre un autre exemple sur lequel je ne travaillais pas. Mais de cette façon, vous voyez que vous pouvez attacher des autorisations à l'URI lui-même.

Le truc de la caméra est que je peux le configurer via ClipData, puis définir des autorisations supplémentaires. Je suppose que dans votre cas, vous n'avez besoin que de FLAG_GRANT_READ_URI_PERMISSION lorsque vous joignez un fichier à un e-mail.

Voici le lien pour aider FileProvider car j'ai basé tout mon post sur les informations que j'ai trouvées là-bas. J'ai eu du mal à trouver des informations sur le package de l'application appareil photo.

J'espère que ça aide.

15
Dusko

Si vous devez autoriser d'autres applications à voir les fichiers privés de votre application (pour le partage ou autre), vous pourrez peut-être gagner du temps et simplement utiliser la bibliothèque de compatibilité v4 FileProvider

4
Splash