Aufleuchten des Bildschirms bei Benachrichtigungen

Der Bildschirmblitz, auch Frontblitz oder Selfieblitz genannt, nutzt die Helligkeit des Displays eines Smartphones, um das Motiv bei Aufnahmen mit der Frontkamera bei schlechten Lichtverhältnissen zu beleuchten. Sie ist in vielen nativen Kamera-Apps und Social-Media-Apps verfügbar. Da die meisten Menschen ihr Smartphone beim Aufnehmen eines Selfies nah genug halten, ist dieser Ansatz effektiv.

Es ist jedoch schwierig für Entwickler, die Funktion richtig zu implementieren und eine gute Aufnahmequalität auf allen Geräten beizubehalten. In dieser Anleitung wird gezeigt, wie Sie diese Funktion mit Camera2, der Low-Level-API des Android-Kamera-Frameworks, richtig implementieren.

Allgemeiner Workflow

Für die korrekte Implementierung der Funktion sind die beiden wichtigsten Faktoren die Verwendung der Belichtungsreihe vor der Aufnahme (automatische Belichtung vor der Aufnahme) und das Timing der Vorgänge. Der allgemeine Workflow ist in Abbildung 1 dargestellt.

Flussdiagramm, das zeigt, wie eine Benutzeroberfläche für den Bildschirmblitz in Camera2 verwendet wird
Abbildung 1: Allgemeiner Workflow für die Implementierung von Bildschirmblitzen.

Die folgenden Schritte werden verwendet, wenn ein Bild mit der Bildschirm-Blitzfunktion aufgenommen werden soll.

  1. Änderungen an der Benutzeroberfläche vornehmen, die für das Aufleuchten des Bildschirms erforderlich sind, um mit dem Display des Geräts ausreichend Licht für Fotos zu erhalten. Für allgemeine Anwendungsfälle empfiehlt Google die folgenden UI-Änderungen, die in unseren Tests verwendet wurden:
    • Der App-Bildschirm ist mit einem weißen Overlay bedeckt.
    • Die Bildschirmhelligkeit ist maximal.
  2. Legen Sie den Modus für die automatische Belichtung (AE) auf CONTROL_AE_MODE_ON_EXTERNAL_FLASH fest, sofern unterstützt.
  3. Lösen Sie mit CONTROL_AE_PRECAPTURE_TRIGGER eine Belichtungssequenz vor der Aufnahme aus.
  4. Warten Sie, bis die automatische Belichtung (AE) und der automatische Weißabgleich (AWB) konvergiert sind.

  5. Nach der Konvergenz wird der normale Ablauf der App für die Fotoaufnahme verwendet.

  6. Senden Sie eine Aufnahmeanfrage an das Framework.

  7. Warten Sie, bis das Aufnahmeergebnis eingegangen ist.

  8. Setzen Sie den AE-Modus zurück, wenn CONTROL_AE_MODE_ON_EXTERNAL_FLASH festgelegt wurde.

  9. Änderungen an der Benutzeroberfläche für das Aufleuchten des Bildschirms löschen

Camera2-Beispielcodes

App-Bildschirm mit einem weißen Overlay abdecken

Fügen Sie der Layout-XML-Datei Ihrer Anwendung eine Ansicht hinzu. Die Ansicht ist hoch genug, um bei der Aufnahme des Bildschirmflackerns über allen anderen UI-Elementen zu liegen. Sie ist standardmäßig ausgeblendet und wird nur sichtbar, wenn Änderungen an der Benutzeroberfläche des Bildschirm-Flashs angewendet werden.

