هنگامی که برنامه خود را برای اشتراک گذاری فایل ها با استفاده از URI های محتوا تنظیم کردید، می توانید به درخواست های دیگر برنامه ها برای آن فایل ها پاسخ دهید. یکی از راههای پاسخ به این درخواستها، ارائه یک رابط انتخاب فایل از برنامه سرور است که سایر برنامهها میتوانند آن را فراخوانی کنند. این رویکرد به یک برنامه مشتری اجازه می دهد تا به کاربران اجازه دهد فایلی را از برنامه سرور انتخاب کنند و سپس URI محتوای فایل انتخابی را دریافت کنند.
این درس به شما نشان میدهد که چگونه یک Activity
انتخاب فایل در برنامه خود ایجاد کنید که به درخواستها برای فایلها پاسخ میدهد.
دریافت درخواست های فایل
برای دریافت درخواست فایلها از برنامههای سرویس گیرنده و پاسخ دادن با URI محتوا، برنامه شما باید یک Activity
انتخاب فایل ارائه دهد. برنامه های سرویس گیرنده این Activity
با فراخوانی startActivityForResult()
با یک Intent
حاوی عمل ACTION_PICK
شروع می کنند. هنگامی که برنامه مشتری startActivityForResult()
فراخوانی میکند، برنامه شما میتواند نتیجهای را در قالب یک URI محتوا برای فایلی که کاربر انتخاب کرده است، به برنامه مشتری بازگرداند.
برای یادگیری نحوه اجرای درخواست برای یک فایل در برنامه مشتری، به درس درخواست فایل اشتراکی مراجعه کنید.
ایجاد یک فعالیت انتخاب فایل
برای تنظیم Activity
انتخاب فایل، با مشخص کردن Activity
در مانیفست خود، همراه با یک فیلتر هدف که با عملکرد ACTION_PICK
و دستههای CATEGORY_DEFAULT
و CATEGORY_OPENABLE
مطابقت دارد، شروع کنید. همچنین فیلترهای نوع MIME را برای فایلهایی که برنامهتان ارائه میکند به برنامههای دیگر اضافه کنید. قطعه زیر به شما نشان می دهد که چگونه فیلتر Activity
و intent جدید را مشخص کنید:
<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>
فعالیت انتخاب فایل را در کد تعریف کنید
در مرحله بعد، یک زیر کلاس Activity
تعریف کنید که فایلهای موجود از files/images/
برنامه شما را در حافظه داخلی نمایش میدهد و به کاربر اجازه میدهد فایل مورد نظر را انتخاب کند. قطعه زیر نحوه تعریف این Activity
و پاسخ به انتخاب کاربر را نشان می دهد:
کاتلین
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 */ ... } ... }
جاوا
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 */ ... } ... }
به انتخاب فایل پاسخ دهید
هنگامی که کاربر یک فایل به اشتراک گذاشته شده را انتخاب کرد، برنامه شما باید مشخص کند که چه فایلی انتخاب شده است و سپس یک URI محتوا برای فایل ایجاد کند. از آنجایی که Activity
لیست فایلهای موجود را در ListView
نمایش میدهد، وقتی کاربر روی نام فایل کلیک میکند، سیستم متد onItemClick()
را فرا میخواند که در آن میتوانید فایل انتخابی را دریافت کنید.
هنگام استفاده از intent برای ارسال URI یک فایل از یک برنامه به برنامه دیگر، باید مراقب باشید که یک URI دریافت کنید که سایر برنامه ها بتوانند آن را بخوانند. انجام این کار در دستگاههای دارای Android 6.0 (سطح API 23) و نسخههای بعدی به مراقبت ویژه نیاز دارد زیرا تغییراتی در مدل مجوزها در آن نسخه از Android ایجاد شده است، بهویژه اینکه READ_EXTERNAL_STORAGE
به یک مجوز خطرناک تبدیل شده است که ممکن است برنامه دریافتکننده فاقد آن باشد.
با در نظر گرفتن این ملاحظات، توصیه میکنیم از استفاده از Uri.fromFile()
که دارای چندین اشکال است اجتناب کنید. این روش:
- به اشتراک گذاری فایل در نمایه ها اجازه نمی دهد.
- نیاز دارد که برنامه شما دارای مجوز
WRITE_EXTERNAL_STORAGE
در دستگاههای دارای Android نسخه 4.4 (سطح API 19) یا پایینتر باشد. - مستلزم آن است که برنامه های دریافت کننده مجوز
READ_EXTERNAL_STORAGE
را داشته باشند، که در اهداف اشتراک گذاری مهم مانند Gmail که این مجوز را ندارند، با شکست مواجه می شوند.
به جای استفاده از Uri.fromFile()
، میتوانید از مجوزهای URI برای دسترسی سایر برنامهها به URIهای خاص استفاده کنید. در حالی که مجوزهای URI روی URI های file://
تولید شده توسط Uri.fromFile()
کار نمی کنند، اما روی URI های مرتبط با ارائه دهندگان محتوا کار می کنند. FileProvider
API می تواند به شما در ایجاد چنین URI کمک کند. این رویکرد همچنین با فایلهایی کار میکند که در حافظه خارجی نیستند، بلکه در حافظه محلی برنامه ارسال کننده قصد هستند.
در onItemClick()
یک شی File
برای نام فایل فایل انتخابی دریافت کنید و آن را به عنوان آرگومان به getUriForFile()
ارسال کنید، همراه با مرجعی که در عنصر <provider>
برای FileProvider
مشخص کرده اید. URI محتوای حاصله شامل اعتبار، یک بخش مسیر مربوط به فهرست فایل (همانطور که در فراداده XML مشخص شده است) و نام فایل شامل پسوند آن است. چگونه FileProvider
دایرکتوریها را به بخشهای مسیر بر اساس فراداده XML نگاشت میکند در بخش تعیین فهرستهای قابل اشتراکگذاری توضیح داده شده است.
قطعه زیر به شما نشان می دهد که چگونه فایل انتخاب شده را شناسایی کنید و یک URI محتوا برای آن دریافت کنید:
کاتلین
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 } ... } ... }
جاوا
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()); } ... } }); ... }
به یاد داشته باشید که فقط میتوانید URI محتوا برای فایلهایی تولید کنید که در فهرستی قرار دارند که در فایل فرادادهای که حاوی عنصر <paths>
است، مشخص شدهاند، همانطور که در بخش تعیین فهرستهای قابل اشتراکگذاری توضیح داده شد. اگر getUriForFile()
برای File
در مسیری که مشخص نکرده اید فراخوانی کنید، یک IllegalArgumentException
دریافت خواهید کرد.
اعطای مجوز برای فایل
اکنون که یک URI محتوا برای فایلی دارید که میخواهید با برنامه دیگری به اشتراک بگذارید، باید به برنامه مشتری اجازه دسترسی به فایل را بدهید. برای اجازه دسترسی، با افزودن URI محتوا به Intent
و سپس تنظیم پرچمهای مجوز روی Intent
، به برنامه مشتری مجوز بدهید. مجوزهایی که اعطا می کنید موقتی هستند و با پایان یافتن پشته وظایف برنامه دریافت کننده به طور خودکار منقضی می شوند.
قطعه کد زیر نحوه تنظیم مجوز خواندن برای فایل را به شما نشان می دهد:
کاتلین
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) ... } ... } ... }
جاوا
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); } ... } ... }); ... }
احتیاط: فراخوانی setFlags()
تنها راه برای اعطای دسترسی ایمن به فایل های شما با استفاده از مجوزهای دسترسی موقت است. از فراخوانی متد Context.grantUriPermission()
برای URI محتوای فایل خودداری کنید، زیرا این روش به شما اجازه دسترسی می دهد که فقط با فراخوانی Context.revokeUriPermission()
می توانید آن را لغو کنید.
از Uri.fromFile()
استفاده نکنید. این برنامههای دریافتکننده را مجبور میکند مجوز READ_EXTERNAL_STORAGE
را داشته باشند، اگر میخواهید بین کاربران اشتراکگذاری کنید، به هیچ وجه کار نمیکند، و در نسخههای Android پایینتر از 4.4 (سطح API 19)، برنامه شما نیاز به داشتن WRITE_EXTERNAL_STORAGE
دارد. و اهداف اشتراکگذاری واقعاً مهم، مانند برنامه Gmail، READ_EXTERNAL_STORAGE
ندارند، که باعث میشود این تماس با شکست مواجه شود. درعوض، میتوانید از مجوزهای URI برای دسترسی سایر برنامهها به URIهای خاص استفاده کنید. در حالی که مجوزهای URI همانطور که توسط Uri.fromFile()
ایجاد میشود، روی URIهای file:// کار نمیکنند، اما روی URIهای مرتبط با ارائهدهندگان محتوا کار میکنند. به جای اجرای برنامه خود فقط برای این، می توانید و باید از FileProvider
همانطور که در اشتراک گذاری فایل توضیح داده شده است استفاده کنید.
فایل را با برنامه درخواست کننده به اشتراک بگذارید
برای اشتراکگذاری فایل با برنامهای که آن را درخواست کرده است، Intent
حاوی URI محتوا و مجوزها را به setResult()
ارسال کنید. هنگامی که Activity
که به تازگی تعریف کرده اید به پایان رسید، سیستم Intent
حاوی URI محتوا را به برنامه مشتری ارسال می کند. قطعه کد زیر نحوه انجام این کار را به شما نشان می دهد:
کاتلین
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) } } }
جاوا
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); } } });
راهی را در اختیار کاربران قرار دهید که پس از انتخاب فایل، فوراً به برنامه مشتری بازگردند. یکی از راههای انجام این کار، ارائه یک تیک یا دکمه Done است. یک روش را با استفاده از ویژگی android:onClick
دکمه با دکمه مرتبط کنید. در متد، finish()
فراخوانی کنید. به عنوان مثال:
کاتلین
fun onDoneClick(v: View) { // Associate a method with the Done button finish() }
جاوا
public void onDoneClick(View v) { // Associate a method with the Done button finish(); }
برای اطلاعات بیشتر مرتبط به این موضوع مراجعه کنید: