Udostępnianie pliku

Po skonfigurowaniu w aplikacji udostępniania plików za pomocą identyfikatorów URI treści możesz odpowiadać żądania dostępu do tych plików. Jednym ze sposobów odpowiedzi na te prośby jest wskazanie plików do wyboru z aplikacji serwera, który mogą wywoływać inne aplikacje. Takie podejście pozwala klientowi pozwala użytkownikom wybrać plik w aplikacji serwera i odebrać ten plik identyfikator URI treści.

Z tej lekcji dowiesz się, jak wybrać plik Activity w Twojej aplikacji która odpowiada na prośby o dostęp do plików.

Otrzymuj prośby o pliki

Aby otrzymywać żądania plików z aplikacji klienckich i odpowiadać za pomocą identyfikatora URI treści, aplikacja powinna wybierz plik Activity. Uruchamianie aplikacji klienckich Activity, wywołując metodę startActivityForResult() za pomocą elementu Intent zawierającego działanie ACTION_PICK Gdy aplikacja klienta zadzwoni startActivityForResult(), aplikacja może zwraca wynik do aplikacji klienckiej w postaci identyfikatora URI treści dla wybranego przez użytkownika pliku.

Aby dowiedzieć się, jak wdrożyć żądanie dotyczące pliku w aplikacji klienckiej, zapoznaj się z tym wykładem. Wysyłanie prośby o udostępnienie pliku

Aktywność dotycząca tworzenia wyboru plików

Aby skonfigurować wybór pliku Activity, zacznij od określenia Activity w pliku manifestu wraz z filtrem intencji pasujące do działania ACTION_PICK oraz kategorie CATEGORY_DEFAULT i CATEGORY_OPENABLE. Dodaj też filtry typów MIME dotyczące plików, które Twoja aplikacja udostępnia innym aplikacjom. Fragment kodu poniżej pokazuje, jak określić nowy element Activity i filtr intencji:

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

Zdefiniuj aktywność związaną z wyborem pliku w kodzie

Następnie zdefiniuj podklasę Activity, która będzie wyświetlać pliki dostępne z jest znajdujący się w katalogu files/images/ aplikacji w pamięci wewnętrznej i umożliwia użytkownikowi wybranie odpowiedni plik. Fragment kodu poniżej pokazuje, jak to definiować Activity i odpowiedz na wybór użytkownika:

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
         */
         ...
    }
    ...
}

Reagowanie na wybór pliku

Gdy użytkownik wybierze udostępniony plik, aplikacja musi określić, który plik został wybrany. a następnie wygeneruj identyfikator URI treści dla pliku. Ponieważ Activity wyświetla lista plików dostępnych w elemencie ListView, gdy użytkownik kliknie nazwę pliku. system wywołuje metodę onItemClick(), w której można pobrać wybrany plik.

Gdy używasz intencji przesłania identyfikatora URI pliku z jednej aplikacji do drugiej: musisz więc zadbać o to, aby uzyskać identyfikator URI które aplikacje mogą odczytać. Dotyczy to urządzeń z Androidem 6.0 (poziom interfejsu API 23) lub nowszym wymaga specjalnych ze względu na zmiany w modelu uprawnień w tej wersji Androida, READ_EXTERNAL_STORAGE – staje się niebezpieczne uprawnienia, których może brakować aplikacja odbierająca.

Mając to na uwadze, zalecamy unikanie stosowania Uri.fromFile(), która ma kilka wad. W ten sposób:

  • Nie można udostępniać plików między profilami.
  • Aplikacja musi mieć WRITE_EXTERNAL_STORAGE na urządzeniach z Androidem 4.4 (poziom interfejsu API 19) lub starszym.
  • Wymaga, aby aplikacje odbierające miały READ_EXTERNAL_STORAGE, które nie uda się zrealizować ważnych celów udostępniania, takich jak Gmail, które nie mają tych uprawnień.

Zamiast Uri.fromFile(), możesz używać uprawnień URI, aby przyznawać innym aplikacjom dostęp do określonych identyfikatorów URI. Uprawnienia do identyfikatora URI nie działają w przypadku file:// identyfikatorów URI wygenerowanych przez: Uri.fromFile(). działają na identyfikatorach URI powiązanych z dostawcami treści. FileProvider interfejs API może ułatwiają tworzenie takich identyfikatorów URI. Ta metoda działa też w przypadku plików, które nie są plikami w pamięci zewnętrznej, ale w pamięci lokalnej aplikacji wysyłającej intencję.

W aplikacji onItemClick() uzyskasz: File obiektu o nazwie wybranego pliku i przekazać go jako argument w getUriForFile() oraz organ nadzorczy określony w <provider> dla elementu FileProvider. Powstały identyfikator URI treści zawiera urząd, segment ścieżki odpowiadający atrybutowi (jak określono w metadanych XML) oraz nazwę pliku wraz z jego nazwą . Jak FileProvider mapuje katalogi na ścieżkę oparte na metadanych XML zostały opisane w sekcji Określ katalogi do udostępniania.

Ten fragment kodu pozwala wykryć wybrany plik i uzyskać dla niego identyfikator URI treści:

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

Pamiętaj, że identyfikatory URI treści możesz generować tylko w przypadku plików, które znajdują się w katalogu określony w pliku metadanych zawierającym element <paths>, opisane w sekcji Wskazywanie katalogów do udostępniania. Jeśli dzwonisz getUriForFile() za File w nieokreślonej ścieżce, otrzymasz żądanie IllegalArgumentException

Przyznaj uprawnienia do pliku

Teraz gdy masz już identyfikator URI treści pliku, który chcesz udostępnić innej aplikacji, zezwól aplikacji klienckiej na dostęp do pliku. Aby zezwolić na dostęp, przyznaj uprawnienia aplikacji klienckiej przez dodanie identyfikatora URI treści do Intent, a potem ustawienie flag uprawnień na Intent. Przyznane uprawnienia są tymczasowe i wygasają automatycznie po zakończeniu stosu zadań aplikacji odbierającej.

Ten fragment kodu pokazuje, jak ustawić uprawnienia do odczytu pliku:

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

Uwaga: połączenie pod numer setFlags() to jedyna opcja to sposób na bezpieczny dostęp do plików za pomocą tymczasowych uprawnień dostępu. Nie dzwoń Metoda Context.grantUriPermission() dla identyfikatora URI zawartości pliku, ponieważ ta metoda przyznaje dostęp, który można unieważnić tylko Dzwonię pod numer Context.revokeUriPermission().

Nie używaj Uri.fromFile(). Wymusza odbieranie aplikacji mieć uprawnienie READ_EXTERNAL_STORAGE, nie będzie w ogóle działać przy udostępnianiu między różnymi użytkownikami i w różnych wersjach Androida w wersji starszej niż 4.4 (poziom interfejsu API 19) wymagałoby musi mieć WRITE_EXTERNAL_STORAGE. W przypadku bardzo ważnych celów udostępniania, takich jak aplikacja Gmail, READ_EXTERNAL_STORAGE, powodując aby zakończyć się niepowodzeniem. Zamiast tego możesz korzystać z uprawnień URI, aby przyznawać innym aplikacjom dostęp do określonych identyfikatorów URI. Chociaż uprawnienia do identyfikatora URI nie działają w przypadku identyfikatorów URI file://, ponieważ są generowane przez Uri.fromFile() – tak pracowaliśmy nad identyfikatorem URI powiązanym z dostawcami treści. Zamiast wdrażać własne rozwiązania, możesz i powinien używać usługi FileProvider jak wyjaśniliśmy w artykule Udostępnianie plików.

Udostępnij plik aplikacji, która wysłała prośbę

Aby udostępnić plik aplikacji, która go zażądała, przekaż Intent zawierający identyfikator URI treści i uprawnienia do setResult(). Gdy zdefiniowany właśnie Activity dobiegnie końca, system wysyła do aplikacji klienckiej identyfikator Intent zawierający identyfikator URI treści. Fragment kodu, który pokazuje, jak to zrobić:

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

Zapewnij użytkownikom możliwość natychmiastowego powrotu do aplikacji klienckiej po wybraniu pliku. Możesz to zrobić na przykład przez zaznaczenie znacznika wyboru lub przycisku Gotowe. Powiąż metodę z za pomocą przycisku android:onClick. W metodzie wywołaj finish() Na przykład:

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

Dodatkowe informacje znajdziesz tutaj: