ContentProvider再探——Fournisseur de documents


Introduction à cette section :

Après avoir étudié la section précédente, je pense que vous savez déjà comment utiliser le ContentProvider fourni par le système ou personnaliser le ContentProvider. Il a essentiellement répondu aux besoins du développement quotidien. Fait intéressant, j'ai vu ces autres fournisseurs dans la documentation officielle :

1.png

Fournisseur de calendrier : Le fournisseur de calendrier est. une bibliothèque de ressources pour les événements liés au calendrier. Grâce à l'API qu'elle fournit, nous Vous pouvez ajouter, supprimer, modifier et vérifier le calendrier, l'heure, les réunions, les rappels et d'autres contenus !
Fournisseur de contacts : Fournisseur de contacts, il va sans dire que celui-ci est le plus utilisé~ Je reviendrai traduire cet article plus tard quand j'aurai le temps !
Storage Access Framework (SAF) : Le framework d'accès au stockage, une nouveauté introduite après la version 4.4, permet aux utilisateurs de parcourir les fichiers sur leurs téléphones mobiles. Le contenu de stockage est pratique et le contenu accessible comprend non seulement : les documents, les images, les vidéos, les audios, les téléchargements, mais inclut également tous Contenu fourni par un ContentProvider spécifique (qui doit avoir une API convenue). Peu importe d’où vient le contenu, quelle que soit l’application En appelant la commande pour parcourir le contenu des fichiers système, le système utilisera une interface unifiée pour vous permettre de parcourir.
En fait, il s'agit d'une application intégrée appelée DocumentsUI. Parce que son IntentFilter n'a pas de LAUNCHER, nous n'en avons pas. Trouvez ce truc sur le bureau ! Hé, essayez le code suivant. Ici, nous avons sélectionné deux téléphones mobiles à titre de comparaison : À titre de comparaison, Lenovo S898T de 4.2 et Nexus 5 de 5.0.1 sont comparés. Exécutez le code suivant :

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent. addCategory (Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivity(intent);
Voici le résultat courant :

2.png3.png

Celui de droite est la nouveauté que la version 4.4 nous apporte. Généralement, nous pouvons l'utiliser lorsque nous obtenons le fichier Url~. Ensuite, descendez simplement le document~


2. Descendez simplement le document :

1) La composition du framework SAF :

  • Fournisseur de documents : Un ContentProvider spécial qui permet à un service de stockage (tel que Google Drive) de Affichez les fichiers que vous gérez au monde extérieur. Il s'agit d'une sous-classe de DocumentsProvider De plus, le format de stockage du fournisseur de documents. Il est cohérent avec le format de stockage de fichiers traditionnel. Quant à la manière de stocker votre contenu, cela dépend entièrement de vous. Le système Android en intègre plusieurs. Un tel fournisseur de documents, tel qu'un fournisseur de documents pour les téléchargements, les images et les vidéos !
  • Application client : Un logiciel client ordinaire qui peut recevoir des retours du fournisseur de documents en déclenchant du contenu ACTION_OPEN_DOCUMENT et/ou ACTION_CREATE_DOCUMENT, tel que sélectionner une image, Renvoyez ensuite un Uri.
  • Picker : Une interface similaire à un gestionnaire de fichiers, et une interface au niveau du système qui donne accès aux conditions de filtrage des clients Le canal de contenu du fournisseur de documents est le programme DocumentsUI mentionné au début !

Quelques fonctionnalités :

  • Les utilisateurs peuvent parcourir le contenu fourni par tous les fournisseurs de documents, pas seulement un seul programme d'application
  • fournit un accès continu à long terme aux fichiers du fournisseur de documents et la persistance des données. Les utilisateurs peuvent ajouter, supprimer, modifier et enregistrer du contenu géré par le fournisseur de documents
  • Prend en charge les services de contenu multi-utilisateurs et temporaires, tels que les fournisseurs de stockage USB, qui n'apparaîtront que lorsque le pilote est installé avec succès

2) Présentation :

Le cœur de SAF est d'implémenter une sous-classe de DocumentsProvider ou un ContentProvider. chez un fournisseur de documents est organisé avec une arborescence de répertoires de fichiers traditionnelle :

4.png

3) Organigramme :

Comme mentionné ci-dessus, les données du fournisseur de documents sont basées sur la structure hiérarchique traditionnelle des fichiers. , mais ce n'est que la représentation externe. La manière de stocker vos données dépend de vous, à condition que votre interface à l'étranger soit accessible via l'API de DocumentsProvider. L'organigramme ci-dessous présente la structure possible d'une application photo utilisant SAF :

5.png

Analyse :

Sur l'image ci-dessus, nous pouvons voir que Picker est un pont entre les appelants et les fournisseurs de contenu ! Il fournit et indique à l'appelant qu'il peut choisir Quels fournisseurs de contenu, tels que DriveDocProvider, UsbDocProvider, CloundDocProvider ici.
Lorsque le client déclenche l'intention de ACTION_OPEN_DOCUMENT ou ACTION_CREATE_DOCUMENT, l'interaction ci-dessus se produira. Bien sûr, nous pouvons également ajouter des conditions de filtre à l'intention, comme limiter le type MIME à "image"

6.png

Ce sont les choses ci-dessus, si vous le souhaitez ! également installé Si vous utilisez d'autres logiciels pour visualiser des images, vous les verrez également ici ! Pour faire simple : une fois que le client a envoyé l'intention des deux actions ci-dessus, l'interface utilisateur du sélecteur sera ouverte, où les options disponibles pertinentes seront affichées. Fournisseur de documents, au choix des utilisateurs, les utilisateurs peuvent obtenir des informations pertinentes sur le fichier après l'avoir sélectionné !


4) Appel du client et récupération de l'Uri renvoyé

Le code d'implémentation est le suivant :

classe publique MainActivity étend AppCompatActivity implémente View.OnClickListener {
    private static final int READ_REQUEST_CODE = 42;

    @Override
    protected void onCreate(Bundle save dInstanceState) {
        super .onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn_show = (Bouton) findViewById(R.id.btn_show);
        btn_show.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intention. setType("image/*");
        startActivityForResult(intent, READ_REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            Uri uri;
            if (data != null) {
                 uri = data.getData();
                Log.e ("HeHe", "Uri : " + uri.toString());
            }
        }
    }
}

Résultat d'exécution : Par exemple, si nous sélectionnons le chien, l'interface utilisateur du Picker s'éteindra d'elle-même, et vous pourrez alors voir un tel uri sur le Logcat :

7.png


5) Obtenez les paramètres du fichier en fonction de l'uri

Le code principal est le suivant :

public void dumpImageMetaData( Uri uri) {
Curseur curseur = getContentResolver()
.query(uri, null, null, null, null, null);
essayez {
if (cursor != null && curseur.moveToFirst ()) {
        String displayName = curseur.getString(
                    curseur.getColumnIndex(OpenableColumns.DISPLAY_NAME)); if (!cursor.isNull(sizeIndex)) {
size = curseur.getString(sizeIndex);
                                                                                                                                       ";
}
Log.e("HeHe", "Taille : " + taille) ;
}
}enfin {
curseur.close();
}
}

Résultat de l'exécution : C'est toujours le même chien. Après avoir appelé la méthode, le nom et la taille du fichier seront saisis, en octets

8.png


6) Obtenir un Bitmap

basé sur Uri Le code de base est le suivant :

Bitmap privé getBitmapFromUri (Uri uri) lance IOException {
ParcelFileDescriptor parcelFileDescriptor =
getContentResolver().openFileDescriptor(uri, "r");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Image Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor .close();
retourner l'image;
}

Exécuter le résultat :

9.gif

7) Obtenez le flux d'entrée basé sur Uri

Le code de base est le suivant :

private String readTextFromUri (Uri uri) lance IOException {
InputStream inputStream = getContentResolver().openInputStream(uri);
BufferedReader reader = new BufferedReader(new InputStreamReader(
inputStream));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readL ine()) != null ) {
       stringBuilder.append(line >
);

Le contenu ci-dessus vous indique uniquement ce que vous pouvez savoir grâce à un Uri, et l'Uri est obtenu via SAF !


8) Créer de nouveaux fichiers et supprimer des fichiers :

