这几天在进行Lives2D的Android移植,lib的编译都OK,然后也简单的跑起来了,然后开始添加Android端的MediaPlayer来播放音效。
MediaPlayer加入之后,问题就出现很多了,JNIEnv的存储、JMethod获取都是比较繁琐的事情,我对Android也不怎么熟悉,花了很多时间。
但是测试发现,游戏各种崩溃。
测试场景如上,一个按钮,点击就会调用JNI 来PlayAudio。
一开始崩溃原因最多的就是 use JNIEnv in other thread 。 我是迷糊的,大概估计意思是JNIEnv不能再GLES的Render Thread里面使用,于是百度谷歌了一番,最后还是DownLoad了 Cocos2d的JNIHelper 过来用。
之后就没有碰到JNIEnv 线程错误了。
继续测试,仍然崩溃,而且直接就是 Fatal signal 11 (SIGSEGV) 错误,顿时眼瞎,瑟瑟发抖。
慢慢排查。
转自http://www.liveslives.com/ http://blog.csdn.net/huutu
排查的重心放到了Lua 传递 Function 给C++ 调用的实现上,我对Lua也不怎么懂,这种实现在tolua++ 处理过后还要手动修改生成的脚本,所以我一直本着怀疑的态度,虽然在Win上没有问题。
来看看我的实现。
UIButton 有一个 SetOnClickListener 接口,设置点击回调
void UIButton::SetOnClickListener(lua_State * varlua_State)
{
mOnClickListener = new LuaFunctionPoint();
mOnClickListener->mlua_State = varlua_State;
mOnClickListener->mFunctionIndexInStack = -2;
mOnClickListener->mArgumentIndexInStack = -1;
mOnClickListener = LuaEngine::GetSingleton()->GetLuaFunction(mOnClickListener);
}
LuaFunctionPoint 是用来保存函数指针 参数指针的一个结构
class LuaFunctionPoint
{
public:
lua_State* mlua_State;
public:
int mFunctionIndexInStack;
int mArgumentIndexInStack;
public:
int mFunctionPoint;
int mArgumentPoint;
public:
LuaFunctionPoint() :mFunctionPoint(LUA_REFNIL), mArgumentPoint(LUA_REFNIL)
{
}
};
然后通过luaL_ref 去获取指针。
LuaFunctionPoint* LuaEngine::GetLuaFunction(LuaFunctionPoint* varLuaFunctionPoint)
{
lua_pushvalue(varLuaFunctionPoint->mlua_State, varLuaFunctionPoint->mFunctionIndexInStack);
varLuaFunctionPoint->mFunctionPoint = luaL_ref(varLuaFunctionPoint->mlua_State, LUA_REGISTRYINDEX);
lua_pushvalue(varLuaFunctionPoint->mlua_State, varLuaFunctionPoint->mArgumentIndexInStack);
varLuaFunctionPoint->mArgumentPoint = luaL_ref(varLuaFunctionPoint->mlua_State, LUA_REGISTRYINDEX);
return varLuaFunctionPoint;
}
最后当有点击事件的时候,调用回调
void LuaEngine::ExecuteLuaFunction(LuaFunctionPoint* varLuaFunctionPoint)
{
lua_rawgeti(m_pLua_State, LUA_REGISTRYINDEX, varLuaFunctionPoint->mFunctionPoint);
lua_rawgeti(m_pLua_State, LUA_REGISTRYINDEX, varLuaFunctionPoint->mArgumentPoint);
lua_call(m_pLua_State, 1, 0);
}
而且后续测试崩溃 ,lua 中也爆出各种莫名其妙的错误。
我觉得可能是我这里的实现,破坏了 lua 的堆栈。
于是,我把这里的回调设置换了一种实现。
首先 UIButton 中,不再保存Function 的 指针了,而是保存FunctionName。
void UIButton::SetOnClickListener(const char* varOnClickListener)
{
mOnClickListener = new LuaFunctionPoint();
mOnClickListener->mFunctionName = varOnClickListener;
}
self.mUIButton:SetOnClickListener(RegisterLuaFunction(function()
print("click button")
end))
RegisterLuaFunction 用来产生函数唯一标志。
然后在点击事件过来的时候,用普通的方式,调用函数
void LuaEngine::ExecuteLuaFunction(LuaFunctionPoint* varLuaFunctionPoint)
{
Helper::LOG("------ExecuteLuaFunction Begin---------");
if (mErrorPause)
{
return;
}
lua_getglobal(m_pLua_State, "CallLuaFunction");
if (!lua_isfunction(m_pLua_State, -1))
{
Helper::LOG("%s is not function", "CallLuaFunction");
return;
}
tolua_pushstring(m_pLua_State, varLuaFunctionPoint->mFunctionName);
int ret = lua_pcall(m_pLua_State, 1, 0, 0);
if (ret != 0)
{
PrintError();
mErrorPause = true;
}
Helper::LOG("------ExecuteLuaFunction End---------");
}
然而,测试还是一如既往的崩溃,仍然是Lua中爆出各种莫名其妙的错误。
肯定是Lua的堆栈被破坏了,但是原因呢?
灵光一闪! 对的,我总是这样。。
堆栈破坏、JNIEnv 在多个线程被使用。
这两者在我脑海中纠缠,爆炸!
Lua是不能跨线程使用的!
而在我的代码中,Render是在GLThread中,而TouchEvent 则是在Activity 线程中!!
惊!!
于是修改了Java端的代码,把TouchEvent 放到Hashtable中,然后在GLThread中去获取。
Hashtable mMotionEventHashtable=new Hashtable();
然后在onTouchEvent 里面,把Event存入 Hashtable ,记得Lock
public boolean onTouchEvent(MotionEvent event)
{
float x= event.getX();
float y=event.getY();
Renderer.reentrantLock.lock();
mMotionEventHashtable.put(event.getAction(), new Vector2((int)x, (int)y));
Renderer.reentrantLock.unlock();
return true;
}
public void onDrawFrame(GL10 gl)
{
//处理点击事件
reentrantLock.lock();
Iterator> iterator=mMotionEventHashtable.entrySet().iterator();
while(iterator.hasNext())
{
Entry entry=iterator.next();
int event=entry.getKey();
Vector2 pos=entry.getValue();
switch (event) {
case MotionEvent.ACTION_DOWN:
Log.i("Lives2D", "onTouch x:"+pos.x+" y:"+pos.y);
nativeWrap.onTouch(pos.x, pos.y);
break;
case MotionEvent.ACTION_UP:
Log.i("Lives2D", "onTouchRelease x:"+pos.x+" y:"+pos.y);
nativeWrap.onTouchRelease(pos.x, pos.y);
break;
default:
break;
}
}
mMotionEventHashtable.clear();
reentrantLock.unlock();
//刷帧
end = System.currentTimeMillis();
long time = end - begin;
if(time < frame_time)
{
try
{
Thread.sleep(frame_time - time);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
begin = System.currentTimeMillis();
//Log.i("Lives2D", "GLThread:"+Thread.currentThread().getId());
nativeWrap.step(0.333f);
}
如此,问题解决。
详细代码Github见:
https://github.com/ThisisGame/Lives2D/tree/V5_SwitchToLua_Windows