Uygulamanızı içerik URI'lerini kullanarak dosya paylaşacak şekilde ayarladıktan sonra, diğer uygulamaların bu dosyalar için isteklerine 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ü sunmaktı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 birlikte startActivityForResult()
işlevini ç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.
İstemci uygulamasında bir dosya isteğini nasıl uygulayacağınızı öğrenmek için Paylaşılan dosya isteğinde bulunma dersini inceleyin.
Dosya seçimi etkinliği oluşturma
Activity
dosya seçimini ayarlamak için öncelikle manifest dosyanızda Activity
bilgisini, ACTION_PICK
işlemi ve CATEGORY_DEFAULT
ile CATEGORY_OPENABLE
kategorileriyle eşleşen bir intent 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 filtresini nasıl belirteceğiniz 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>
Kodda dosya seçimi etkinliğini tanımlayın
Daha sonra, uygulamanızın files/images/
dizininden dahili depolamada 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, bu Activity
öğesini nasıl tanımlayacağınızı ve kullanıcının seçimine nasıl yanıt vereceğinizi gösterir:
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
Bir kullanıcı paylaşılan bir dosyayı seçtiğinde, uygulamanızın hangi dosyanın seçildiğini belirlemesi ve ardından dosya için içerik URI'si oluşturması gerekir. Activity
, bir ListView
içindeki kullanılabilir dosyaların listesini gösterdiğinden, kullanıcı bir dosya adını tıkladığında sistem, seçilen dosyayı alabileceğiniz onItemClick()
yöntemini çağırır.
Bir dosyanın URI'sini bir uygulamadan diğerine göndermek için niyet kullanırken diğer uygulamaların okuyabileceği bir URI almaya dikkat etmeniz gerekir. Android 6.0 (API düzeyi 23) ve sonraki sürümleri çalıştıran cihazlarda bu işlemi yapmak,
Android'in bu sürümünde izin modelinde yapılan değişiklikler (özellikle de READ_EXTERNAL_STORAGE
uygulamasının sahip olmadığı
tehlikeli bir izne dönüşmesi)
gerekir.
Bu noktaları göz önünde bulundurarak, bazı dezavantajlara neden olan Uri.fromFile()
kullanmaktan kaçınmanızı öneririz. Bu yöntem:
- Profiller arasında dosya paylaşımına izin vermez.
- Android 4.4 (API düzeyi 19) veya önceki sürümleri çalıştıran cihazlarda uygulamanızın
WRITE_EXTERNAL_STORAGE
iznine sahip olmasını gerektirir. - Alıcı uygulamaların
READ_EXTERNAL_STORAGE
iznine sahip olması gerekir. Bu izne sahip olmayan önemli paylaşım hedeflerinde (ör. Gmail) başarısız olur.
Diğer uygulamaların belirli URI'lara erişmesine izin vermek için Uri.fromFile()
kullanmak yerine URI izinlerini kullanabilirsiniz. URI izinleri Uri.fromFile()
tarafından oluşturulan file://
URI'larında çalışmasa da İçerik Sağlayıcılarla ilişkilendirilen URI'lerde ç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 dosyalar için de geçerlidir.
onItemClick()
ürününde, seçilen dosyanın dosya adı için bir File
nesnesi alın ve bunu, FileProvider
için <provider>
öğesinde belirttiğiniz yetkiliyle birlikte getUriForFile()
öğesine bağımsız değişken olarak iletin.
Ortaya çıkan içerik URI'si yetkiliyi, dosyanın dizinine karşılık gelen bir yol segmentini (XML meta verilerinde 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 dosya için içerik URI'sinin nasıl 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 yoldaki File
için getUriForFile()
çağırırsanı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'sine sahip olduğunuza göre, istemci uygulamasının dosyaya erişmesine izin vermeniz gerekir. Erişime izin vermek için içerik URI'sını bir Intent
öğesine ekleyip 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
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()
numaralı telefonu aramaktı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 iznini verir.
Uri.fromFile()
kullanmayın. Bu yaklaşım, uygulamaların alınmasını zorunlu kılar, READ_EXTERNAL_STORAGE
iznini almaya çalışır, kullanıcılar arasında paylaşımda bulunmaya çalışıyorsanız hiçbir şekilde çalışmaz ve Android'in 4.4 (API düzeyi 19) öncesi sürümlerinde (API düzeyi 19) uygulamanızın WRITE_EXTERNAL_STORAGE
içermesi gerekir.
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 uygulamaların belirli URI'lara erişmesine izin 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'te çalışır. Yalnızca bunun için kendi aracınızı uygulamak yerine, Dosya paylaşımı bölümünde açıklandığı gibi FileProvider
aracını 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'sını içeren Intent
öğesini istemci uygulamasına gönderir. Aşağıdaki kod snippet'i, bunu nasıl yapacağınızı gösterir:
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, dosya seçtiklerinde istemci uygulamasına hemen dönmeleri 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 bir yöntemi düğmeyle ilişkilendirin. Yöntemde finish()
çağrısı yapın. Örnek:
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(); }
Konuyla ilgili daha fazla bilgi için aşağıdaki konulara bakın: