Datei teilen

Nachdem Sie Ihre App für die Freigabe von Dateien mithilfe von Inhalts-URIs eingerichtet haben, können Sie auf Anfragen anderer Apps für diese Dateien antworten. Eine Möglichkeit, auf diese Anfragen zu antworten, besteht darin, eine Schnittstelle zur Dateiauswahl der Serveranwendung bereitzustellen, die von anderen Anwendungen aufgerufen werden kann. Mit diesem Ansatz können Nutzer in einer Clientanwendung eine Datei aus der Serveranwendung auswählen und dann den Inhalts-URI der ausgewählten Datei empfangen.

In dieser Lektion erfahren Sie, wie Sie in Ihrer Anwendung eine Dateiauswahl Activity erstellen, die auf Anfragen für Dateien antwortet.

Dateianfragen erhalten

Um Anfragen für Dateien von Clientanwendungen zu erhalten und mit einem Inhalts-URI zu antworten, sollte Ihre Anwendung den Activity für die Dateiauswahl bereitstellen. Client-Apps starten diesen Activity durch Aufrufen von startActivityForResult() mit einem Intent, das die Aktion ACTION_PICK enthält. Wenn die Clientanwendung startActivityForResult() aufruft, kann die Anwendung ein Ergebnis an die Clientanwendung in Form eines Inhalts-URI für die vom Nutzer ausgewählte Datei zurückgeben.

Informationen zum Implementieren einer Anfrage für eine Datei in einer Clientanwendung finden Sie in der Lektion Freigegebene Datei anfordern.

Aktivität „Dateiauswahl erstellen“

Zum Einrichten der Dateiauswahl Activity geben Sie zuerst Activity in Ihrem Manifest sowie einen Intent-Filter an, der der Aktion ACTION_PICK und den Kategorien CATEGORY_DEFAULT und CATEGORY_OPENABLE entspricht. Fügen Sie außerdem MIME-Typ-Filter für die Dateien hinzu, die Ihre Anwendung in anderen Anwendungen bereitstellt. Das folgende Snippet zeigt, wie Sie den neuen Activity und den Intent-Filter angeben:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
        <application>
        ...
            <activity
                android:name=".FileSelectActivity"
                android:label="@File Selector" >
                <intent-filter>
                    <action
                        android:name="android.intent.action.PICK"/>
                    <category
                        android:name="android.intent.category.DEFAULT"/>
                    <category
                        android:name="android.intent.category.OPENABLE"/>
                    <data android:mimeType="text/plain"/>
                    <data android:mimeType="image/*"/>
                </intent-filter>
            </activity>

Aktivität für Dateiauswahl im Code definieren

Definieren Sie als Nächstes eine Activity-Unterklasse, die die im internen Speicher Ihrer Anwendung verfügbaren Dateien aus dem files/images/-Verzeichnis anzeigt und dem Nutzer die Möglichkeit gibt, die gewünschte Datei auszuwählen. Das folgende Snippet zeigt, wie diese Activity definiert und auf die Auswahl des Nutzers reagiert wird:

Kotlin

class MainActivity : Activity() {

    // The path to the root of this app's internal storage
    private lateinit var privateRootDir: File
    // The path to the "images" subdirectory
    private lateinit var imagesDir: File
    // Array of files in the images subdirectory
    private lateinit var imageFiles: Array<File>
    // Array of filenames corresponding to imageFiles
    private lateinit var imageFilenames: Array<String>

    // Initialize the Activity
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Set up an Intent to send back to apps that request a file
        resultIntent = Intent("com.example.myapp.ACTION_RETURN_FILE")
        // Get the files/ subdirectory of internal storage
        privateRootDir = filesDir
        // Get the files/images subdirectory;
        imagesDir = File(privateRootDir, "images")
        // Get the files in the images subdirectory
        imageFiles = imagesDir.listFiles()
        // Set the Activity's result to null to begin with
        setResult(Activity.RESULT_CANCELED, null)
        /*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */
        ...
    }
    ...
}

Java

public class MainActivity extends Activity {
    // The path to the root of this app's internal storage
    private File privateRootDir;
    // The path to the "images" subdirectory
    private File imagesDir;
    // Array of files in the images subdirectory
    File[] imageFiles;
    // Array of filenames corresponding to imageFiles
    String[] imageFilenames;
    // Initialize the Activity
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Set up an Intent to send back to apps that request a file
        resultIntent =
                new Intent("com.example.myapp.ACTION_RETURN_FILE");
        // Get the files/ subdirectory of internal storage
        privateRootDir = getFilesDir();
        // Get the files/images subdirectory;
        imagesDir = new File(privateRootDir, "images");
        // Get the files in the images subdirectory
        imageFiles = imagesDir.listFiles();
        // Set the Activity's result to null to begin with
        setResult(Activity.RESULT_CANCELED, null);
        /*
         * Display the file names in the ListView fileListView.
         * Back the ListView with the array imageFilenames, which
         * you can create by iterating through imageFiles and
         * calling File.getAbsolutePath() for each File
         */
         ...
    }
    ...
}

