عکس گرفتن

توجه: این صفحه به کلاس دوربین اشاره دارد که منسوخ شده است. توصیه می‌کنیم از CameraX یا برای موارد استفاده خاص از Camera2 استفاده کنید. ‫CameraX و Camera2 هر دو از Android 5.0 (سطح میانای برنامه‌سازی کاربردی ۲۱) و بالاتر پشتیبانی می‌کنند.

این درس نحوه گرفتن عکس را با واگذار کردن کار به برنامه دوربین دیگری در دستگاه آموزش می‌دهد. (اگر ترجیح می‌دهید عملکرد دوربین خودتان را بسازید، کنترل کردن دوربین را ببینید.)

فرض کنید درحال پیاده‌سازی یک سرویس هواشناسی با انبوه سپاری هستید که با ترکیب کردن عکس‌های آسمان که توسط دستگاه‌های اجراکننده برنامه مشتری شما گرفته شده است، یک نقشه آب‌وهوای جهانی ایجاد می‌کند. ادغام عکس‌ها تنها بخش کوچکی از برنامه شما است. می‌خواهید با کمترین دردسر عکس بگیرید، نه اینکه دوربین را از نو اختراع کنید. خوشبختانه، اکثر دستگاه‌های مجهز به 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 (سطح میانای برنامه‌سازی کاربردی ۲۸) و پایین‌تر، خواندن و نوشتن در این دایرکتوری به‌ترتیب به اجازه‌های 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 (سطح API ‏۲۹) و بالاتر، دایرکتوری مناسب برای هم‌رسانی عکس‌ها جدول MediaStore.Images است. تا زمانی که برنامه‌تان فقط به دسترسی به عکس‌هایی نیاز دارد که کاربر بااستفاده از برنامه شما گرفته است، نیازی نیست هیچ‌گونه اجازه فضای ذخیره‌سازی را اعلام کنید.

بااین‌حال، اگر می‌خواهید عکس‌ها فقط برای برنامه‌تان خصوصی بماند، می‌توانید به‌جای آن از دایرکتوری ارائه‌شده توسط Context.getExternalFilesDir() استفاده کنید. در Android نسخه ۴.۳ و پایین‌تر، نوشتن در این دایرکتوری به WRITE_EXTERNAL_STORAGE اجازه نیز نیاز دارد. از Android نسخه ۴.۴، دیگر به این اجازه نیازی نیست زیرا دایرکتوری برای برنامه‌های دیگر دردسترس نیست، بنابراین می‌توانید با افزودن مشخصه maxSdkVersion اعلام کنید که این اجازه فقط باید در نسخه‌های پایین‌تر Android درخواست شود:

<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) استفاده می‌کنیم که شناسه منبع یکسان content:// را برمی‌گرداند. برای برنامه‌های جدیدتری که Android نسخه ۷.۰ (سطح میانای برنامه‌سازی کاربردی ۲۴) و بالاتر را هدف‌یابی می‌کنند، ارسال 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) مطابقت داشته باشد. در بخش فراداده تعریف ارائه‌دهنده، می‌توانید ببینید که ارائه‌دهنده انتظار دارد مسیرهای واجدشرایط در فایل منبع اختصاصی، res/xml/file_paths.xml، پیکربندی شوند. محتوای لازم برای این مثال خاص در اینجا آمده است:

<?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 را با نام بسته واقعی برنامه‌تان جایگزین کنید. همچنین، برای توضیحات گسترده درباره مشخص‌کننده‌های مسیر که می‌توانید علاوه‌بر external-path استفاده کنید، به مستندات FileProvider مراجعه کنید.

افزودن عکس به گالری

وقتی عکسی را ازطریق قصد ایجاد می‌کنید، باید بدانید تصویرتان کجا قرار دارد، زیرا در ابتدا گفته‌اید کجا ذخیره شود. برای دیگران، شاید ساده‌ترین راه برای دردسترس قرار دادن عکس این باشد که آن را ازطریق «ارائه‌دهنده رسانه» سیستم دردسترس قرار دهید.

توجه: اگر عکستان را در دایرکتوری ارائه‌شده توسط 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);
}