Créer des fichiers :

private void createFile(String mimeType, String fileName) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, WRITE_REQUEST_CODE);
}

L'URI du fichier créé peut être obtenu dans onActivityResult()

Supprimer le fichier :

À condition que Document.COLUMN_FLAGS contienne SUPPORTS_DELETE

DocumentsContract.deleteDocument(getContentResolver(), uri);

9) Écrivez un fournisseur de documents personnalisé

Si vous souhaitez que les données de votre application soient ouvertes dans documentui, vous devez écrire votre propre fournisseur de documents. Ce qui suit décrit les étapes pour personnaliser DocumentsProvider :

  • La version de l'API est 19 ou supérieure
  • Enregistrez le fournisseur dans manifest.xml
  • Nom du fournisseur Ajoutez le nom du package au nom de la classe, par exemple : com.example.android.storageprovider.MyCloudProvider
  • Authority est le nom du package + le nom du type du fournisseur, par exemple : com.example.android .storageprovider.documents
  • La valeur de l'attribut android:exported est vraie

Ce qui suit est un exemple d'écriture Provider :

<manifest... >
...
<uses-sdk
android:minSdkVersion="19"
android :targetSdkVersion="19" />
                                                               🎜> android:grantUriPermissions="true"
android:exported="true"
android:permission="android.permission.MANAGE_DOCUMENTS"
android : activé ="@bool/atLeastKitKat">
                                                                                                             </provider>
</application>

</manifest>

10) La sous-classe de DocumentsProvider

implémente au moins les méthodes suivantes :

  • queryRoots()
  • queryChildDocuments()
  • queryDocument()
  • openDocument()

Il existe d'autres méthodes, mais elles ne sont pas nécessaires. Ce qui suit montre une implémentation de l'accès au système de fichiers La manière approximative d’écrire DocumentsProvider.

Implémenter les racines de requête

@Override
public Cursor queryRoots(String[] projection) lance FileNotFoundException {

    // Créez un curseur avec soit les champs demandés, soit la projection par défaut
    // projection if "projection" est null.
    final MatrixCursor result =
            new MatrixCursor(resolveRootProjection(projection));

    // Si l'utilisateur n'est pas connecté, renvoie un curseur racine vide.  Cela supprime notre
    // fournisseur de la liste entièrement.
    if (!isUserLoggedIn()) {
        return result;
    }

    // Il est possible d'avoir plusieurs racines ( par exemple pour plusieurs comptes dans la
    // même application) -- il suffit d'ajouter plusieurs lignes de curseur.
    // Construisez une ligne pour une racine appelée "MyCloud".
    finale MatrixCursor.RowBuilder row = result.newRow ();
    row.add(Root.COLUMN_ROOT_ID, ROOT);
    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));

    // FLAG_SUPPORTS_CREATE signifie au au moins un répertoire sous la racine prend en charge
    // création de documents. FLAG_SUPPORTS_RECENTS signifie que les documents les plus
    // récemment utilisés de votre application s'afficheront dans la catégorie "Récents".
    // FLAG_SUPPORTS_SEARCH permet aux utilisateurs de rechercher tous les documents que l'application
    // partage.
    row.add (Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
            Root.FLAG_SUPPORTS_RECENTS |
            Root.FLAG_SUPPORTS_SEARCH);

    // COLUMN_TITLE est le titre racine (par exemple Galerie, Drive).
    row.add (Root.COLUMN_TITLE, getContext().getString(R.string.title));

    // Cet identifiant de document ne ne peut pas changer une fois qu'il est partagé.
    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));

    // Les types MIME enfants sont utilisés pour filtrer les racines et uniquement présent à les
    //  racines utilisateur qui contiennent le type souhaité quelque part dans leur hiérarchie de fichiers.
    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace ());
    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);

    return result;
}

Implémenter queryChildDocuments