Auf eine Dateiauswahl reagieren

Wenn ein Nutzer eine freigegebene Datei auswählt, muss Ihre Anwendung ermitteln, welche Datei ausgewählt wurde, und dann einen Inhalts-URI für die Datei generieren. Da Activity die Liste der verfügbaren Dateien in einer ListView anzeigt, ruft das System die Methode onItemClick() auf, wenn der Nutzer auf einen Dateinamen klickt, mit der Sie die ausgewählte Datei abrufen können.

Wenn Sie den URI einer Datei mit einem Intent von einer Anwendung an eine andere senden, müssen Sie darauf achten, einen URI zu erhalten, den andere Anwendungen lesen können. Auf Geräten mit Android 6.0 (API-Level 23) und höher ist besondere Vorsicht aufgrund von Änderungen am Berechtigungsmodell in dieser Android-Version erforderlich. Insbesondere ist READ_EXTERNAL_STORAGE in Zukunft eine gefährliche Berechtigung, die der empfangenden App möglicherweise fehlt.

In Anbetracht dieser Erwägungen empfehlen wir, Uri.fromFile() nicht zu verwenden, da dies einige Nachteile mit sich bringt. Diese Methode:

  • Die Dateifreigabe zwischen Profilen wird nicht zugelassen.
  • Hierfür muss Ihre App auf Geräten mit Android 4.4 (API-Level 19) oder niedriger die Berechtigung WRITE_EXTERNAL_STORAGE haben.
  • Erfordert, dass empfangende Apps die Berechtigung READ_EXTERNAL_STORAGE haben. Dies schlägt bei wichtigen Freigabezielen wie Gmail fehl, die diese Berechtigung nicht haben.

Statt Uri.fromFile() können Sie auch URI-Berechtigungen verwenden, um anderen Anwendungen Zugriff auf bestimmte URIs zu gewähren. URI-Berechtigungen funktionieren zwar nicht für file://-URIs, die von Uri.fromFile() generiert wurden, funktionieren jedoch für URIs, die mit Contentanbietern verknüpft sind. Mit der FileProvider API können Sie solche URIs erstellen. Dieser Ansatz funktioniert auch mit Dateien, die sich nicht im externen Speicher, sondern im lokalen Speicher der Anwendung befinden, die den Intent sendet.

Rufen Sie in onItemClick() ein File-Objekt für den Dateinamen der ausgewählten Datei ab und übergeben Sie es zusammen mit der Zertifizierungsstelle, die Sie im Element <provider> für FileProvider angegeben haben, als Argument an getUriForFile(). Der resultierende Inhalts-URI enthält die Zertifizierungsstelle, ein Pfadsegment, das dem Verzeichnis der Datei entspricht (wie in den XML-Metadaten angegeben) und den Namen der Datei einschließlich ihrer Dateiendung. Im Abschnitt Teilbare Verzeichnisse angeben wird beschrieben, wie FileProvider Verzeichnisse auf der Grundlage von XML-Metadaten Pfadsegmenten zuordnet.

Das folgende Snippet zeigt, wie Sie die ausgewählte Datei erkennen und einen Inhalts-URI dafür abrufen:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            /*
             * Get a File for the selected file name.
             * Assume that the file names are in the
             * imageFilename array.
             */
            val requestFile = File(imageFilenames[position])
            /*
             * Most file-related method calls need to be in
             * try-catch blocks.
             */
            // Use the FileProvider to get a content URI
            val fileUri: Uri? = try {
                FileProvider.getUriForFile(
                        this@MainActivity,
                        "com.example.myapp.fileprovider",
                        requestFile)
            } catch (e: IllegalArgumentException) {
                Log.e("File Selector",
                        "The selected file can't be shared: $requestFile")
                null
            }
            ...
        }
        ...
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            /*
             * When a filename in the ListView is clicked, get its
             * content URI and send it to the requesting app
             */
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                /*
                 * Get a File for the selected file name.
                 * Assume that the file names are in the
                 * imageFilename array.
                 */
                File requestFile = new File(imageFilename[position]);
                /*
                 * Most file-related method calls need to be in
                 * try-catch blocks.
                 */
                // Use the FileProvider to get a content URI
                try {
                    fileUri = FileProvider.getUriForFile(
                            MainActivity.this,
                            "com.example.myapp.fileprovider",
                            requestFile);
                } catch (IllegalArgumentException e) {
                    Log.e("File Selector",
                          "The selected file can't be shared: " + requestFile.toString());
                }
                ...
            }
        });
        ...
    }

Sie können Inhalts-URIs nur für Dateien generieren, die sich in einem Verzeichnis befinden, das Sie in der Metadatendatei mit dem <paths>-Element angegeben haben. Eine Beschreibung hierzu finden Sie im Abschnitt Teilbare Verzeichnisse angeben. Wenn Sie getUriForFile() für ein File in einem Pfad aufrufen, den Sie nicht angegeben haben, erhalten Sie ein IllegalArgumentException.

Berechtigungen für die Datei gewähren

Da Sie nun einen Inhalts-URI für die Datei haben, die Sie für eine andere App freigeben möchten, müssen Sie der Client-App Zugriff auf die Datei gewähren. Erteilen Sie der Client-App Berechtigungen, um den Zugriff zu ermöglichen. Fügen Sie dazu den Inhalts-URI zu einem Intent hinzu und legen Sie dann Berechtigungs-Flags für Intent fest. Die von Ihnen gewährten Berechtigungen sind temporär und laufen automatisch ab, wenn der Aufgabenstack der empfangenden Anwendung fertig ist.

Das folgende Code-Snippet zeigt, wie Sie die Leseberechtigung für die Datei festlegen:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            ...
            if (fileUri != null) {
                // Grant temporary read permission to the content URI
                resultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                ...
            }
            ...
        }
        ...
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                ...
                if (fileUri != null) {
                    // Grant temporary read permission to the content URI
                    resultIntent.addFlags(
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
                ...
             }
             ...
        });
    ...
    }

Achtung:Das Aufrufen von setFlags() ist die einzige Möglichkeit, mit temporären Zugriffsberechtigungen sicheren Zugriff auf Ihre Dateien zu gewähren. Rufen Sie nicht die Methode Context.grantUriPermission() für den Inhalts-URI einer Datei auf, da diese Methode Zugriff gewährt, den Sie nur durch Aufrufen von Context.revokeUriPermission() widerrufen können.

Verwende Uri.fromFile() nicht. Dadurch wird erzwingt, dass empfangene Apps die Berechtigung READ_EXTERNAL_STORAGE haben. Sie funktioniert überhaupt nicht, wenn Sie versuchen, Inhalte für Nutzer freizugeben. Bei Android-Versionen unter 4.4 (API-Level 19) ist WRITE_EXTERNAL_STORAGE erforderlich. Und sehr wichtige Freigabeziele wie die Gmail App haben das READ_EXTERNAL_STORAGE nicht, wodurch dieser Aufruf fehlschlägt. Stattdessen können Sie mithilfe von URI-Berechtigungen anderen Apps Zugriff auf bestimmte URIs gewähren. URI-Berechtigungen funktionieren zwar nicht für file://-URIs, da sie von Uri.fromFile() generiert werden, funktionieren aber bei URIs, die mit Contentanbietern verknüpft sind. Anstatt eigene zu implementieren, können und sollten Sie FileProvider verwenden, wie unter Dateifreigabe beschrieben.

Datei für die anfragende App freigeben

Wenn Sie die Datei für die angeforderte App freigeben möchten, übergeben Sie den Intent mit dem Inhalts-URI und den Berechtigungen an setResult(). Wenn die soeben definierte Activity abgeschlossen ist, sendet das System die Intent mit dem Inhalts-URI an die Client-App. Im folgenden Code-Snippet sehen Sie, wie das funktioniert:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            ...
            if (fileUri != null) {
                ...
                // Put the Uri and MIME type in the result Intent
                resultIntent.setDataAndType(fileUri, contentResolver.getType(fileUri))
                // Set the result
                setResult(Activity.RESULT_OK, resultIntent)
            } else {
                resultIntent.setDataAndType(null, "")
                setResult(RESULT_CANCELED, resultIntent)
            }
        }
    }

Java

    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Define a listener that responds to clicks on a file in the ListView
        fileListView.setOnItemClickListener(
                new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView,
                    View view,
                    int position,
                    long rowId) {
                ...
                if (fileUri != null) {
                    ...
                    // Put the Uri and MIME type in the result Intent
                    resultIntent.setDataAndType(
                            fileUri,
                            getContentResolver().getType(fileUri));
                    // Set the result
                    MainActivity.this.setResult(Activity.RESULT_OK,
                            resultIntent);
                    } else {
                        resultIntent.setDataAndType(null, "");
                        MainActivity.this.setResult(RESULT_CANCELED,
                                resultIntent);
                    }
                }
        });

Geben Sie Nutzern die Möglichkeit, nach Auswahl einer Datei sofort zur Client-App zurückzukehren. Dazu können Sie z. B. ein Häkchen oder die Schaltfläche Fertig setzen. Verknüpfe über das Attribut android:onClick der Schaltfläche eine Methode mit der Schaltfläche. Rufen Sie in der Methode finish() auf. Beispiele:

Kotlin

    fun onDoneClick(v: View) {
        // Associate a method with the Done button
        finish()
    }

Java

    public void onDoneClick(View v) {
        // Associate a method with the Done button
        finish();
    }

Weitere Informationen finden Sie hier: