پساز اینکه برنامهتان را برای همرسانی فایلها بااستفاده از نشانیهای وب محتوا راهاندازی کردید، میتوانید به درخواستهای برنامههای دیگر برای آن فایلها پاسخ دهید. یکی از روشهای پاسخ دادن به این درخواستها ارائه میانای انتخاب فایل از برنامه سرور است که برنامههای دیگر میتوانند آن را فراخوانی کنند. این رویکرد به برنامه کاربر امکان میدهد به کاربران اجازه دهد فایلی را از برنامه سرور انتخاب کنند و سپس نشانی وب محتوای فایل انتخابشده را دریافت کنند.
این درس نشان میدهد چگونه در برنامهتان Activity انتخاب فایل ایجاد کنید
که به درخواستهای فایل پاسخ میدهد.
دریافت درخواستهای فایل
برای دریافت درخواست فایل از برنامههای کارخواه و پاسخ با نشانی وب محتوا، برنامه شما باید
انتخاب فایل Activity را ارائه دهد. برنامههای مشتری این کار را
Activity با فراخوانی startActivityForResult() با Intent حاوی کنش
ACTION_PICK شروع میکنند. وقتی برنامه کلاینت
startActivityForResult() را فرا میخواند، برنامه شما میتواند
نتیجهای را در قالب نشانی وب محتوا برای فایل انتخابشده کاربر به برنامه کلاینت برگرداند.
برای آشنایی با نحوه پیادهسازی درخواست فایل در برنامه کارخواه، درس درخواست فایل همرسانیشده را ببینید.
ایجاد فعالیت انتخاب فایل
برای راهاندازی انتخاب فایل Activity، ابتدا
Activity را در مانیفست خود، همراه با فیلتر هدفی
که با کنش ACTION_PICK و دستههای
CATEGORY_DEFAULT و
CATEGORY_OPENABLE مطابقت دارد مشخص کنید. همچنین فیلترهای نوع MIME
را برای فایلهایی که برنامهتان به برنامههای دیگر ارائه میدهد اضافه کنید. تکهکد زیر نحوه مشخص کردن
Activity جدید و فیلتر هدف را نشان میدهد:
<manifest xmlns:android="http://schemas.android.com/apk/res/and>roid" ..<. a>pplication ... < activity android:name=".FileSelectActivity" @ android:la>bel="File Se<lector" > inte<nt-filter action android:name>="android.intent<.action.PICK"/ category android>:name="android.i<ntent.category.DEFAULT"/ category > android:name="<android.intent.category.OPEN>ABLE"/ < data android:mimeType=">text/plain"/< > data a<ndroid:mi>meType="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 ima<geFi>les: ArrayFile // Array of filenames corresponding to imageFiles private lateinit var imageFi<lename>s: ArrayString // 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 */ ... } ... }
پاسخ به انتخاب فایل
وقتی کاربری فایل همرسانیشدهای را انتخاب میکند، برنامه شما باید تعیین کند که کدام فایل انتخاب شده است و سپس
«نشانی وب محتوا» را برای فایل تولید کند. ازآنجاییکه Activity فهرست فایلهای دردسترس را در ListView نمایش میدهد، وقتی کاربر روی نام فایلی کلیک میکند، سیستم روش onItemClick() را فرا میخواند که در آن میتوانید فایل انتخابشده را دریافت کنید.
هنگام استفاده از هدف برای ارسال نشانی وب فایل از برنامهای به برنامه دیگر،
باید مراقب باشید نشانی وبی دریافت کنید که برنامههای دیگر بتوانند آن را بخوانند. انجام این کار در دستگاههای دارای Android 6.0 (سطح میانای برنامهسازی کاربردی ۲۳) و بالاتر بهدلیل تغییرات مدل اجازهها در آن نسخه Android، بهویژه تبدیل شدن READ_EXTERNAL_STORAGE به
اجازه خطرناک که برنامه دریافتکننده ممکن است آن را نداشته باشد، نیاز به مراقبت ویژه دارد.
با درنظر گرفتن این ملاحظات، توصیه میکنیم از
Uri.fromFile() که
چندین نقطه ضعف دارد استفاده نکنید. این روش:
- اجازه همرسانی فایل بین نمایهها را نمیدهد.
- لازم است برنامه شما در دستگاههای دارای Android نسخه ۴.۴ (سطح میانای برنامهسازی کاربردی ۱۹) یا پایینتر
WRITE_EXTERNAL_STORAGEاجازه داشته باشد. - برنامههای دریافتکننده باید اجازه
READ_EXTERNAL_STORAGEرا داشته باشند، که در هدفهای همرسانی مهم، مثل Gmail، که این اجازه را ندارند، با خطا مواجه میشود.
بهجای استفاده از Uri.fromFile()،
میتوانید از اجازههای نشانی وب برای اعطای دسترسی به برنامههای دیگر
به نشانیهای وب خاص استفاده کنید. اگرچه اجازههای URI روی
شناسههای URI تولیدشده توسط Uri.fromFile() در file:// کار نمیکند،
اما روی شناسههای URI مرتبط با «ارائهدهندگان محتوا» کار میکند. «میانای برنامهسازی کاربردی»
FileProvider میتواند
به شما کمک کند چنین نشانیهای وب بسازید. این روش برای فایلهایی که در فضای ذخیرهسازی خارجی نیستند، بلکه در فضای ذخیرهسازی محلی برنامه ارسالکننده هدف هستند نیز کار میکند.
در onItemClick()، یک
شیء File برای نام فایل فایل انتخابشده دریافت کنید و آن را بهعنوان آرگومان به
getUriForFile()، همراه با
مرجعی که در
عنصر <provider> برای FileProvider مشخص کردهاید، ارسال کنید.
نشانی وب محتوای حاصل شامل مرجع، بخش مسیری متناظر با
دایرکتوری فایل (همانطور که در فراداده XML مشخص شده است)، و نام فایل بههمراه
پسوند آن است. نحوه FileProvider تخصیص دادن دایرکتوریها به بخشهای مسیر براساس فراداده XML در بخش مشخص کردن دایرکتوریهای همرسانیشدنی توضیح داده شده است.
تکهکد زیر نشان میدهد که چگونه فایل انتخابشده را شناسایی کنید و «نشانی وب محتوا» را برای آن دریافت کنید:
کاتلین
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()); } ... } }); ... }
بهیاد داشته باشید که فقط میتوانید نشانیهای وب محتوا را برای فایلهایی تولید کنید که در دایرکتوریای قرار دارند که
در فایل فراداده حاوی عنصر <paths> مشخص کردهاید، همانطور که در بخش مشخص کردن دایرکتوریهای همرسانیشدنی توضیح داده شده است. اگر برای
File در مسیری که مشخص نکردهاید با
getUriForFile() تماس بگیرید،
IllegalArgumentException دریافت میکنید.
اجازههای فایل را اعطا کنید
اکنون که برای فایلی که میخواهید با برنامه دیگری همرسانی کنید شناسه منبع یکنواخت محتوا دارید، باید
به برنامه کلاینت اجازه دهید به فایل دسترسی داشته باشد. برای اجازه دادن به دسترسی، با افزودن نشانی وب محتوا به 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() برای
نشانی وب محتوای فایل خودداری کنید، زیرا این روش دسترسیای را اعطا میکند که فقط با فراخوانی Context.revokeUriPermission() میتوانید آن را لغو کنید.
از Uri.fromFile() استفاده نکنید. این اجازه برنامههای دریافتکننده را ملزم میکند
اجازه READ_EXTERNAL_STORAGE را داشته باشند،
اگر بخواهید بین کاربران همرسانی کنید اصلاً کار نخواهد کرد، و در نسخههای
Android پایینتر از ۴.۴ (سطح میانای برنامهسازی کاربردی ۱۹)،
برنامهتان باید WRITE_EXTERNAL_STORAGE را داشته باشد.
و هدفهای همرسانی واقعاً مهم، مثل برنامه Gmail،
READ_EXTERNAL_STORAGE را ندارند، که باعث میشود
این تماس ناموفق باشد.
درعوض، میتوانید از اجازههای URI برای اعطای دسترسی به برنامههای دیگر برای URIهای خاص استفاده کنید.
درحالیکه اجازههای URI روی نشانیهای وب file:// که توسط
Uri.fromFile() تولید میشود کار نمیکند،
روی نشانیهای وب مرتبط با «ارائهدهندگان محتوا» کار میکند. بهجای اینکه فقط برای این کار پیادهسازی خودتان را انجام دهید،
میتوانید و باید از FileProvider
همانطور که در همرسانی فایل توضیح داده شده است استفاده کنید.
فایل را با برنامه درخواستکننده همرسانی کنید
برای همرسانی فایل با برنامهای که آن را درخواست کرده است، Intent
حاوی نشانی وب محتوا و اجازهها را به setResult() ارسال کنید. وقتی Activity که تعریف کردهاید تمام شد، سیستم 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) { ... // 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); } } });
به کاربران راهی ارائه دهید تا پساز انتخاب فایل، بلافاصله به برنامه کارخواه برگردند.
یکی از راههای انجام این کار ارائه علامت تأیید یا دکمه انجام شد است. بااستفاده از
مشخصه 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(); }
برای اطلاعات مرتبط بیشتر، به این موارد مراجعه کنید: