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


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

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

в этой схеме я могу делать соответствующие вызовы, такие как open/release camera и start/stop preview in 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 65

5 ответов:

Edit: если targetSDK больше 10, помещая приложение в спящий режим вызовов onPauseиonStop. источник

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

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

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

как a правило, я управляю камерой в классе, который реализует обратные вызовы 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();
}

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

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

private SurfaceView preview;

preview is init in onCreate метод. В onResume метод set 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.Обратный вызов связан с его поверхностью.

есть ли активность на экране? Если это так, то не будет SurfaceHolder.Обратный вызов, потому что поверхность все еще находится на экране.

чтобы управлять любым SurfaceView, вы можете обрабатывать его только в onPause/onResume. Для Поверхностного Держателя.Обратный вызов, вы можете использовать его, если поверхность изменяется (создается, sizechanged и уничтожается), например, инициализировать 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.утиль.Бревно; импорт андроид.утиль.SparseArray;

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