Dosya paylaşma

Uygulamanızı içerik URI'leri kullanarak dosya paylaşacak şekilde ayarladıktan sonra, diğer uygulamaların bu dosyalar için yaptığı isteklere yanıt verebilirsiniz. Bu isteklere yanıt vermenin bir yolu, sunucu uygulamasından diğer uygulamaların çağırabileceği bir dosya seçimi arayüzü sağlamaktır. Bu yaklaşım, bir istemci uygulamasının, kullanıcıların sunucu uygulamasından bir dosya seçmesine ve ardından seçilen dosyanın içerik URI'sini almasına izin vermesine olanak tanır.

Bu derste, uygulamanızda dosya isteklerine yanıt veren bir dosya seçimini Activity nasıl oluşturacağınız gösterilmektedir.

Dosya istekleri alma

İstemci uygulamalarından dosya isteği almak ve içerik URI'si ile yanıt vermek için uygulamanızın bir dosya seçimi Activity sağlaması gerekir. İstemci uygulamaları bu Activity işlemini, ACTION_PICK işlemini içeren bir Intent ile startActivityForResult() çağırarak başlatır. İstemci uygulaması startActivityForResult() işlevini çağırdığında, uygulamanız istemci uygulamasına kullanıcının seçtiği dosya için içerik URI'si biçiminde bir sonuç döndürebilir.

Bir istemci uygulamasında bir dosyaya yönelik isteğin nasıl uygulanacağını öğrenmek için Paylaşılan dosya isteğinde bulunma dersine bakın.

Dosya seçimi etkinliği oluşturma

Activity dosya seçimini ayarlamak için işe manifest dosyanızda Activity özelliğini ve ACTION_PICK işlemiyle ve CATEGORY_DEFAULT ile CATEGORY_OPENABLE kategorileriyle eşleşen bir amaç filtresi belirterek başlayın. Ayrıca, uygulamanızın diğer uygulamalara sunduğu dosyalar için MIME türü filtreleri ekleyin. Aşağıdaki snippet'te yeni Activity ve intent filtresinin nasıl belirtileceği gösterilmektedir:

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

Dosya seçimi etkinliğini kodda tanımlama

Ardından, uygulamanızın dahili depolama alanındaki files/images/ dizininde bulunan dosyaları görüntüleyen ve kullanıcının istenen dosyayı seçmesine olanak tanıyan bir Activity alt sınıfı tanımlayın. Aşağıdaki snippet'te bu Activity öğesinin nasıl tanımlanacağı ve kullanıcının seçimine nasıl yanıt verileceği gösterilmektedir:

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

Dosya seçimine yanıt verme

Kullanıcı paylaşılan bir dosyayı seçtiğinde, uygulamanız hangi dosyanın seçildiğini belirlemeli ve ardından dosya için bir içerik URI'si oluşturmalıdır. Activity, ListView içinde kullanılabilir dosyaların listesini görüntülediğinden, kullanıcı dosya adını tıkladığında sistem onItemClick() yöntemini çağırır. Bu yöntemde seçilen dosyayı alabilirsiniz.

Bir dosyanın URI'sını bir uygulamadan diğerine göndermek için intent kullanırken diğer uygulamaların okuyabileceği bir URI almaya dikkat etmelisiniz. Android 6.0 (API düzeyi 23) ve sonraki sürümleri çalıştıran cihazlarda bu işlemi yapmak, Android'in ilgili sürümündeki izin modelinde yapılan değişiklikler, özellikle de READ_EXTERNAL_STORAGE tehlikeli izin haline gelmesinden ve alıcı uygulamanın sahip olmayabileceğinden özel dikkat gerektirir.

Bu hususları göz önünde bulundurarak, çeşitli dezavantajları olan Uri.fromFile() kullanmaktan kaçınmanızı öneririz. Bu yöntem:

  • Profiller arasında dosya paylaşımına izin vermez.
  • Uygulamanızın, Android 4.4 (API düzeyi 19) veya önceki sürümleri çalıştıran cihazlarda WRITE_EXTERNAL_STORAGE iznine sahip olmasını gerektirir.
  • Alıcı uygulamaların READ_EXTERNAL_STORAGE iznine sahip olmasını gerektirir. Bu izin, Gmail gibi önemli paylaşım hedeflerinde başarısız olur.

Uri.fromFile() kullanmak yerine, diğer uygulamalara belirli URI'lara erişim izni vermek için URI izinlerini kullanabilirsiniz. URI izinleri Uri.fromFile() tarafından oluşturulan file:// URI'larında çalışmasa da İçerik Sağlayıcılarla ilişkilendirilmiş URI'ler üzerinde çalışır. FileProvider API, bu tür URI'ler oluşturmanıza yardımcı olabilir. Bu yaklaşım, harici depolama alanında olmayan, amacı gönderen uygulamanın yerel depolama alanındaki dosyalarla da çalışır.

onItemClick() ürününde, seçilen dosyanın dosya adı için bir File nesnesi alın ve bunu getUriForFile() öğesine, FileProvider için <provider> öğesinde belirttiğiniz yetkiyle birlikte bağımsız değişken olarak iletin. Ortaya çıkan içerik URI'si; yetkiyi, dosyanın dizinine karşılık gelen bir yol segmentini (XML meta verisinde belirtildiği gibi) ve dosyanın uzantısını içeren adını içerir. FileProvider ürününün XML meta verilerine göre dizinleri yol segmentleriyle nasıl eşlediği, Paylaşılabilir dizinleri belirtme bölümünde açıklanmıştır.

Aşağıdaki snippet'te, seçilen dosyanın nasıl algılanacağı ve bunun için nasıl içerik URI'sı alınacağı gösterilmektedir:

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

Paylaşılabilir dizinleri belirtme bölümünde açıklandığı gibi, yalnızca <paths> öğesini içeren meta veri dosyasında belirttiğiniz bir dizinde bulunan dosyalar için içerik URI'leri oluşturabileceğinizi unutmayın. Belirtmediğiniz bir yolda File için getUriForFile() çağrısı yaparsanız IllegalArgumentException alırsınız.

Dosya için izin verme

Artık başka bir uygulamayla paylaşmak istediğiniz dosyanın içerik URI'sına sahip olduğunuza göre, istemci uygulamanın dosyaya erişmesine izin vermeniz gerekir. Erişime izin vermek için içerik URI'sını bir Intent öğesine ekleyip ardından Intent üzerinde izin işaretlerini ayarlayarak istemci uygulamasına izinler verin. Verdiğiniz izinler geçicidir ve alıcı uygulamanın görev yığını tamamlandığında bu izinler otomatik olarak sona erer.

Aşağıdaki kod snippet'inde dosya için okuma izninin nasıl ayarlanacağı gösterilmektedir:

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

Dikkat: Geçici erişim izinlerini kullanarak dosyalarınıza güvenli bir şekilde erişim izni vermenin tek yolu setFlags() işlevini çağırmaktır. Bir dosyanın içerik URI'si için Context.grantUriPermission() yöntemini çağırmaktan kaçının. Bu yöntem, yalnızca Context.revokeUriPermission() yöntemini çağırarak iptal edebileceğiniz erişim sağlar.

Uri.fromFile() kullanmayın. Bu politika, uygulamaların alınmasını READ_EXTERNAL_STORAGE iznini almaya zorlar, kullanıcılar arasında paylaşımda bulunmaya çalışıyorsanız hiç çalışmaz ve Android'in 4.4'ten (API düzeyi 19) önceki sürümlerinde uygulamanızın WRITE_EXTERNAL_STORAGE iznini gerektirir. Gmail uygulaması gibi gerçekten önemli paylaşım hedeflerinde READ_EXTERNAL_STORAGE bulunmadığından bu çağrı başarısız olur. Bunun yerine, diğer uygulamalara belirli URI'lara erişim izni vermek için URI izinlerini kullanabilirsiniz. URI izinleri Uri.fromFile() tarafından oluşturulan file:// URI'larında çalışmasa da İçerik Sağlayıcılarla ilişkilendirilmiş Uris üzerinde çalışır. Kendi komutlarınızı sadece bunun için uygulamak yerine, Dosya paylaşımı bölümünde açıklandığı gibi FileProvider kullanabilirsiniz ve kullanmalısınız.

Dosyayı, istekte bulunan uygulamayla paylaşın

Dosyayı, istekte bulunan uygulamayla paylaşmak için içerik URI'sini ve izinleri içeren Intent öğesini setResult() öğesine iletin. Az önce tanımladığınız Activity tamamlandığında, sistem içerik URI'sini içeren Intent öğesini istemci uygulamaya gönderir. Aşağıdaki kod snippet'inde bunu nasıl yapacağınız gösterilmektedir:

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

Kullanıcılara bir dosya seçtiklerinde istemci uygulamasına hemen geri dönebilmeleri için bir yol sağlayın. Bunu yapmanın bir yolu onay işareti veya Bitti düğmesi sağlamaktır. Düğmenin android:onClick özelliğini kullanarak düğmeyle bir yöntemi ilişkilendirin. Yöntemde finish() yöntemini çağırın. Örneğin:

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

İlgili daha fazla bilgi için aşağıdaki konulara bakın: