【问题标题】:Android Game - how correctly handle onPause and onResume if game runs in GLSurfaceView's video threadAndroid 游戏 - 如果游戏在 GLSurfaceView 的视频线程中运行,如何正确处理 onPause 和 onResume
【发布时间】:2019-11-14 09:33:40
【问题描述】:

我从事在 GLSurfaceView 的视频线程中运行的 c++ 游戏(游戏的循环是 GLSurfaceView 的循环,因为它以连续模式运行)。我在如何正确处理 Activity.onPause/onResume 以及我的游戏的 nativePause 时遇到问题。在 nativePause 中,我发布了 opengl 资源和各种大数据。我没有 nativeResume,因为这是由 Android 在我调用 GLSurfaceView.onResume() 时处理的,它再次调用方法 onSurfaceCreated/onSurfaceChanged ,我在其中再次分配我的资源。

这是我现在的做法:

暂停

java中的Activity处理onPause并运行glSurfaceView的自定义nativePause方法:

@Override
protected void onPause() {
    super.onPause();

    glSurfaceView.nativePause();
}

nativePause 向游戏的视频循环发送异步请求。处理视频循环并释放各种资源。接下来,向主线程发送另一条消息,其中包含 nativePause 已完成的信息,我执行 GLSurfaceView.onPause(),这将停止视频线程。

onResume

这个方法实现简单,只用onResume()启动surfaceview的视频线程

@Override
protected void onResume() {
    super.onResume();

    glSurfaceView.onResume();
}

但问题是,onPause 对视频线程进行异步调用并返回到主线程。 Activity.onResume 通常在整个暂停机制完成之前被调用,然后它崩溃或挂起。如果游戏在视频线程中运行,我应该如何正确处理 onPause/onResume?

编辑:

Java 方面:

public class RendererWrapper implements Renderer {
    public native void onSurfaceCreated();
    public native void onSurfaceChanged(int width, int height);
    public native void onDrawFrame();
....
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        onSurfaceCreated();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        onSurfaceChanged(width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        onDrawFrame();
    }
....
}

public class VideoSurface extends GLSurfaceView {
    public VideoSurface(Context context) {
        super(context);

        this.setEGLContextClientVersion(2);
        this.renderer = new RendererWrapper();
        this.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
        this.setRenderer(renderer);
        this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    public native void nativePause();
}

RendererWrapper 中的原生 onDrawFrame() 是主游戏循环。

C++端

void nativePause() {
    InputEvent *event = inputQueue.getWriteEvent();
    event->type = InputEvent::PAUSE;

    inputQueue.incWriteIndex();
}

void onDrawFrame() {
    if (isPaused) {
        return;
    }

    InputEvent *event = inputQueue.getReadEvent();
    if (event) {
        inputQueue.incReadIndex();

        ....
        if (event->type == InputEvent::PAUSE) {
            release();
            return;
        }
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor(1.0f, 0.0f, 0.0f, 0.0f);

    game->draw();
}

EDIT2:

class EventQueue {
public:
    static const int size = 256;
    volatile int readIndex;
    volatile int writeIndex;
    InputEvent *events;

    EventQueue() {
        readIndex = 0;
        writeIndex = 0;
        events = new InputEvent[size];
    }

    InputEvent* getReadEvent() {
        if (writeIndex == readIndex) {
            return 0; // queue empty
        }
        return events + readIndex;
    }

    InputEvent* getWriteEvent() {
        if (((writeIndex + 2) & (size - 1)) == readIndex) {
            return 0; // queue full
        }
        return events + writeIndex;
    }

    void incReadIndex() {
        readIndex = (readIndex + 1) & (size - 1);
    }

    void incWriteIndex() {
        writeIndex = (writeIndex + 1) & (size - 1);
    }
};

【问题讨论】:

  • 如果是我,我会阻止 nativePause() 直到它完成。我相信在Activity.onPause() 已经返回之后调用GLSurfaceView.onPause() 是非法的,您可能正在找出原因。使nativePause() 同步应该相当容易;您仍然可以指望您的 OpenGL 线程存在;我假设您不依赖对onDrawFrame() 的任何干预调用来进行清理;您应该能够阻塞直到您的 OpenGL 线程通知条件变量或其他东西,nativePause() 操作已完成。
  • 谢谢,你的意思是简单的Thread.sleep()?
  • 不,我的意思是使用已建立的线程通信技术。要么 1.) 将消息从线程 A 传递给线程 B,让线程 B 进行清理,然后将消息传递给线程 A(然后解除阻塞并从 nativePause() 返回),或者 2.) 让线程 A 获取锁,进行清理,释放锁,然后从nativePause() 返回。 pthread_mutex_lockpthread_cond_wait/pthread_cond_signal 可能是您想要使用的,在这里。如果您需要帮助,最好将nativePause() 的所有相关代码添加到您的问题中。
  • 谢谢,我添加了nativePause现在如何工作的代码。它创建输入事件并将其放入输入队列。这个队列是在绘制线程的循环中读取的
  • 看起来你已经在使用线程间通信了,所以我们为什么不坚持你已经习惯的东西。我不能确切地知道inputQueue 是什么类型的对象。你有一些文件吗?它是否有 blocking 版本的 getReadEvent() 方法?另外,我认为重点是让 OpenGL 线程调用 release()(不管是什么),否则事情会中断?

标签: android game-engine onresume glsurfaceview onpause


【解决方案1】:

小心那个反复无常的把戏。在许多情况下,it doesn't do what you think it does。如果它到目前为止有效,那可能是由于运气。

由于InputQueue 类并不真正适合此,我将向您展示如何使用条件变量解决问题(代码未测试):

#include <pthread.h>

pthread_cond_t cond;
pthread_mutex_t mutex;
bool released = false;

...

pthread_cond_init(&cond, NULL);    //TODO: check return value
pthread_mutex_init(&mutex, NULL);    //TODO: check return value

...

void nativePause() {
    InputEvent *event = inputQueue.getWriteEvent();
    event->type = InputEvent::PAUSE;

    inputQueue.incWriteIndex();

    //Wait for the OpenGL thread to accomplish the release():
    pthread_mutex_lock(&mutex);
    while(!released) {
        pthread_cond_wait(&cond, &mutex);   //Expected to always return 0.
    }
    pthread_mutex_unlock(&mutex);
}

void onDrawFrame() {

    ...

    if (event) {
        inputQueue.incReadIndex();

        ....
        if (event->type == InputEvent::PAUSE) {
            release();

            pthread_mutex_lock(&mutex);
            released = true;
            pthread_cond_broadcast(&cond);    //Notifies the nativePause() thread, which is supposed to be blocking in the condition loop, at this point.
            pthread_mutex_unlock(&mutex);

            return;
        }
    }

    ...    
}

...

void nativeCleanup()
{
    pthread_cond_destroy(&cond);    //Expected to return 0.
    pthread_mutex_destroy(&mutex);    //Expected to return 0.
}

至少,这应该可行。代码假定 OpenGL 线程保证存在,直到 onPause() 返回之后。我想这是真的;我真的不记得了。

【讨论】:

  • 哇!我刚刚在模拟器中测试了它,它工作得很好!我今天将在真实设备上测试它并让你知道,但这似乎正是我需要的:)
  • 所以,据我所知 - 它有效。真的非常感谢! :) 现在我有很好的暂停/恢复机制。我可以发布大数据,保存设置和释放数据库连接。我的最后一个问题是关于整个应用程序的退出。我在按下按钮时调用 Activity.finish。这调用了 Activity.onPause,其中(在这种情况下)我运行 nativeDestroy(销毁视频线程中分配的对象)。但是,如果我的应用程序长时间暂停,android 可以发送 Activity.onDestroy 事件。我想我也应该在这个事件中运行 nativeDestroy,但我不知道如何,因为视频线程已经暂停......
  • @user1063364 不客气,别忘了接受这个答案!你是对的,onDestroy() 最终可能会被发送,但它总是在onPause() 之后,如果要带回ActivityonCreate()/onResume()/onSurfaceCreated() 循环将跟随它。所以,我要说的是,我认为您也需要在onPause() 中调用nativeDestroy()如果 它需要OpenGL 线程。 (我还没有看到nativeDestroy() 的文档,所以我不知道。)然后你需要能够重建onResume()/onSurfaceCreated() 中的所有内容。
  • 问题是,在 onResume 和 onSurfaceCreated() 可能不会被调用之后,opengl 上下文可能不会丢失。但是会调用 onDraw,可能我应该在这里重新创建所有内容。
  • @user1063364 我认为你可能是对的。也许你可以用一个变量来跟踪它;如果第二次调用onSurfaceCreated(),您可以推断出上下文丢失了,所以请快速执行nativeDestroy(),然后执行nativeCreate()(或其他)以使自己回到正轨。只是一个想法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多