ملاحظة: تشير هذه الصفحة إلى فئة Camera التي تم إيقافها نهائيًا. ننصحك باستخدام CameraX أو Camera2 لحالات استخدام معيّنة. يتوافق كل من CameraX و Camera2 مع نظام التشغيل Android 5.0 (المستوى 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
.
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)
}
}
@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>
في نظام التشغيل Android 10 (مستوى واجهة برمجة التطبيقات 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
.)
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
}
}
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
واستخدامه على النحو التالي:
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)
}
}
}
}
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) وأعلى، حيث يؤدي تمرير معرف الموارد المنتظم (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()
،
لا يمكن لبرنامج الماسح الضوئي للوسائط الوصول إلى الملفات لأنّها خاصة بتطبيقك.
يوضّح المثال التالي كيفية استدعاء أداة فحص الوسائط في النظام لإضافة صورتك إلى قاعدة بيانات مقدّم الوسائط، ما يجعلها متاحة في تطبيق "معرض الصور" على Android والتطبيقات الأخرى.
private fun galleryAddPic() {
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
val f = File(currentPhotoPath)
mediaScanIntent.data = Uri.fromFile(f)
sendBroadcast(mediaScanIntent)
}
}
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 إلى مصفوفة ذاكرة تم تحجيمها من قبل إلى مع حجم عرض الوجهة. توضح الطريقة التالية كمثال هذا الأسلوب.
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)
}
}
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);
}