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

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

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

В этой схеме я могу делать соответствующие вызовы, такие как открыть / отпустить камеру и запустить / остановить предварительный просмотр в 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");
    }
}

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

5 ответов

Решение

Редактировать: если 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;
       }
   }
}

а затем в деятельности:

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

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

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

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
}

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

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

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

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

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

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

Активность на экране? Если это так, SurfaceHolder.Callback не будет, потому что Surface все еще на экране.

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

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

Прямо сейчас эти взаимодействия порядка обычно могут называться неопределенным поведением для целей разработки.

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

Например, мой 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

импорт android.util.Log; import android.util.SparseArray;

/ ** * Создано woliver 2016/06/24. * * Хост-среда Android, определяет жизненный цикл активности для OnCreate, onStart, onResume, onPause, onStop, onDestory, * где мы должны освободить память и дескрипторы для использования другими приложениями. * При возобновлении мы обязаны время от времени перепривязывать и активировать эти предметы с другими объектами. * Как правило, эти другие объекты предоставляют методы обратного вызова из среды хоста, которые предоставляют * onCreated и onDestroy, в которых мы можем связываться только с этим объектом из OnCreated и и освобождаем * out bind onDestory. * Эти типы методов обратного вызова, время выполнения которых контролируется нашей средой хоста *, и они не являются гарантией того, что поведение / порядок выполнения жизненного цикла действия и эти методы обратного вызова * остаются согласованными. * В целях разработки взаимодействия и порядок выполнения можно технически назвать неопределенными *, так как это зависит от реализации реализации хоста, 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();
}

Пример реализации и использования класса, который имеет дело с неопределенным поведением разработчиков среды хоста 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);
    }
}
Другие вопросы по тегам