ملاحظة: تشير هذه الصفحة إلى فئة الكاميرا التي تم إيقافها نهائيًا. ننصح باستخدام CameraX أو Camera2 في حالات استخدام معيّنة. تتوافق كل من CameraX وCamera2 مع الإصدار 5.0 من نظام التشغيل Android (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث.
للتعرّف على كيفية نقل البيانات من Camera1 إلى CameraX، يُرجى الاطّلاع على
نقل البيانات من Camera1 إلى CameraX.
بدلاً من ذلك، يمكنك استخدام
مهارة الذكاء الاصطناعي الوكيل لنقل البيانات من Camera1 إلى CameraX.
يوضّح هذا الدرس كيفية التقاط صورة من خلال تفويض العمل إلى تطبيق كاميرا آخر على الجهاز. (إذا كنت تفضّل إنشاء وظائف الكاميرا الخاصة بك، يُرجى الاطّلاع على التحكّم في الكاميرا).
لنفترض أنّك تنفّذ خدمة طقس تعتمد على حشد الموارد وتنشئ خريطة طقس عالمية من خلال دمج صور السماء التي التقطتها الأجهزة التي تشغّل تطبيق العميل. إنّ دمج الصور ليس سوى جزء صغير من تطبيقك. أنت تريد التقاط الصور بأقل قدر من الضجة، وليس إعادة اختراع الكاميرا. لحسن الحظ، معظم الأجهزة التي تعمل بنظام التشغيل Android تحتوي على تطبيق كاميرا واحد على الأقل مثبّت. في هذا الدرس، ستتعرّف على كيفية التقاط صورة باستخدام Gemini.
طلب ميزة الكاميرا
إذا كانت إحدى الوظائف الأساسية لتطبيقك هي التقاط الصور، يمكنك حصر ظهوره على 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. تتم مشاركة الدليل الذي توفّره هذه الطريقة بين جميع التطبيقات. على نظام التشغيل Android 9 (المستوى 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().
في الإصدار 4.3 من نظام التشغيل Android والإصدارات الأقدم، تتطلّب الكتابة في هذا الدليل أيضًا الحصول على إذن
WRITE_EXTERNAL_STORAGE. بدءًا من الإصدار Android 4.4، لم يعُد الإذن مطلوبًا لأنّ الدليل
لا يمكن للتطبيقات الأخرى الوصول إليه، لذا يمكنك الإعلان عن أنّه يجب طلب الإذن فقط في
الإصدارات الأقدم من 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)
الذي يعرض معرّف موارد منتظم (URI) لـ content://. بالنسبة إلى التطبيقات الأحدث التي تستهدف الإصدار 7.0 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 24) والإصدارات الأحدث، يؤدي تمرير معرّف موارد منتظم (URI) من النوع file:// عبر حدود الحزمة إلى حدوث 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()،
لن يتمكّن الماسح الضوئي للوسائط من الوصول إلى الملفات لأنّها خاصة بتطبيقك.
توضّح الطريقة في المثال التالي كيفية استدعاء أداة فحص الوسائط في النظام لإضافة صورتك إلى قاعدة بيانات Media Provider، ما يجعلها متاحة في تطبيق "معرض الصور" على 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); }