ملاحظة: تشير هذه الصفحة إلى فئة Camera التي تم إيقافها نهائيًا. ننصح باستخدام CameraX أو Camera2 لحالات استخدام معيّنة. يتوافق كلّ من CameraX وCamera2 مع الإصدار 5.0 من نظام التشغيل Android (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث.
يشرح هذا الدرس كيفية التقاط صورة من خلال تفويض المهمة إلى تطبيق كاميرا آخر على الجهاز. (إذا كنت تفضّل إنشاء وظائف الكاميرا بنفسك، يُرجى الاطّلاع على التحكّم في الكاميرا).
لنفترض أنّك بصدد تنفيذ خدمة طقس مستندة إلى حشد الموارد تُنشئ خريطة طقس عالمية من خلال دمج صور السماء التي التقطتها الأجهزة التي تعمل بتطبيق العميل. ولا يشكّل دمج الصور سوى جزء صغير من تطبيقك. تريد التقاط الصور بسهولة وبدون أي تعقيدات، وليس اختراع الكاميرا. لحسن الحظ، تم تثبيت تطبيق كاميرا واحد على الأقل على معظم الأجهزة التي تعمل بنظام التشغيل Android. ستتعرّف في هذا الدرس على كيفية التقاط الجهاز لصورة نيابةً عنك.
طلب ميزة الكاميرا
إذا كانت إحدى الوظائف الأساسية لتطبيقك هي التقاط الصور، يمكنك حصر ظهوره على
Google Play بالأجهزة المزوّدة بكاميرا. للإعلان عن أنّ تطبيقك يعتمد على استخدام
كاميرا، ضَع علامة
<uses-feature>
في
ملف البيان:
<manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest>
إذا كان تطبيقك يستخدم كاميرا ولكن لا يتطلّب استخدامها لكي يعمل، اضبط بدلاً من ذلك
android:required
على false
. عند إجراء ذلك، سيسمح Google Play للأجهزة
التي لا تتضمّن كاميرا بتنزيل تطبيقك. بعد ذلك، تقع على عاتقك مسؤولية التحقّق من
مدى توفّر الكاميرا أثناء التشغيل من خلال الاتصال بالرقم
hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
.
إذا لم تكن الكاميرا متاحة، عليك إيقاف ميزات الكاميرا.
الحصول على الصورة المصغّرة
إذا لم تكن مهمة التقاط صورة هي الإنجاز الأهم في تطبيقك، قد تحتاج إلى استرداد الصورة من تطبيق الكاميرا وإجراء تعديلات عليها.
يشفِّر تطبيق "كاميرا Android" الصورة في الردّ
Intent
الذي يتم إرساله إلى
onActivityResult()
كBitmap
صغير في الإضافات،
ضمن المفتاح "data"
. تسترجع التعليمة البرمجية التالية هذه الصورة وتعرضها في ImageView
.
Kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { val imageBitmap = data.extras.get("data") as Bitmap imageView.setImageBitmap(imageBitmap) } }
Java
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { Bundle extras = data.getExtras(); Bitmap imageBitmap = (Bitmap) extras.get("data"); imageView.setImageBitmap(imageBitmap); } }
ملاحظة: قد تكون هذه الصورة المصغّرة من "data"
مناسبة لتكون رمزًا،
ولكن ليس أكثر من ذلك. تتطلّب معالجة صورة بالحجم الكامل بعض الجهد الإضافي.
حفظ الصورة بالحجم الكامل
يحفظ تطبيق "كاميرا Android" صورة بالحجم الكامل إذا أعطيته ملفًا لحفظها فيه. يجب تقديم اسم ملف مؤهَّل بالكامل حيث يجب أن يحفظ تطبيق الكاميرا الصورة.
بشكل عام، يجب حفظ أي صور يلتقطها المستخدم باستخدام كاميرا الجهاز على الجهاز
في مساحة التخزين الخارجية العلنية حتى تتمكّن جميع التطبيقات من الوصول إليها. يتم توفير الدليل المناسب للصور
المشترَكة من خلال دالة
getExternalStoragePublicDirectory()
،
مع الوسيطة
DIRECTORY_PICTURES
. تتم مشاركة الدليل الذي تقدّمه هذه الطريقة بين جميع التطبيقات. في الإصدار 9 من نظام Android (المستوى
28 من واجهة برمجة التطبيقات) والإصدارات الأقدم، تتطلّب القراءة والكتابة في هذا الدليل إذنَي
READ_EXTERNAL_STORAGE
و
WRITE_EXTERNAL_STORAGE
على التوالي:
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
في الإصدار 10 من نظام التشغيل Android (المستوى 29 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يكون الدليل المناسب لمشاركة الصور هو جدول
MediaStore.Images
.
لست بحاجة إلى الإفصاح عن أي أذونات تخزين، ما دام تطبيقك يحتاج فقط إلى الوصول إلى
الصور التي التقطها المستخدم باستخدام تطبيقك.
ومع ذلك، إذا كنت تريد أن تظل الصور خاصة بتطبيقك فقط، يمكنك بدلاً من ذلك استخدام ملف تعريف العميل
الذي يوفّره
Context.getExternalFilesDir()
.
في الإصدار Android 4.3 والإصدارات الأقدم، تتطلّب الكتابة في هذا الدليل أيضًا إذن
WRITE_EXTERNAL_STORAGE
. اعتبارًا من الإصدار 4.4 من Android، لم يعُد هذا الإذن مطلوبًا لأنّه لا يمكن للتطبيقات الأخرى الوصول إلى الدليل، لذا يمكنك الإفصاح عن أنّه يجب طلب الإذن فقط على الإصدارات الأقدم من Android من خلال إضافة السمة
maxSdkVersion
:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> ... </manifest>
ملاحظة: عند إلغاء تثبيت تطبيقك، يتم حذف الملفات التي تحفظها في الدلائل التي يوفّرها getExternalFilesDir()
أو
getFilesDir()
.
بعد تحديد الدليل الذي سيحتوي على الملف، عليك إنشاء اسم ملف مقاوم للتصادم.
يمكنك أيضًا حفظ المسار في متغيّر عضو لاستخدامه لاحقًا. في ما يلي مثال على حلّ
في طريقة تعرض اسم ملف فريدًا لصورة جديدة باستخدام طابع زمني.
(يفترض هذا المثال أنّك تستدعي الطريقة من داخل Context
.)
Kotlin
lateinit var currentPhotoPath: String @Throws(IOException::class) private fun createImageFile(): File { // Create an image file name val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES) return File.createTempFile( "JPEG_${timeStamp}_", /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ).apply { // Save a file: path for use with ACTION_VIEW intents currentPhotoPath = absolutePath } }
Java
String currentPhotoPath; private File createImageFile() throws IOException { // Create an image file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File image = File.createTempFile( imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); // Save a file: path for use with ACTION_VIEW intents currentPhotoPath = image.getAbsolutePath(); return image; }
باستخدام هذه الطريقة المتاحة لإنشاء ملف للصورة، يمكنك الآن إنشاء ملف
Intent
واستخدامه على النحو التالي:
Kotlin
private fun dispatchTakePictureIntent() { Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -> // Ensure that there's a camera activity to handle the intent takePictureIntent.resolveActivity(packageManager)?.also { // Create the File where the photo should go val photoFile: File? = try { createImageFile() } catch (ex: IOException) { // Error occurred while creating the File ... null } // Continue only if the File was successfully created photoFile?.also { val photoURI: Uri = FileProvider.getUriForFile( this, "com.example.android.fileprovider", it ) takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI) startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) } } } }
Java
private void dispatchTakePictureIntent() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // Ensure that there's a camera activity to handle the intent if (takePictureIntent.resolveActivity(getPackageManager()) != null) { // Create the File where the photo should go File photoFile = null; try { photoFile = createImageFile(); } catch (IOException ex) { // Error occurred while creating the File ... } // Continue only if the File was successfully created if (photoFile != null) { Uri photoURI = FileProvider.getUriForFile(this, "com.example.android.fileprovider", photoFile); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); } } }
ملاحظة: نحن نستخدم
getUriForFile(Context, String, File)
الذي يعرض عنوان URL content://
. بالنسبة إلى التطبيقات الأحدث التي تستهدف الإصدار 7.0 من نظام التشغيل Android (المستوى
24 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يؤدي تمرير معرّف موارد منتظم (file://
URI) عبر حدود حزمة إلى FileUriExposedException
.
لذلك، نقدّم الآن طريقة أكثر عمومية لتخزين الصور باستخدام
FileProvider
.
عليك الآن ضبط
FileProvider
. في بيان
تطبيقك، أضِف مزوّدًا إلى تطبيقك:
<application> ... <provider android:name="androidx.core.content.FileProvider" android:authorities="com.example.android.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data> </provider> ... </application>
تأكَّد من أنّ سلسلة الهيئات تتطابق مع الوسيطة الثانية لسمة
getUriForFile(Context, String, File)
.
في قسم البيانات الوصفية لتعريف موفّر الخدمة، يمكنك الاطّلاع على أنّ موفّر الخدمة يتوقّع
ضبط المسارات المؤهّلة في ملف موارد مخصّص، وهو
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="my_images" path="Pictures" /> </paths>
يتطابق مكوّن المسار مع المسار الذي يعرضه
getExternalFilesDir()
عند استدعائه باستخدام
Environment.DIRECTORY_PICTURES
.
احرص على استبدال com.example.package.name
باسم الحزمة الفعلي
لتطبيقك. اطّلِع أيضًا على مستندات
FileProvider
للحصول على
وصف مفصّل لمحدّدات المسارات التي يمكنك استخدامها إلى جانب external-path
.
إضافة الصورة إلى معرض
عند إنشاء صورة من خلال نية، من المفترض أن تعرف مكان الصورة، لأنّك تحديدًا من حدّد مكان حفظها في المقام الأول. بالنسبة إلى المستخدمين الآخرين، قد تكون أسهل طريقة للسماح بالوصول إلى صورتك هي من خلال موفِّر الوسائط في النظام.
ملاحظة: إذا حفظت صورتك في الدليل المقدَّم من
getExternalFilesDir()
،
لا يمكن لأداة فحص الوسائط الوصول إلى الملفات لأنّها خاصة بتطبيقك.
يوضّح المثال التالي كيفية استدعاء أداة فحص الوسائط في النظام لإضافة صورتك إلى قاعدة بيانات مقدّم الوسائط، ما يجعلها متاحة في تطبيق "معرض الصور" على Android وفي التطبيقات الأخرى.
Kotlin
private fun galleryAddPic() { Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent -> val f = File(currentPhotoPath) mediaScanIntent.data = Uri.fromFile(f) sendBroadcast(mediaScanIntent) } }
Java
private void galleryAddPic() { Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); File f = new File(currentPhotoPath); Uri contentUri = Uri.fromFile(f); mediaScanIntent.setData(contentUri); this.sendBroadcast(mediaScanIntent); }
فك ترميز صورة تم تصغيرها
قد يكون من الصعب إدارة عدة صور بالحجم الكامل في حال كانت الذاكرة محدودة. إذا لاحظت أنّ تطبيقك ينفد من الذاكرة بعد عرض بضع صور فقط، يمكنك تقليل مقدار الذاكرة الديناميكية المستخدَمة بشكل كبير من خلال توسيع ملف JPEG إلى صفيف ذاكرة تم توسيعه مسبقًا لتتم مطابقته مع حجم العرض الوجهة. يوضّح المثال التالي لهذه الطريقة هذه التقنية.
Kotlin
private fun setPic() { // Get the dimensions of the View val targetW: Int = imageView.width val targetH: Int = imageView.height val bmOptions = BitmapFactory.Options().apply { // Get the dimensions of the bitmap inJustDecodeBounds = true BitmapFactory.decodeFile(currentPhotoPath, bmOptions) val photoW: Int = outWidth val photoH: Int = outHeight // Determine how much to scale down the image val scaleFactor: Int = Math.max(1, Math.min(photoW / targetW, photoH / targetH)) // Decode the image file into a Bitmap sized to fill the View inJustDecodeBounds = false inSampleSize = scaleFactor inPurgeable = true } BitmapFactory.decodeFile(currentPhotoPath, bmOptions)?.also { bitmap -> imageView.setImageBitmap(bitmap) } }
Java
private void setPic() { // Get the dimensions of the View int targetW = imageView.getWidth(); int targetH = imageView.getHeight(); // Get the dimensions of the bitmap BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; BitmapFactory.decodeFile(currentPhotoPath, bmOptions); int photoW = bmOptions.outWidth; int photoH = bmOptions.outHeight; // Determine how much to scale down the image int scaleFactor = Math.max(1, Math.min(photoW/targetW, photoH/targetH)); // Decode the image file into a Bitmap sized to fill the View bmOptions.inJustDecodeBounds = false; bmOptions.inSampleSize = scaleFactor; bmOptions.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions); imageView.setImageBitmap(bitmap); }