Подтвердить что ты не робот

Как обратные вызовы SurfaceHolder связаны с жизненным циклом деятельности?

Я пытаюсь реализовать приложение, которое требует предварительного просмотра камеры на поверхности. Поскольку я вижу вещи, как жизненные циклы активности, так и поверхности состоят из следующих состояний:

  • Когда я впервые запускаю свою активность: onResume()->onSurfaceCreated()->onSurfaceChanged()
  • Когда я покидаю свою активность: onPause()->onSurfaceDestroyed()

В этой схеме я могу выполнять соответствующие вызовы, такие как open/release camera и start/stop preview в onPause/onResume и onSurfaceCreated()/onSurfaceDestroyed().

Он отлично работает, если я не заблокирую экран. Когда я запускаю приложение, затем заблокируйте экран и разблокируйте его позже, я вижу:

onPause() - и ничего больше после того, как экран заблокирован - затем onResume() после разблокировки - и никаких поверхностных обратных вызовов после этого. Фактически, после нажатия кнопки питания и вызывается экран onResume(), но экран блокировки по-прежнему активен, поэтому перед тем, как активность станет видимой.

С этой схемой я получаю черный экран после разблокировки, и никаких обратных вызовов не вызываются.

Здесь фрагмент кода, который не связан с фактической работой с камерой, а вызывает обратные вызовы SurfaceHolder. Вышеупомянутая проблема воспроизводится даже с помощью этого кода на моем телефоне (обратные вызовы вызываются в обычной последовательности, когда вы нажимаете кнопку "Назад", но при блокировке экрана отсутствуют):

class Preview extends SurfaceView implements SurfaceHolder.Callback {

    private static final String tag= "Preview";

    public Preview(Context context) {
        super(context);
        Log.d(tag, "Preview()");
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        Log.d(tag, "surfaceCreated");
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.d(tag, "surfaceDestroyed");
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        Log.d(tag, "surfaceChanged");
    }
}

Любые идеи о том, почему поверхность остается неразрушенной после приостановки операции? Кроме того, как вы справляетесь с жизненным циклом камеры в таких случаях?

4b9b3361

Ответ 1

Изменить:, если targetSDK больше 10, при этом приложение отключает вызовы onPause и onStop. Источник

Я просмотрел жизненный цикл как Activity, так и SurfaceView в крошечном приложении для камеры на моем пряничном телефоне. Вы совершенно правы; при нажатии кнопки питания поверхность не будет разрушена, чтобы перевести телефон в режим сна. Когда телефон переходит в режим сна, активность выполняет onPause. (И не делает onStop.) Он выполняет onResume, когда телефон просыпается, и, как вы указываете, он делает это, пока экран блокировки все еще виден и принимает вход, что немного странно. Когда я делаю действие невидимым, нажимая кнопку "Домой", активность выполняет как onPause, так и onStop. Что-то вызывает обратный вызов surfaceDestroyed в этом случае между концом onPause и началом onStop. Это не очень очевидно, но это кажется очень последовательным.

Когда кнопка питания нажата, чтобы спящий телефон, если только что-то явно не сделано, чтобы остановить его, камера продолжает работать! Если у меня есть камера, выполняющая обратный вызов для каждого кадра предварительного просмотра с помощью Log.d() там, заявления журнала продолжают поступать, пока телефон притворяется спать. Я думаю, что это очень подлый.

В качестве другой путаницы обратные вызовы surfaceCreated и surfaceChanged происходят после onResume в активности, если создается поверхность.

Как правило, я управляю камерой в классе, который реализует обратные вызовы SurfaceHolder.

class Preview extends SurfaceView implements SurfaceHolder.Callback {
    private boolean previewIsRunning;
    private Camera camera;

    public void surfaceCreated(SurfaceHolder holder) {
        camera = Camera.open();
        // ...
        // but do not start the preview here!
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // set preview size etc here ... then
        myStartPreview();
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        myStopPreview();
        camera.release();
        camera = null;
    }

   // safe call to start the preview
   // if this is called in onResume, the surface might not have been created yet
   // so check that the camera has been set up too.
   public void myStartPreview() {
       if (!previewIsRunning && (camera != null)) {
           camera.startPreview();
           previewIsRunning = true;
       }
   }

   // same for stopping the preview
   public void myStopPreview() {
       if (previewIsRunning && (camera != null)) {
           camera.stopPreview();
           previewIsRunning = false;
       }
   }
}

а затем в Activity:

@Override public void onResume() {
    preview.myStartPreview();  // restart preview after awake from phone sleeping
    super.onResume();
}
@Override public void onPause() {
    preview.myStopPreview();  // stop preview in case phone is going to sleep
    super.onPause();
}

и это, похоже, работает нормально для меня. События вращения приводят к разрушению и воссозданию активности, что приводит к тому, что SurfaceView также будет уничтожен и воссоздан.

Ответ 2

Еще одно простое решение, которое прекрасно работает - чтобы изменить видимость поверхности предварительного просмотра.

private SurfaceView preview;

Предварительный просмотр является init в onCreate. В методе onResume установите View.VISIBLE для поверхности предварительного просмотра:

@Override
public void onResume() {
    preview.setVisibility(View.VISIBLE);
    super.onResume();
}

и соответственно в onPause установить видимость View.GONE:

@Override
public void onPause() {
    super.onPause();
    preview.setVisibility(View.GONE);
    stopPreviewAndFreeCamera(); //stop and release camera
}

Ответ 3

SurfaceHolder.Callback связан с его поверхностью.

Является ли действие на экране? Если это так, не будет SurfaceHolder.Callback, потому что поверхность по-прежнему находится на экране.

Чтобы управлять любым SurfaceView, вы можете обрабатывать его только в onPause/onResume. Для SurfaceHolder.Callback вы можете использовать его, если поверхность изменена (создана, изменена по размеру и уничтожена), например, инициализируйте openGL при создании поверхности и уничтожьте openGL, когда surfaceDestroyed и т.д.

Ответ 4

Благодаря обоим предыдущим ответам мне удалось сделать работу с предварительным просмотром камеры, когда вы возвращаетесь с фона или экрана блокировки.

Как упоминалось в @e7fendy, обратный вызов SurfaceView не будет вызываться во время блокировки экрана, поскольку вид поверхности все еще отображается для системы.

Следовательно, как советовал @validcat, вызывая preview.setVisibility(View.VISIBLE); и preview.setVisibility(View.GONE); соответственно в onPause() и onResume(), принудительное отображение поверхности непосредственно и будет вызывать обратные вызовы.

К тому времени решение от @emrys57 плюс эти два метода видимости, приведенные выше, заставит ваш просмотр камеры работать прямо:)

Поэтому я могу дать только +1 каждому из вас, как вы все это заслужили;)

Ответ 5

Вот альтернативное решение для всех методов обратного вызова, которые могут быть подвергнуты тем же undefined поведениям порядка событий с циклом активности. Если вы не собираетесь проверять весь код андроида для каждого обратного вызова, который вы используете для определения триггера происхождения и кто контролирует реализации, и надеемся, что база кода в будущем не изменится, можно действительно сказать, что порядок событий между обратными вызовами и события жизненного цикла активности могут быть гарантированы.

В настоящее время эти взаимодействия порядка обычно можно назвать поведением undefined для целей разработки.

Лучше всего было бы всегда правильно обрабатывать это поведение undefined, так что это никогда не будет проблемой в первую очередь, гарантируя, что порядок определяется поведением.

Мой Sony Xperia, например, во сне, переключает мое текущее приложение, уничтожая приложение, а затем перезапускает его и помещает в состояние паузы, верьте или нет.

Сколько проверок поведения заказа событий google предоставляет в их SDK, поскольку специальная тестовая сборка для среды хоста использует, я не знаю, но они определенно должны прилагать усилия для обеспечения того, чтобы поведение заказов событий было заблокировано, строго по этому вопросу.

https://code.google.com/p/android/issues/detail?id=214171&sort=-opened&colspec=ID%20Status%20Priority%20Owner%20Summary%20Stars%20Reporter%20Opened

import android.util.Log; import android.util.SparseArray;

/**  * Создано woliver 2016/06/24.  *  * Android-хост-среда, диктует жизненный цикл активности для OnCreate, onStart, onResume, onPause, onStop, onDestory,  * где нам требуется освободить память и ручки для использования другими приложениями.  * Когда мы возобновляем время от времени, мы должны повторно отследить и активировать эти предметы другими объектами.  * Обычно эти другие объекты предоставляют методы обратного вызова из среды хоста, которые обеспечивают  * onCreated и onDestroy, в котором мы можем привязываться только к этому объекту из OnCreated и  * вне bind onDestory.  * Эти типы методов обратного вызова, время для запуска - это контроллер нашей среды-хозяина  * и их нет гарантий, что поведение/порядок выполнения жизненного цикла активности и эти методы обратного вызова  * остается непротиворечивым.  * В целях развития взаимодействия и порядок исполнения можно технически называть undefined  * поскольку это зависит от реализации реализации хоста, samsung, sony, htc.  *  * См. Следующий документ разработчика: https://developer.android.com/reference/android/app/Activity.html  * Цитата:  * Если активность полностью закрыта другой деятельностью, она прекращается. Он по-прежнему сохраняет все состояние  * и информация о члене, однако, он больше не отображается пользователю, поэтому его окно  * скрыт, и он часто будет убит системой, если в другом месте требуется память.  * EndQuato:  *  * Если активность не скрыта, то любые обратные вызовы, которые, как ожидалось, были бы вызваны хостом  * система не будет вызвана, например, интерфейс OnCreate и OnDestory интерфейс SurfaceView обратного вызова.  * Это означает, что вам придется остановить объект, привязанный к SurfaceView, например камеру  * в режиме паузы и никогда не будет перепроверять объект, поскольку обратный вызов OnCreate никогда не будет вызван.  *  */

public abstract class WaitAllActiveExecuter<Size>
{
     private SparseArray<Boolean> mReferancesState = null;

// Use a dictionary and not just a counter, as hosted code
// environment implementer may make a mistake and then may double executes things.
private int mAllActiveCount = 0;
private String mContextStr;

public WaitAllActiveExecuter(String contextStr, int... identifiers)
{
    mReferancesState = new SparseArray<Boolean>(identifiers.length);

    mContextStr = contextStr;

    for (int i  = 0; i < identifiers.length; i++)
        mReferancesState.put(identifiers[i], false);
}

public void ActiveState(int identifier)
{
    Boolean state = mReferancesState.get(identifier);

    if (state == null)
    {
        // Typically panic here referance was not registered here.
        throw new IllegalStateException(mContextStr + "ActiveState: Identifier not found '" + identifier + "'");
    }
    else if(state == false){

        mReferancesState.put(identifier, true);
        mAllActiveCount++;

        if (mAllActiveCount == mReferancesState.size())
            RunActive();
    }
    else
    {
        Log.e(mContextStr, "ActivateState: called to many times for identifier '" + identifier + "'");
        // Typically panic here and output a log message.
    }
}

public void DeactiveState(int identifier)
{
    Boolean state = mReferancesState.get(identifier);

    if (state == null)
    {
        // Typically panic here referance was not registered here.
        throw new IllegalStateException(mContextStr + "DeActiveState: Identifier not found '" + identifier + "'");
    }
    else if(state == true){

        if (mAllActiveCount == mReferancesState.size())
            RunDeActive();

        mReferancesState.put(identifier, false);
        mAllActiveCount--;
    }
    else
    {
        Log.e(mContextStr,"DeActiveState: State called to many times for identifier'" + identifier + "'");
        // Typically panic here and output a log message.
    }
}

private void RunActive()
{
    Log.v(mContextStr, "Executing Activate");

    ExecuterActive();
}

private void RunDeActive()
{
    Log.v(mContextStr, "Executing DeActivate");

    ExecuterDeActive();
}


abstract public void ExecuterActive();

abstract public void ExecuterDeActive();
}

Пример реализации и использования класса, с которым связано или поведение undefined среды хоста Android исполнители.

private final int mBCTSV_SurfaceViewIdentifier = 1;
private final int mBCTSV_CameraIdentifier = 2;

private WaitAllActiveExecuter mBindCameraToSurfaceView =
        new WaitAllActiveExecuter("BindCameraToSurfaceViewe", new int[]{mBCTSV_SurfaceViewIdentifier, mBCTSV_CameraIdentifier})
{
    @Override
    public void ExecuterActive() {

        // Open a handle to the camera, if not open yet and the SurfaceView is already intialized.
        if (mCamera == null)
        {
            mCamera = Camera.open(mCameraIDUsed);

            if (mCamera == null)
                throw new RuntimeException("Camera could not open");

            // Look at reducing the calls in the following two methods, some this is unessary.
            setDefaultCameraParameters(mCamera);
            setPreviewSizesForCameraFromSurfaceHolder(getSurfaceHolderForCameraPreview());
        }

        // Bind the Camera to the SurfaceView.
        try {
            mCamera.startPreview();
            mCamera.setPreviewDisplay(getSurfaceHolderForCameraPreview());
        } catch (IOException e) {

            e.printStackTrace();
            ExecuterDeActive();

            throw new RuntimeException("Camera preview could not be set");
        }
    }

    @Override
    public void ExecuterDeActive() {

        if ( mCamera != null )
        {
            mCamera.stopPreview();

            mCamera.release();
            mCamera = null;
        }
    }
};

@Override
protected void onPause() {


    mBindCameraToSurfaceView.DeactiveState(mBCTSV_CameraIdentifier);

    Log.v(LOG_TAG, "Activity Paused - After Super");
}

@Override
public void  onResume() {

    mBindCameraToSurfaceView.ActiveState(mBCTSV_CameraIdentifier);
}

private class SurfaceHolderCallback implements SurfaceHolder.Callback
{
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    Log.v(LOG_TAG, "Surface Changed");

    }

    public void surfaceCreated(SurfaceHolder surfaceHolder) {

        Log.v(LOG_TAG, "Surface Created");
        mBindCameraToSurfaceView.ActiveState(mBCTSV_SurfaceViewIdentifier);
    }

    public void surfaceDestroyed(SurfaceHolder arg0) {

        Log.v(LOG_TAG, "Surface Destoryed");
        mBindCameraToSurfaceView.DeactiveState(mBCTSV_SurfaceViewIdentifier);
    }
}