دوستان سلام امیدوارم این مقاله مفید واقع باشه برای شما و پیشاپیش این نکته مهم رو ذکر کنم اگر مطالبی از این مقاله مشکلی دارد در کامنتها برام بنویسید.
یک ارائه دهنده محتوا یا Content Provider وظیفه ارائه اطلاعات از یک برنامه به برنامه های دیگر، هنگام درخواست آنها را به عهده دارد. چنین درخواست هایی به وسیله متد های موجود در کلاس ContentResolver مدیریت می شود. یک ارائه دهنده محتوا، می تواند از راههای مختلفی برای ذخیره سازی اطلاعات خود استفاده کند این به این معنی است که این اطلاعات می تواند در دیتابیس یا فایل یا حتی روی شبکه ذخیره شود.

ارائه دهنده محتوا یا همون Content Provider به شما این اجازه را می دهد که محتوای خود را در یک جا جمع کنید و تعداد زیادی اپلیکیشن مختلف داشته باشید که زمانی که به این محتوا نیاز داشتند، به آن دسترسی پیدا کنند. یک ارائه دهنده محتوا بسیار شبیه به یک دیتابیس رفتار می کند، یعنی شما می توانید به آن درخواست دریافت یا ویرایش اطلاعات آن را بدهید و آن مانند متد های ()insert(), update(), delete, و ()query اطلاعات را اضافه یا حذف یا اراده می دهد. در اغلب موارد، این اطلاعات در دیتابیس SQLite ذخیره شده است.
به عنوان مثال متد ()query : این متد یک درخواست از یک مشتری دریافت می کند و نتیجه را به عنوان شیء Cursor بازمی گرداند.
در اندروید از Content Provider ها برای اشتراک داده ها بین برنامه های مختلف استفاده می شود. Content Provider ها را همانند یک منبع داده (Data Store) تصور کنید که داده های ذخیره شد در ان به برنامه که از آن استفاده می کند وابسته نمی باشد. و این مهم است که چگونه برنامه ها می تواند با استفاده از یک رابط برنامه سازگار به ان دسترسی داشته باشند.content Provider ها بسیار شبیه دیتابیس عمل می کنند شما می توانید از Query بگیرید آن را ویرایشو حذف- و به روز رسانی کنید. بر خلاف دیتابیس ها Content Provider ها را های زیادی برای ذخیر ه داده هایشان دارند مانند ذخیره در فایل ها د ر دیتابیس و حتا ذخیره در شبکه.
اندروید(Android ships) Content Provider ها مفیدی را فراهم می کند از جمله موارد زیر :
در کنار بسیاری از content Provider ها پیش ساخته که در پکیج android.provider هستند شما می توانید Content Provider خوتان را همایجاد کنید
برای query گرفتن از Content Provider ها شما می توان دستورات queryخود را در یک URI, تعیین کنید..
قالبب URI باید به صورت زیر باشد
<prefix>://<authority>/<data_type>/<id>
</id></data_type></authority></prefix>
(پیشوند) Prefix
این بخش همیشه به صورت content:// تنظیم می شود.
(منبع) Authority
این بخش نام تامین کنندهی محتوا را تعیین میکند. برای مثال contacts، browser و غیره.
(نوع داده) data_type
یک فایل جاوای جدیداین بخش نشان دهندهی نوع داده ای است که این تامین کنندهی محتوای خاص ایجاد میکند. برای مثال، اگر شما همهی شماره تماس ها را از تامین کننده محتوای Contacts دریافت کنید، آنگاه مسیر داده people خواهد بود و URI آن شبیه به این خواهد بود:
content://contacts/people
(شناسه) id
این بخش رکورد خاص مورد تقاضا را مشخص می کند. برای مثال، اگر شما شماره تماس نفر 5 ام را از تامین کننده محتوا درخواست کنید، آنگاه URI شبیه به این خواهد شد:
content://contacts/people/5
در جدول زیر مثال های مشاهده می کنید

در کنار دستورات Query همراه URI, شما می توانید Query های ثابت که از قبل تعریف شده است استفاده کنید به عنوان مثال شما می توانید به جای URI زیر
Uri allContacts = Uri.parse(“content://contacts/people”);
شما می توانید از دستور زیر استفاده کنید
Uri allContacts = ContactsContract.Contacts.CONTENT_URI;
در کنار بسیاری از content Provider ها پیش ساخته که در پکیج android.provider هستند شما می توانید Content Provider خودتان را هم ایجاد کنید.
خب حالا ببینیم که چطور میتوانیم یه content provider برای خودمون ایجاد کنیم:
مراحل ایجاد به صورت زیر است:
اول از همه، شما باید یک کلاس Content Provider را ایجاد کنید که از کلاس ContentProvider اکستند (extend) شده.
در گام دوم، شما باید آدرس UIR مربوط به تامین کننده محتوای خود را تعریف کنید که برای دسترسی به محتوا مورد استفاده قرار می گیرد.
سپس، شما باید پایگاه دادهی خود را برای ذخیره ی محتوا ایجاد کنید. .در داخل Content Provider شما آزاد هستید که تصمیم بگیرید که به چه طریق می خواهید داده های خود را ذخیره کنید در یک فایل , د ر فایل xml یا در یک دیتابیس
پس از آن شما باید پرس و جو های Content Provider را برای اجرای عملیات مختلف مربوط به پایگاه داده، پیاده سازی کنید.
در نهایت، باید با استفاده از تگ <provider > ک Content Provider خود را در فایل manifest پروژه ثبت کنید.
بهترین را ه برای فهمیدن content Provider ها استفاده از آن به صورت عملی می باشد.
حالا اگه میخوایید که نمونه کد دیتابیس برای توضیحات بالا ببینید این لینک رو نگاهی بندازید.چون باعث طولانی شدن مقاله میشد من اینجا کپی نکردم.
https://code.tutsplus.com/tutorials/android-fundamentals-working-with-content-providers--mobile-5549
خب تا اینجا ما با مفهوم Content Provider ها آشنا شدیم اینکه چی هستند و اصلا برای چی استفاده میشوند.
حالا من از تجربه خودم با کارکردن content provider ها و توضیحاتی در مورد یک چالشی که اخیرا یکی پروژه هام برخوردم و بعد از کلی جستجو متوجه شدم رو براتون میگم.
از من خواستن که از دوربین یوزر بتونه عکس بگیره و در ایمیج ویو نشون بده.
اپ روی api level 24 هیچ مشکلی نداشت و میتونستم uri مربوط به عکس رو از activity result اکتیویتی بگیرم و نشون بدم توی ایمیج ویو.با دستور زیر دروبین اجرا میشد
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) fragment.startActivityForResult( intent,requestCode )
و با کد زیر uri واقعی عکسی که گرفتم رو داشتم و میتونستم توی ایمیج ویو نشون بدم.این uri رو توی callback اکتیویتی ریزالت میگرفتم.
val contentURI: Uri = data?.data!! val bitmap = MediaStore.Images.Media.getBitmap( MainActivity.contentResolver, contentURI ) setImageBitmap(contentURI, bitmap)
خب بالاتر در مورد MediaStore و URI توضیح دادم.
عکسی که گرفته میشد در گالری هم دخیره میشد و بعد از پاک شدن اپ همچنان بود.یعنی خود os اونو ذخیره میکرد و خودش هم uri فایل رو بهم میداد و همه چی ساده بنظر میومد .
تا اینکه متوجه شدم برای api بالاتر از 24 اپ کرش میکنه و هنگام انتخاب عکس از دوریبن در activity result با خالی بودن data مواجه میشدم که بعد از کلی بررسی و سرچ کردن به این نتیجه رسیدم به خاطر تدابیر امنیتی در نظرگرفته شده توسط گوگل بعد از api level 24 (مثلا اگر برنامه ای پاک شد باید محتوای مربوط به اون برنامه مثل همین عکسایی که با دوربین گرفته شده باید پاک شوند یا مثلا اگر توسط برنامه فایلی ایجاد شد پاک شود و اینکه اگر برنامه ای محتوایی را در حافظه داخلی و خارجی گوشی ذخیره کرد برنامه های دیگر به این محتوا دسترسی نداشته باشند تا اینجایی که من متوجه شدم و اگر دلایل دیگری هم هست در کامنت ها ذکر کنید )
بعد از اینکه عکس از دوربین گرفته شد ذخیره سازی اون عکس در حافظه داخلی یا خارجی بر عهده خود برنامه هست بنابراین حق دسترسی به آن پرونده باید از برنامه ما باشد نه دوربین. هر عملیاتی که با این پرونده انجام می شود باید از طریق برنامه ما انجام شود نه توسط خود برنامه دوربین.
و به همین دلیل توسعه دهنده خودش مجبور هست اینکارا رو انجام بدهد.
(دقیقا برعکس نسخه های قبلی api level کمتر از 24) بنابرین باید فایل ذخیره شود و سپس uri معتبر مربوط به اون مورد استفاده قرار بگیرد .
خب پس Intent من برای استارت دوربین به صورت زیر تغییر میکنه(در مورد also و intent بعدا مقاله خواهم گذاشت فعلا اینجا خیلی مهم نیست توضیح بیشتر بدم)
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> // Ensure that there's a camera activity to handle the intent takePictureIntent.resolveActivity(fragment.activity?.packageManager!!)?.also { createFile() takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, currentPhotoUri) fragment.startActivityForResult(takePictureIntent, requestCode) } }
و متد createFile() چکار میکنه؟
private fun createFile(): File? { val photoFile: File? = try { createImageFile() } catch (ex: IOException) { Log.i("mylog error", "Error creating file: " + ex.toString()) ex.printStackTrace() null } // Continue only if the File was successfully created photoFile?.also { val photoURI: Uri = context?.let { context -> FileProvider.getUriForFile( context, getApplicationContext()?.packageName!!, it ) } as Uri currentPhotoUri = photoURI } return photoFile } private fun createImageFile(): File { val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) val storageDir: File? = context!!.getExternalFilesDir(Environment.DIRECTORY_PICTURES) Log.i("mylog", "storage dir = " + storageDir?.absolutePath) return File.createTempFile( "JPEG_${timeStamp}_", /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ).apply { // Save a file: path for use with ACTION_VIEW intents currentPhotoPath = absolutePath } }
اگر یکم به کد دقیت کنید خیلی سخت نیست بدونید چیکار میکنه.همونطور که ملاحضه میکنید متد greatefile داره اول قبل از اینکه دوربین شروع بکار کنه یک فایل با پسوند jpg ایجاد میکنه و اونو توی یه دایرکتوری دخیره میکنه و در نهایت مسیر اون رو بشما میده و با putExtra به برنامه دوربین میده حالا توی اکتیوتی ریزالت میتونید با این uri اون فایل رو بگیرید و حالا توی ایمیج ویو میخواید نشون بدید یا هر کار دیگه ای.
val bitmap = MediaStore.Images.Media.getBitmap( activity!!.contentResolver, currentPhotoUri ) setImageBitmap( CommonUtil.currentPhotoUri, bitmap!!, true)
خب پس اگر targetSdkVersion پروژه شما بالاتر از 24 باشد ، از FileProvider که یک زیر کلاس از ContentProvider هست برای دسترسی به دیتا استفاده می شود .
حالا در کنار موارد بالا که گفتم باید چندتا کانفیگ دیگ هم باید انجام بدیم .
در مسیر res/xml پروژه فایل زیر را ایجاد کنید.اگر فولدر xml ندارید آن را بسازید
provider_paths.xml
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="."/> </paths>
بعد یک provider در فایل AndroidManifest.xml اضافه کنید .
<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider>
اگر از androidx استفاده می کنید ، مسیر FileProvider باید این باشد:
android:name="androidx.core.content.FileProvider"
و در نهایت به جای
Uri uri = Uri.fromFile(fileImagePath);
به این صورت زیر استفاده نمایید.
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
جهت اطلاعات و مطالعه بیشتر در مورد این کانفیگ هایی ک در فایل xml و manifest انجام دادیم لینک زیر رو نگاهی بندازید.
https://infinum.com/the-capsized-eight/share-files-using-fileprovider
و لینک زیرهم جهت اطلاعات بیشتر پیرامون مطالب مقاله
امیدوارم مفید واقع شده باشد.موفق باشید