Robienie zdjęć

Uwaga: ta strona dotyczy wycofanej klasy Camera. Zalecamy korzystanie z Aparatu X lub – w określonych przypadkach – z Aparatu 2. Aparaty CameraX i Aparat 2 obsługują Androida 5.0 (poziom interfejsu API 21) i nowsze wersje.

Z tej lekcji dowiesz się, jak zrobić zdjęcie przez przekazanie pracy do innej aplikacji aparatu urządzenia. (Jeśli wolisz utworzyć własną funkcję kamery, zobacz Sterowanie kamerą).

Załóżmy, że wdrażasz usługę pogodową, która tworzy globalną mapę pogodową przez łączenie zdjęć nieba z urządzeń z Twoją aplikacją kliencką. Integracja zdjęć to tylko mała część aplikacji. Chcesz robić zdjęcia bez zbędnych komplikacji, a nie wymyślać nowego aparatu. Na szczęście większość urządzeń z Androidem ma już co najmniej jedną aplikację aparatu. Zainstalowano. Z tej lekcji dowiesz się, jak zrobić zdjęcie.

Wysyłanie prośby o funkcję aparatu

Jeśli podstawową funkcją aplikacji jest robienie zdjęć, ogranicz widoczność aplikacji w Google Play do urządzeń z kamerą. Aby poinformować, że aplikacja wymaga kamery, umieść tag <uses-feature> w pliku manifestu:

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

Jeśli Twoja aplikacja używa kamery do działania, ale jej nie wymaga, ustaw android:required do false. Jeśli to zrobisz, Google Play zezwoli na urządzenia bez kamery, aby pobrać aplikację. Następnie Twoim zadaniem jest sprawdzenie dostępności kamery w czasie wykonywania kodu przez wywołanie funkcji hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY). Jeśli kamera nie jest dostępna, wyłącz funkcje kamery.

Wybierz miniaturę

Jeśli samo zrobienie zdjęcia nie jest szczytem ambicji Twojej aplikacji, prawdopodobnie chcesz odzyskać obraz z aplikacji aparatu i zrobić z nim coś użytecznego.

Aparat w Androidzie koduje w odpowiedzi zdjęcie. Dostarczono Intent do onActivityResult() jako małe Bitmap w dodatkach. w kluczu "data". Poniższy kod umożliwia pobranie tego obrazu i wyświetlenie go w 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);
    }
}

Uwaga: ta miniatura z "data" może być odpowiednia na ikonę, ale nie na wiele więcej. Praca z pełnowymiarowymi obrazami wymaga nieco więcej wysiłku.

Zapisywanie zdjęcia w pełnym rozmiarze

Aparat w Androidzie zapisuje zdjęcie w pełnym rozmiarze, jeśli dodasz do niego plik. Ty musi zawierać pełną nazwę pliku, w którym aplikacja aparatu ma zapisać zdjęcie.

Ogólnie wszystkie zdjęcia, które użytkownik robi aparatem urządzenia, powinny być zapisywane na urządzeniu. w publicznej pamięci zewnętrznej, aby były dostępne dla wszystkich aplikacji. Prawidłowy katalog dla udostępnionych zdjęć jest udostępniany przez getExternalStoragePublicDirectory() za pomocą argumentu DIRECTORY_PICTURES. Katalog udostępniony przy użyciu tej metody jest wspólny dla wszystkich aplikacji. Android 9 (poziom interfejsu API 28) lub niższym, odczyt i zapis w tym katalogu wymaga READ_EXTERNAL_STORAGE oraz WRITE_EXTERNAL_STORAGE uprawnień:

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

W Androidzie 10 (poziom interfejsu API 29) i wyższych odpowiednim katalogiem do udostępniania zdjęć jest tabela MediaStore.Images. Nie musisz deklarować żadnych uprawnień dostępu do pamięci, o ile aplikacja potrzebuje dostępu tylko do zdjęć zrobionych przez użytkownika za pomocą aplikacji.

Jeśli jednak chcesz, aby zdjęcia pozostały prywatne tylko w Twojej aplikacji, możesz zamiast tego użyć katalog udostępniony przez Context.getExternalFilesDir() W Androidzie 4.3 i starszych wersjach zapisywanie w tym katalogu wymaga także WRITE_EXTERNAL_STORAGE uprawnienia. Począwszy od Androida 4.4 uprawnienia nie są już wymagane, ponieważ katalog nie jest dostępne dla innych aplikacji, więc zadeklaruj, że uprawnienia powinny być wysyłane tylko na starszych wersji Androida przez dodanie maxSdkVersion :

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="28" />
    ...
</manifest>

Uwaga: pliki zapisane w katalogach udostępnionych przez getExternalFilesDir() lub getFilesDir() są usuwane, gdy użytkownik odinstaluje aplikację.

Gdy zdecydujesz, w którym katalogu ma się znajdować plik, musisz utworzyć nazwę pliku odporną na kolizje. Możesz również zapisać ścieżkę w zmiennej uczestnika do późniejszego użycia. Oto przykładowe rozwiązanie: w metodzie, która zwraca unikalną nazwę pliku dla nowego zdjęcia z użyciem sygnatury daty i godziny. (W tym przykładzie przyjęto, że ta metoda jest wywoływana z poziomu obiektu 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;
}

Gdy ta metoda jest dostępna do utworzenia pliku zdjęcia, możesz utworzyć i wywołać Intent lubi to:

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);
        }
    }
}

Uwaga: używamy getUriForFile(Context, String, File) , który zwraca identyfikator URI content://. Najnowsze aplikacje kierowane na Androida 7.0 (poziom interfejsu API 24) lub wyższym, przekazanie identyfikatora URI file:// przez granicę pakietu powoduje FileUriExposedException Dlatego udostępniamy teraz bardziej ogólny sposób przechowywania obrazów za pomocą FileProvider

Teraz musisz skonfigurować FileProvider. W manifeście aplikacji dodaj dostawcę:

<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>

Upewnij się, że ciąg znaków authorities jest zgodny z drugim argumentem funkcji getUriForFile(Context, String, File). W sekcji metadanych dostawcy widać, że dostawca tego oczekuje odpowiednie ścieżki do skonfigurowania w specjalnym pliku zasobów res/xml/ścieżki_plików.xml. Tutaj Czy treść jest wymagana w tym konkretnym przykładzie:

<?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>

Komponent ścieżki odpowiada ścieżce zwracanej przez funkcję getExternalFilesDir() po wywołaniu z Environment.DIRECTORY_PICTURES Pamiętaj, aby zastąpić com.example.package.name rzeczywistą nazwą pakietu do aplikacji. Zapoznaj się też z dokumentacją usługi FileProvider za obszerny opis specyfikatorów ścieżek, których możesz używać oprócz external-path.

Dodaj zdjęcie do galerii

Gdy tworzysz zdjęcie za pomocą intencji, wiesz, gdzie się ono znajduje, ponieważ na początku określasz, gdzie je zapisać. W przypadku innych użytkowników najłatwiejszym sposobem udostępnienia zdjęcia jest udostępnienie go za pomocą dostawcy multimediów systemu.

Uwaga: jeśli zdjęcie zostało zapisane w katalogu udostępnionym przez getExternalFilesDir() skaner multimediów nie może uzyskać dostępu do plików, ponieważ są one prywatne w aplikacji.

Ten przykładowy sposób pokazuje, jak wywołać skaner multimediów systemu, aby dodać zdjęcie do bazy danych dostawcy multimediów, dzięki czemu będzie ono dostępne w aplikacji Galeria na Androidzie oraz w innych aplikacjach.

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);
}

Dekodowanie skalowanego obrazu

Zarządzanie wieloma zdjęciami w pełnym rozmiarze może być trudne z powodu ograniczonej pamięci. Jeśli zauważysz, że w aplikacji zaczyna brakować pamięci po wyświetleniu tylko kilku obrazów, można znacznie zmniejszyć ilości dynamicznej stosu wykorzystywanej przez rozwinięcie pliku JPEG do tablicy pamięci, która została już przeskalowana do do rozmiaru docelowego widoku. Prezentację tej metody przedstawia przykładowa metoda.

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);
}