Im folgenden Codebeispiel wird die Farbe Weiß (#FFFFFF) als Beispiel für die Ansicht verwendet. Anwendungen können die Farbe auswählen oder Nutzern je nach ihren Anforderungen mehrere Farben anbieten.

<View
    android:id="@+id/white_color_overlay"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:visibility="invisible"
    android:elevation="8dp" />

Bildschirmhelligkeit maximieren

Es gibt mehrere Möglichkeiten, die Bildschirmhelligkeit in einer Android-App zu ändern. Eine direkte Möglichkeit besteht darin, den WindowManager-Parameter screenBrightness in der Referenz Activity Window zu ändern.

Kotlin

private var previousBrightness: Float = -1.0f

private fun maximizeScreenBrightness() {
    activity?.window?.let { window ->
        window.attributes?.apply {
            previousBrightness = screenBrightness
            screenBrightness = 1f
            window.attributes = this
        }
    }
}

private fun restoreScreenBrightness() {
    activity?.window?.let { window ->
        window.attributes?.apply {
            screenBrightness = previousBrightness
            window.attributes = this
        }
    }
}

Java

private float mPreviousBrightness = -1.0f;

private void maximizeScreenBrightness() {
    if (getActivity() == null || getActivity().getWindow() == null) {
        return;
    }

    Window window = getActivity().getWindow();
    WindowManager.LayoutParams attributes = window.getAttributes();

    mPreviousBrightness = attributes.screenBrightness;
    attributes.screenBrightness = 1f;
    window.setAttributes(attributes);
}

private void restoreScreenBrightness() {
    if (getActivity() == null || getActivity().getWindow() == null) {
        return;
    }

    Window window = getActivity().getWindow();
    WindowManager.LayoutParams attributes = window.getAttributes();

    attributes.screenBrightness = mPreviousBrightness;
    window.setAttributes(attributes);
}

AE-Modus auf CONTROL_AE_MODE_ON_EXTERNAL_FLASH festlegen

CONTROL_AE_MODE_ON_EXTERNAL_FLASH ist mit API-Level 28 oder höher verfügbar. Dieser AE-Modus ist jedoch nicht auf allen Geräten verfügbar. Prüfen Sie daher, ob der AE-Modus verfügbar ist, und legen Sie den Wert entsprechend fest. Verwenden Sie CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES, um die Verfügbarkeit zu prüfen.

Kotlin

private val characteristics: CameraCharacteristics by lazy {
    cameraManager.getCameraCharacteristics(cameraId)
}

@RequiresApi(Build.VERSION_CODES.P)
private fun isExternalFlashAeModeAvailable() =
    characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES)
        ?.contains(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH) ?: false

Java

try {
    mCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
} catch (CameraAccessException e) {
    e.printStackTrace();
}

@RequiresApi(Build.VERSION_CODES.P)
private boolean isExternalFlashAeModeAvailable() {
    int[] availableAeModes = mCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);

    for (int aeMode : availableAeModes) {
        if (aeMode == CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH) {
            return true;
        }
    }
    return false;
}

Wenn für die Anwendung eine wiederholte Aufnahmeanfrage festgelegt ist (für die Vorschau erforderlich), muss der AE-Modus auf die wiederholte Anfrage eingestellt werden. Andernfalls wird er bei der nächsten wiederholten Aufnahme möglicherweise von einem Standard- oder anderen vom Nutzer festgelegten AE-Modus überschrieben. In diesem Fall hat die Kamera möglicherweise nicht genügend Zeit, alle Vorgänge auszuführen, die sie normalerweise für den AE-Modus eines externen Blitzes ausführt.

Um sicherzustellen, dass die Kamera die Aktualisierungsanfrage für den AE-Modus vollständig verarbeitet, prüfen Sie das Aufnahmeergebnis im wiederholten Aufnahme-Callback und warten Sie, bis der AE-Modus im Ergebnis aktualisiert wird.

Callback für die Aufnahme, der auf die Aktualisierung des AE-Modus warten kann

Das folgende Code-Snippet zeigt, wie das geht.

Kotlin

private val repeatingCaptureCallback = object : CameraCaptureSession.CaptureCallback() {
    private var targetAeMode: Int? = null
    private var aeModeUpdateDeferred: CompletableDeferred? = null

    suspend fun awaitAeModeUpdate(targetAeMode: Int) {
        this.targetAeMode = targetAeMode
        aeModeUpdateDeferred = CompletableDeferred()
        // Makes the current coroutine wait until aeModeUpdateDeferred is completed. It is
        // completed once targetAeMode is found in the following capture callbacks
        aeModeUpdateDeferred?.await()
    }

    private fun process(result: CaptureResult) {
        // Checks if AE mode is updated and completes any awaiting Deferred
        aeModeUpdateDeferred?.let {
            val aeMode = result[CaptureResult.CONTROL_AE_MODE]
            if (aeMode == targetAeMode) {
                it.complete(Unit)
            }
        }
    }

    override fun onCaptureCompleted(
        session: CameraCaptureSession,
        request: CaptureRequest,
        result: TotalCaptureResult
    ) {
        super.onCaptureCompleted(session, request, result)
        process(result)
    }
}