public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
                             String sortOrder) lance FileNotFoundException {

    résultat final MatrixCursor = nouveau
            MatrixCursor(resolveDocumentProjection( projection));
    final File parent = getFileForDocId(parentDocumentId);
    for (File file : parent.listFiles()) {
        // Ajoute le nom d'affichage du fichier, le type MIME, la taille, et ainsi de suite .<>
@Override
public Cursor queryDocument(String documentId, String[] projection) throws
        FileNotFoundException {
    // Créez un curseur avec la projection demandée ou la projection par défaut.
final MatrixCursor result = new

            MatrixCursor(resolveDocumentProjection(projection));    includeFile(result, documentId, null);    return result;

}

D'accord, c'est à peu près tout dans le document : Au début, je voulais le traduire moi-même, mais ensuite j'ai trouvé la traduction chinoise de ce document en passant du temps en ligne, donc j'étais trop paresseux ~

Lien de traduction chinoise :cadre d'accès au stockage Android Storage Access Framework


3. Android 4.4 Problème d'obtention du chemin de ressource :

En fait, l'endroit où nous utilisons davantage ce SAF n'est rien d'autre que l'obtention de l'Uri de l'image, et d'en haut Exemples que nous avons également trouvés : Le lien que l'on obtient est comme ceci :

content://com.android.providers.media.documents/document/image%3A69983

Pour un tel lien, on peut obtenir directement l'uri grâce à la méthode ci-dessus !

Bien sûr, il s'agit de la version 4.4 ou supérieure~ !

S'il s'agit d'une version précédente : l'uri peut être comme ceci :

content://media/external/images/media/image%3A69983

Voici une solution complète que j'ai vue ailleurs, le lien d'origine : Sous Android 4.4 Problème d'obtention du chemin des ressources

> if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
         // ExternalStorageProvider   
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String [] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return  Environnement.getExternalStorageDirectory () + "/" + split[1];
            }

            // TODO gérer les volumes non principaux   
         }
        // DownloadsProvider   
        sinon if (isDownloadsDocument(uri) ) {

            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                     Uri.pars e("content://downloads/public_downloads"), Long. valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider   
       else if (isMediaDocument(uri)) {
            finale String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_ CONTENT_URI;
            } else if ( "audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }
            final String selection = "_id=?";
            final String[] selectionArgs = new String[] {
                    split[1]
            };
            return getDataColumn(context, contentUri, selection, selectionArgs);
         }
    }
    // MediaStore (et général)  
    else if ("content".equalsIgnoreCase(uri.getScheme())) {
        return getDataColumn(context, uri, null, null);
    }
    // File  
    else if ( "file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }
    return null;
}

/**
* Obtenez la valeur de la colonne de données pour ce Uri. Ceci est utile pour 
 * MediaStore Uris et autres fournisseurs de contenu basés sur des fichiers. 
 *
 * @param context Le contexte. 
 * @param uri L'Uri à interroger. 
 * @param selection (Facultatif) Filtre utilisé dans la requête. 
 * @param selectionArgs (Facultatif) Arguments de sélection utilisés dans la requête. 
 * @return La valeur de la colonne _data , qui est généralement un chemin de fichier. 
 */
public static String getDataColumn(Context context, Uri uri, String selection,
                                  String[] selectionArgs) {

    Cursor curs ou = null;
    finale String column = " _data";
    final String[] projection = {
            column
    };

    essayez {
         cursor = context.getContentResolver().query(uri, projection, selection, sélectionArgs ,
                null);
        if (curseur != null && cursor.moveToFirst()) {
           final int column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);


/**
 * @param uri L'Uri à vérifier. 
 * @return Si l'autorité Uri est ExternalStorageProvider. 
 */
public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
* @param uri L'Uri à vérifier.
* @return Quelle que soit l'autorité Uri, DownloadsProvider
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers. downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri L'Uri à vérifier. 
 * @return Si l'autorité Uri est MediaProvider. 
 */
public static boolean isMediaDocument(Uri uri) {
return "com .android.providers.media.documents".equals(uri.getAuthority());
}

Résumé de cette section :

D'accord, à propos de ça C'est tout pour Android Storage Access Framework SAF. Il n'y a pas d'exemples. Nous l'utiliserons plus tard et l'étudierons en profondeur. Sachez-le simplement. Il sera beaucoup plus facile d'obtenir le chemin du fichier après la version 4.4~

.