Java

static class AwaitingCaptureCallback extends CameraCaptureSession.CaptureCallback {
    private int mTargetAeMode;
    private CountDownLatch mAeModeUpdateLatch = null;

    public void awaitAeModeUpdate(int targetAeMode) {
        mTargetAeMode = targetAeMode;
        mAeModeUpdateLatch = new CountDownLatch(1);
        // Makes the current thread wait until mAeModeUpdateLatch is released, it will be
        // released once targetAeMode is found in the capture callbacks below
        try {
            mAeModeUpdateLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void process(CaptureResult result) {
        // Checks if AE mode is updated and decrements the count of any awaiting latch
        if (mAeModeUpdateLatch != null) {
            int aeMode = result.get(CaptureResult.CONTROL_AE_MODE);
            if (aeMode == mTargetAeMode) {
                mAeModeUpdateLatch.countDown();
            }
        }
    }

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session,
            @NonNull CaptureRequest request,
            @NonNull TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);
        process(result);
    }
}

private final AwaitingCaptureCallback mRepeatingCaptureCallback = new AwaitingCaptureCallback();

Wiederkehrende Anfrage zum Aktivieren oder Deaktivieren des AE-Modus einrichten

Nachdem der Capture-Callback eingerichtet wurde, zeigen die folgenden Codebeispiele, wie eine wiederkehrende Anfrage festgelegt wird.

Kotlin

/** [HandlerThread] where all camera operations run */
private val cameraThread = HandlerThread("CameraThread").apply { start() }

/** [Handler] corresponding to [cameraThread] */
private val cameraHandler = Handler(cameraThread.looper)

private suspend fun enableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        session.setRepeatingRequest(
            camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
                addTarget(previewSurface)
                set(
                    CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH
                )
            }.build(), repeatingCaptureCallback, cameraHandler
        )

        // Wait for the request to be processed by camera
        repeatingCaptureCallback.awaitAeModeUpdate(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH)
    }
}

private fun disableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        session.setRepeatingRequest(
            camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
                addTarget(previewSurface)
            }.build(), repeatingCaptureCallback, cameraHandler
        )
    }
}

Java

private void setupCameraThread() {
    // HandlerThread where all camera operations run
    HandlerThread cameraThread = new HandlerThread("CameraThread");
    cameraThread.start();

    // Handler corresponding to cameraThread
    mCameraHandler = new Handler(cameraThread.getLooper());
}

private void enableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        try {
            CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            requestBuilder.addTarget(mPreviewSurface);
            requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH);
            mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        // Wait for the request to be processed by camera
        mRepeatingCaptureCallback.awaitAeModeUpdate(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH);
    }
}

private void disableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        try {
            CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            requestBuilder.addTarget(mPreviewSurface);
            mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}

Sequenz vor der Aufnahme auslösen

Wenn Sie eine Belichtungssequenz vor der Aufnahme auslösen möchten, können Sie eine CaptureRequest mit dem Wert CONTROL_AE_PRECAPTURE_TRIGGER_START für die Anfrage senden. Sie müssen warten, bis die Anfrage verarbeitet wurde, und dann, bis die AE und der AWB konvergieren.

Auch wenn der Pre-Capture-Trigger mit einer einzelnen Aufnahmeanfrage ausgelöst wird, ist das Warten auf die AE- und AWB-Konvergenz etwas komplexer. Sie können den AE-Status und den AWB-Status mit einem Aufnahme-Callback verfolgen, der auf eine wiederkehrende Anfrage festgelegt ist.

Wenn Sie denselben wiederkehrenden Rückruf aktualisieren, können Sie den Code vereinfachen. Anwendungen erfordern häufig eine Vorschau, für die beim Einrichten der Kamera eine wiederholte Anfrage eingerichtet wird. Sie können den Rückruf für die wiederholte Erfassung also einmal auf diese erste wiederholte Anfrage festlegen und dann wiederverwenden, um die Ergebnisse zu prüfen und zu warten.

Callback-Code aktualisieren, um auf Konvergenz zu warten

Verwenden Sie das folgende Code-Snippet, um den wiederholten Erfassungs-Callback zu aktualisieren.

Kotlin

private val repeatingCaptureCallback = object : CameraCaptureSession.CaptureCallback() {
    private var targetAeMode: Int? = null
    private var aeModeUpdateDeferred: CompletableDeferred? = null

    private var convergenceDeferred: CompletableDeferred? = null

    suspend fun awaitAeModeUpdate(targetAeMode: Int) {
        this.targetAeMode = targetAeMode
        aeModeUpdateDeferred = CompletableDeferred()
        // Makes the current coroutine wait until aeModeUpdateDeferred is completed. It is
        // completed once targetAeMode is found in the following capture callbacks
        aeModeUpdateDeferred?.await()
    }

    suspend fun awaitAeAwbConvergence() {
        convergenceDeferred = CompletableDeferred()
        // Makes the current coroutine wait until convergenceDeferred is completed, it will be
        // completed once both AE & AWB are reported as converged in the capture callbacks below
        convergenceDeferred?.await()
    }

    private fun process(result: CaptureResult) {
        // Checks if AE mode is updated and completes any awaiting Deferred
        aeModeUpdateDeferred?.let {
            val aeMode = result[CaptureResult.CONTROL_AE_MODE]
            if (aeMode == targetAeMode) {
                it.complete(Unit)
            }
        }

        // Checks for convergence and completes any awaiting Deferred
        convergenceDeferred?.let {
            val aeState = result[CaptureResult.CONTROL_AE_STATE]
            val awbState = result[CaptureResult.CONTROL_AWB_STATE]

            val isAeReady = (
                    aeState == null // May be null in some devices (e.g. legacy camera HW level)
                            || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
                            || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED
                    )

            val isAwbReady = (
                    awbState == null // May be null in some devices (e.g. legacy camera HW level)
                            || awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED
                    )

            if (isAeReady && isAwbReady) {
                // if any non-null convergenceDeferred is set, complete it
                it.complete(Unit)
            }
        }
    }

    override fun onCaptureCompleted(
        session: CameraCaptureSession,
        request: CaptureRequest,
        result: TotalCaptureResult
    ) {
        super.onCaptureCompleted(session, request, result)
        process(result)
    }
}

Java

static class AwaitingCaptureCallback extends CameraCaptureSession.CaptureCallback {
    private int mTargetAeMode;
    private CountDownLatch mAeModeUpdateLatch = null;

    private CountDownLatch mConvergenceLatch = null;

    public void awaitAeModeUpdate(int targetAeMode) {
        mTargetAeMode = targetAeMode;
        mAeModeUpdateLatch = new CountDownLatch(1);
        // Makes the current thread wait until mAeModeUpdateLatch is released, it will be
        // released once targetAeMode is found in the capture callbacks below
        try {
            mAeModeUpdateLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void awaitAeAwbConvergence() {
        mConvergenceLatch = new CountDownLatch(1);
        // Makes the current coroutine wait until mConvergenceLatch is released, it will be
        // released once both AE & AWB are reported as converged in the capture callbacks below
        try {
            mConvergenceLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void process(CaptureResult result) {
        // Checks if AE mode is updated and decrements the count of any awaiting latch
        if (mAeModeUpdateLatch != null) {
            int aeMode = result.get(CaptureResult.CONTROL_AE_MODE);
            if (aeMode == mTargetAeMode) {
                mAeModeUpdateLatch.countDown();
            }
        }

        // Checks for convergence and decrements the count of any awaiting latch
        if (mConvergenceLatch != null) {
            Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
            Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE);

            boolean isAeReady = (
                    aeState == null // May be null in some devices (e.g. legacy camera HW level)
                            || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
                            || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED
            );

            boolean isAwbReady = (
                    awbState == null // May be null in some devices (e.g. legacy camera HW level)
                            || awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED
            );

            if (isAeReady && isAwbReady) {
                mConvergenceLatch.countDown();
                mConvergenceLatch = null;
            }
        }
    }

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session,
            @NonNull CaptureRequest request,
            @NonNull TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);
        process(result);
    }
}

Callback während der Kameraeinrichtung auf eine wiederkehrende Anfrage festlegen

Im folgenden Codebeispiel wird gezeigt, wie Sie den Callback während der Initialisierung auf eine wiederkehrende Anfrage festlegen.

Kotlin

// Open the selected camera
camera = openCamera(cameraManager, cameraId, cameraHandler)

// Creates list of Surfaces where the camera will output frames
val targets = listOf(previewSurface, imageReaderSurface)

// Start a capture session using our open camera and list of Surfaces where frames will go
session = createCameraCaptureSession(camera, targets, cameraHandler)

val captureRequest = camera.createCaptureRequest(
        CameraDevice.TEMPLATE_PREVIEW).apply { addTarget(previewSurface) }

// This will keep sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
session.setRepeatingRequest(captureRequest.build(), repeatingCaptureCallback, cameraHandler)

Java

// Open the selected camera
mCamera = openCamera(mCameraManager, mCameraId, mCameraHandler);

// Creates list of Surfaces where the camera will output frames
List targets = new ArrayList<>(Arrays.asList(mPreviewSurface, mImageReaderSurface));

// Start a capture session using our open camera and list of Surfaces where frames will go
mSession = createCaptureSession(mCamera, targets, mCameraHandler);

try {
    CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    requestBuilder.addTarget(mPreviewSurface);

    // This will keep sending the capture request as frequently as possible until the
    // session is torn down or session.stopRepeating() is called
    mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler);
} catch (CameraAccessException e) {
    e.printStackTrace();
}

Auslösen und Warten vor der Aufnahme

Nachdem Sie den Rückruf festgelegt haben, können Sie das folgende Codebeispiel für das Auslösen und Warten einer Sequenz vor der Aufnahme verwenden.

Kotlin

private suspend fun runPrecaptureSequence() {
    // Creates a new capture request with CONTROL_AE_PRECAPTURE_TRIGGER_START
    val captureRequest = session.device.createCaptureRequest(
        CameraDevice.TEMPLATE_PREVIEW
    ).apply {
        addTarget(previewSurface)
        set(
            CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
            CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START
        )
    }

    val precaptureDeferred = CompletableDeferred()
    session.capture(captureRequest.build(), object: CameraCaptureSession.CaptureCallback() {
        override fun onCaptureCompleted(
            session: CameraCaptureSession,
            request: CaptureRequest,
            result: TotalCaptureResult
        ) {
            // Waiting for this callback ensures the precapture request has been processed
            precaptureDeferred.complete(Unit)
        }
    }, cameraHandler)

    precaptureDeferred.await()

    // Precapture trigger request has been processed, we can wait for AE & AWB convergence now
    repeatingCaptureCallback.awaitAeAwbConvergence()
}

Java

private void runPrecaptureSequence() {
    // Creates a new capture request with CONTROL_AE_PRECAPTURE_TRIGGER_START
    try {
        CaptureRequest.Builder requestBuilder =
                mSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        requestBuilder.addTarget(mPreviewSurface);
        requestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);

        CountDownLatch precaptureLatch = new CountDownLatch(1);
        mSession.capture(requestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                           @NonNull CaptureRequest request,
                                           @NonNull TotalCaptureResult result) {
                Log.d(TAG, "CONTROL_AE_PRECAPTURE_TRIGGER_START processed");
                // Waiting for this callback ensures the precapture request has been processed
                precaptureLatch.countDown();
            }
        }, mCameraHandler);

        precaptureLatch.await();

        // Precapture trigger request has been processed, we can wait for AE & AWB convergence now
        mRepeatingCaptureCallback.awaitAeAwbConvergence();
    } catch (CameraAccessException | InterruptedException e) {
        e.printStackTrace();
    }
}

Alles zusammenfügen

Nachdem alle wichtigen Komponenten bereit sind, können alle Schritte in der in der vorherigen Diskussion und den Codebeispielen angegebenen Reihenfolge ausgeführt werden, wenn ein Bild aufgenommen werden soll, z. B. wenn ein Nutzer auf die Aufnahmeschaltfläche klickt.

Kotlin

// User clicks captureButton to take picture
captureButton.setOnClickListener { v ->
    // Apply the screen flash related UI changes
    whiteColorOverlayView.visibility = View.VISIBLE
    maximizeScreenBrightness()

    // Perform I/O heavy operations in a different scope
    lifecycleScope.launch(Dispatchers.IO) {
        // Enable external flash AE mode and wait for it to be processed
        enableExternalFlashAeMode()

        // Run precapture sequence and wait for it to complete
        runPrecaptureSequence()

        // Start taking picture and wait for it to complete
        takePhoto()

        disableExternalFlashAeMode()
        v.post {
            // Clear the screen flash related UI changes
            restoreScreenBrightness()
            whiteColorOverlayView.visibility = View.INVISIBLE
        }
    }
}

Java

// User clicks captureButton to take picture
mCaptureButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Apply the screen flash related UI changes
        mWhiteColorOverlayView.setVisibility(View.VISIBLE);
        maximizeScreenBrightness();

        // Perform heavy operations in a different thread
        Executors.newSingleThreadExecutor().execute(() -> {
            // Enable external flash AE mode and wait for it to be processed
            enableExternalFlashAeMode();

            // Run precapture sequence and wait for it to complete
            runPrecaptureSequence();

            // Start taking picture and wait for it to complete
            takePhoto();

            disableExternalFlashAeMode();

            v.post(() -> {
                // Clear the screen flash related UI changes
                restoreScreenBrightness();
                mWhiteColorOverlayView.setVisibility(View.INVISIBLE);
            });
        });
    }
});

Beispielbilder

In den folgenden Beispielen sehen Sie, was passiert, wenn das Blinken des Bildschirms falsch implementiert ist und wenn es richtig implementiert ist.

Wenn es falsch gemacht wird

Wenn der Bildschirmblitz nicht richtig implementiert ist, erhalten Sie bei mehreren Aufnahmen, Geräten und Lichtverhältnissen inkonsistente Ergebnisse. Oft sind die aufgenommenen Bilder falsch belichtet oder haben einen falschen Farbton. Bei einigen Geräten sind diese Arten von Fehlern bei bestimmten Lichtverhältnissen stärker zu sehen, z. B. bei wenig Licht statt bei völliger Dunkelheit.

In der folgenden Tabelle finden Sie Beispiele für solche Probleme. Sie wurden in der CameraX-Lab-Infrastruktur aufgenommen, wobei die Lichtquellen eine warmweiße Farbe hatten. Anhand dieser warmweißen Lichtquelle können Sie sehen, dass der blaue Farbton ein tatsächliches Problem ist und nicht der Nebeneffekt einer Lichtquelle.

Umgebung Unterbelichtung Überbelichtung Farbton
Dunkle Umgebung (keine andere Lichtquelle als das Smartphone) Fast vollständig dunkles Foto Zu stark aufgehelltes Foto Foto mit violettem Farbton
Wenig Licht (zusätzliche Lichtquelle mit etwa 3 Lux) Etwas dunkles Foto Zu stark aufgehelltes Foto Foto mit bläulichem Farbton

Wenn es richtig gemacht wird

Wenn die Standardimplementierung für dieselben Geräte und Bedingungen verwendet wird, sehen Sie die Ergebnisse in der folgenden Tabelle.

Umgebung Unterbelichtung (behoben) Überbelichtung (behoben) Farbton (fest)
Dunkle Umgebung (keine andere Lichtquelle als das Smartphone) Foto löschen Foto löschen Klares Foto ohne Farbton
Wenig Licht (zusätzliche Lichtquelle mit etwa 3 Lux) Foto löschen Foto löschen Ein scharfes Foto ohne Farbton

Wie zu sehen, verbessert sich die Bildqualität mit der Standardimplementierung deutlich.