您的位置:首页 > 移动开发 > Cocos引擎

cocos2d-x在Android的运行流程始末

2014-05-16 20:34 267 查看
由于Android的应用层是从Activity开始的,也就是创建完一个Cocos2dx后src文件夹下的Java文件。其中主要看Activity创建时的操作:

protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
}


这个方法很简单,就是调用父类的onCreate方法,也就是,这个自定义的Activity不做什么,把所有的动作都交给了它的父类Cocos2dxActivity去操作

@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sContext = this;
this.mHandler = new Cocos2dxHandler(this);

this.init();

Cocos2dxHelper.init(this, this);
}
在这个方法中主要看Cocos2dxActivity的初始化方法init,Cocos2dxHandler是工具辅助类,不是重点。

public void init() {
/*
Adnroid窗口布局参数的作用(从网上获取)
1)fill_parent
设置一个构件的布局为fill_parent将强制性地使构件扩展,以填充布局单元内尽可能多的空间。这跟Windows控件的dockstyle属性大体一致。设置一个顶部布局或控件为fill_parent将强制性让它布满整个屏幕。
2) wrap_content
设置一个视图的尺寸为wrap_content将强制性地使视图扩展以显示全部内容。以TextView和ImageView控件为例,设置为wrap_content将完整显示其内部的文本和图像。布局元素将根据内容更改大小。设置一个视图的尺寸为wrap_content大体等同于设置Windows控件的Autosize属性为True。
3)match_parent
Android2.2中match_parent和fill_parent是一个意思 .两个参数意思一样,match_parent更贴切,于是从2.2开始两个词都可以用。那么如果考虑低版本的使用情况你就需要用fill_parent了
*/

//初始化窗口布局
ViewGroup.LayoutParams framelayout_params =
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT);
FrameLayout framelayout = new FrameLayout(this);
framelayout.setLayoutParams(framelayout_params);

//初始化Cocos2dx的文本编辑布局
ViewGroup.LayoutParams edittext_layout_params =
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
Cocos2dxEditText edittext = new Cocos2dxEditText(this);
edittext.setLayoutParams(edittext_layout_params);
framelayout.addView(edittext);

//初始化Cocos2dx视图
this.mGLSurfaceView = this.onCreateView();

//把Cocos2dxGLSurfaceView加入到当前的窗口布局中
framelayout.addView(this.mGLSurfaceView);

// Switch to supported OpenGL (ARGB888) mode on emulator
//在模拟器中切换支持OpenGL模式的渲染(ARGB888)
if (isAndroidEmulator())
this.mGLSurfaceView.setEGLConfigChooser(8 , 8, 8, 8, 16, 0);
//设置Cocos2dx的渲染器
this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());
//设置Cocos2dx的文本编辑
this.mGLSurfaceView.setCocos2dxEditText(edittext);

//把显示布局(即Cocos2dx的视图)绑定到Activity上,建立显示窗口
setContentView(framelayout);
}


首先在init中先看this.mGLSurfaceView = this.onCreateView(); this.mGLSurfaceView是一个Cocos2dxGLSurfaceView类。在进入到Cocos2dxGLSurfaceView这个类中可以看到时继承于GLSurfaceView(可以把GLSurfaceView看成一个视图,里面有个方法设置了这个视图的渲染器,然后通过这个渲染器来进行画面的渲染)。

在Android中,GLSurfaceView是一个支持OpenGL的渲染视图,通过继承SurfaceView中的surface来渲染OpenGL

并提供了以下特性(来自于网上)

1> 管理一个surface,这个surface就是一块特殊的内存,能直接排版到android的视图view上。

2> 管理一个EGL display,它能让opengl把内容渲染到上述的surface上。

3> 用户自定义渲染器(render)。

4> 让渲染器在独立的线程里运作,和UI线程分离。

5> 支持按需渲染(on-demand)和连续渲染(continuous)。

6> 一些可选工具,如调试。

重点是自定义的渲染器,也就是Cocos2dx引擎封装的渲染器。进入Cocos2dxRenderer类,看到他是继承GLSurfaceView.Renderer接口,这个接口定义了三个方法:

onSurfaceCreated: 创建GLSurfaceView时被调用,只调用一次,做初始化工作

onSurfaceChanged: 当GLSurfaceView的几何体被改变时被调用

onDrawFrame: 绘制渲染GLSurfaceView

1. onSurfaceCreated:

@Override
public void onSurfaceCreated(final GL10 pGL10, final EGLConfig pEGLConfig) {
Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
this.mLastTickInNanoSeconds = System.nanoTime();
}
private static native void nativeInit(final int pWidth, final int pHeight);
这是一个用JNI调用了C++中的方法(这个C++函数在jni/hellocpp/main.cpp中),如下

void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv*  env, jobject thiz, jint w, jint h)
{
if (!CCDirector::sharedDirector()->getOpenGLView())
{
CCEGLView *view = CCEGLView::sharedOpenGLView();
view->setFrameSize(w, h);

AppDelegate *pAppDelegate = new AppDelegate();
CCApplication::sharedApplication()->run();
}
else
{
ccGLInvalidateStateCache();
CCShaderCache::sharedShaderCache()->reloadDefaultShaders();
ccDrawInit();
CCTextureCache::reloadAllTextures();
CCNotificationCenter::sharedNotificationCenter()->postNotification(EVENT_COME_TO_FOREGROUND, NULL);
CCDirector::sharedDirector()->setGLDefaultValues();
}
}
首先这个方法会判断OpenGLView是否初始化

inline CCEGLView* getOpenGLView(void) { return m_pobOpenGLView; }
进入到CCDirector的构造函数中和init方法中。可以在init方法中找到m_pobOpenGLView = NULL;

接下来的两句是是设置窗口大小。而AppDelegate才是入口的重点。

执行完上面的步骤后开始初始化App然后初始化AppDelegate,AppDelegate的初始化过程也很简单。就实现了一个获取AppDelegate的单例。

AppDelegate的构造方法是个空方法

AppDelegate::AppDelegate() {

}
而AppDelegate的父类是CCApplication类。

class  AppDelegate : private cocos2d::CCApplication
进入到CCApplication的构造函数中(由于不同的平台CCApplication有着不同的实现类,这里要选中Android的实现类)



// sharedApplication pointer
CCApplication * CCApplication::sm_pSharedApplication = 0;

CCApplication::CCApplication()
{
CCAssert(! sm_pSharedApplication, "");
sm_pSharedApplication = this;
}
可以看到是CCApplication是一个单例,里面也没做多少动作。

创建完AppDelegate后开始run

int CCApplication::run()
{
// Initialize instance and cocos2d.
if (! applicationDidFinishLaunching())
{
return 0;
}

return -1;
}
可以看到里面调用了applicationDidFinishLaunching方法,这个方法被AppDelegate重写了,也就是在这里通过多态调用了AppDelegate的applicationDidFinishLaunching方法,然后就可以看到是cocos2dx的开发者很熟悉的Cocos2dx游戏的入口函数了:

bool AppDelegate::applicationDidFinishLaunching() {
// initialize director
CCDirector* pDirector = CCDirector::sharedDirector();
CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();

pDirector->setOpenGLView(pEGLView);

// turn on display FPS
pDirector->setDisplayStats(true);

// set FPS. the default value is 1.0/60 if you don't call this
pDirector->setAnimationInterval(1.0 / 60);

// create a scene. it's an autorelease object
CCScene *pScene = HelloWorld::scene();

// run
pDirector->runWithScene(pScene);

return true;
}
第一步是在导演类中是在OpenGLView,这样在便能使CCDirector::sharedDirector()->getOpenGLView()返回true。然后又设置了是否显示FPS,帧率,并创建了一个场景。并把这个场景当初是游戏的初始场景,压入栈中(由一个CCArray来维护场景),最后运行该场景,整个游戏便开始由这个场景开始启动。

void CCDirector::runWithScene(CCScene *pScene)
{
CCAssert(pScene != NULL, "This command can only be used to start the CCDirector. There is already a scene present.");
CCAssert(m_pRunningScene == NULL, "m_pRunningScene should be null");

pushScene(pScene);
startAnimation();
}
由这个方法可以看下场景是如何运行的,进入pushScene方法中

void CCDirector::pushScene(CCScene *pScene)
{
CCAssert(pScene, "the scene should not null");

m_bSendCleanupToScene = false;

m_pobScenesStack->addObject(pScene);
m_pNextScene = pScene;
}
这里的关键是场景栈的实现也就是m_pobSceneStack变量的维护。

在CCDirector的init方法中可以找到m_pobScenesStack的创建。

m_pobScenesStack = new CCArray();
m_pobScenesStack->init();
这里发现一个很奇怪的写法,首先在new CCArray时肯定是会调用不带参数的构造函数,结果发现里面已经调用了init方法

CCArray::CCArray()
: data(NULL)
{
init();
}
而这里在初始化完CCArray后又调用了init方法,不知表达什么含义

由于调用了CCArray::init方法,所以只初始了存放一个场景的CCArray类

bool CCArray::init()
{
return initWithCapacity(1);
}
bool CCArray::initWithCapacity(unsigned int capacity)
{
ccArrayFree(data);
data = ccArrayNew(capacity);
return true;
}


这里看下CCArray的实现过程

在初始化这个CCArray时,先会清空这个CCArray的所有对象,然后创建ccArray这个结构体。



其实CCArray中的数据便是这个ccArray结构体中的一个指向CCObject*的数组

typedef struct _ccArray {
unsigned int num, max;
CCObject** arr;
} ccArray;




ccArray* ccArrayNew(unsigned int capacity)
{
if (capacity == 0)
capacity = 1;

ccArray *arr = (ccArray*)malloc( sizeof(ccArray) );
arr->num = 0;
arr->arr =  (CCObject**)calloc(capacity, sizeof(CCObject*));
arr->max = capacity;

return arr;
}
可以看到ccArrayNew的执行是一个C语言的写法,先动态申请一块内存,然后初始化内存空间为0,以下是百度百科的一句话:

calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据(注意:由于CCArray存放是CCObject指针,在调用calloc时会连续分配长度为sizeof(CCObject*)的内存大小)

最后把当前的场景存到m_pobScenesStack中,最后让m_pNextScene指向该场景

由此可以看到切换场景时不推荐使用pushScene,因为pushScene会不断的把新的场景加到m_pobScenesStack中,而不释放上一个场景的内存空间,导致内存会急剧增长,如果创建的场景很多的话。所以如果要使用pushScene一般的做法便是在创建完一个场景后插入到m_pobScenesStack的头部,然后调用popScene回收上一个场景的内存。(这样的做法会破坏掉栈的先进后出的原则,是不推荐的做法,因为popScene本来就是一个模拟出栈的方法)

void CCDirector::popScene(void)
{
CCAssert(m_pRunningScene != NULL, "running scene should not null");

m_pobScenesStack->removeLastObject();
unsigned int c = m_pobScenesStack->count();

if (c == 0)
{
end();//没有场景,游戏结束(设置一个参数,使Cocos2dx退出主循环来达到目的)
}
else
{
m_bSendCleanupToScene = true;
m_pNextScene = (CCScene*)m_pobScenesStack->objectAtIndex(c - 1);
}
}


再看下replaceScene

void CCDirector::replaceScene(CCScene *pScene)
{
CCAssert(m_pRunningScene, "Use runWithScene: instead to start the director");
CCAssert(pScene != NULL, "the scene should not be null");

unsigned int index = m_pobScenesStack->count();

m_bSendCleanupToScene = true;
m_pobScenesStack->replaceObjectAtIndex(index - 1, pScene);

m_pNextScene = pScene;
}
void CCArray::replaceObjectAtIndex(unsigned int index, CCObject* pObject, bool bReleaseObject/* = true*/)
{
ccArrayInsertObjectAtIndex(data, pObject, index);
ccArrayRemoveObjectAtIndex(data, index+1);
}
replaceScene的做法也很简单,就是用当前的场景替换掉上一个场景,这样便能把上一个场景的内存清掉。替换的做法也跟上面的类似,先插入新场景,再删除新场景。

先不扯那么多,执行完场景压栈后会调用startAnimation,这个从这个方法开始便会开始又切换到Java应用中

void CCDisplayLinkDirector::startAnimation(void)
{
if (CCTime::gettimeofdayCocos2d(m_pLastUpdate, NULL) != 0)
{
CCLOG("cocos2d: DisplayLinkDirector: Error on gettimeofday");
}

m_bInvalid = false;
#ifndef EMSCRIPTEN
CCApplication::sharedApplication()->setAnimationInterval(m_dAnimationInterval);
#endif // EMSCRIPTEN
}
void CCApplication::setAnimationInterval(double interval)
{
JniMethodInfo methodInfo;
if (! JniHelper::getStaticMethodInfo(methodInfo, "org/cocos2dx/lib/Cocos2dxRenderer", "setAnimationInterval",
"(D)V"))
{
CCLOG("%s %d: error to get methodInfo", __FILE__, __LINE__);
}
else
{
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, interval);
}
}
可以看到从setAnimationInterval方法开始回调java中的org/cocos2dx/lib/Cocos2dxRenderer.setAnimationInterval方法,并传入一个帧率的大小,返回值为void

public static void setAnimationInterval(final double pAnimationInterval) {
Cocos2dxRenderer.sAnimationInterval = (long) (pAnimationInterval * Cocos2dxRenderer.NANOSECONDSPERSECOND);
}
这里只做了初始化渲染的帧率。

这样Cocos2dx的GLSurfaceView的创建边执行完毕了。(在创建过程并没有执行Cocos2dx的主循环)

2. onSurfaceChanged:

第二个方法是空实现,略过

@Override
public void onSurfaceChanged(final GL10 pGL10, final int pWidth, final int pHeight) {
}


3. onDrawFrame:

第三个方法是绘制方法。

@Override
public void onDrawFrame(final GL10 gl) {
Cocos2dxRenderer.nativeRender();
}
private static native void nativeRender();
这也是一个调用C++的方法(这个方法在cocos2dx/platform/android/jni/Java_org_cocos2dx_lib_Cocos2dxRenderer.cpp中)

JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) {
cocos2d::CCDirector::sharedDirector()->mainLoop();
}
终于到了这个主循环了!!!可以看到这个渲染器的绘制方法不干别的,就是调用Cocos2dx的主循环。这里的主循环跟win32平台不太一样,是通过Render来渲染的,是一种间接的调用方式来实现。

这里借鉴上网络上的一段关于Android的渲染逻辑。

游戏引擎要兼顾UI事件和屏幕帧刷新。Android的OpenGL应用采用了UI线程(Main Thread) + 渲染线程(Render Thread)的模式。Activity活在Main Thread(主线程)中,也叫做UI线程。该线程负责捕获与用户交互的信息和事件,并与渲染(Render)线程交互。比如当用户接听电话、切换到其他 程序时,渲染线程必须知道发生了 这些事件,并作出即时的处理,而这些事件及处理方式都是由主线程中的Activity以及其装载的View传递给渲染线程的。

上面的那段话说明Android的渲染便是不断执行Cocos2dx的主函数,因为屏幕每时每刻都会在渲染,每渲染一次调用一次Cocos2dx主函数来绘制游戏的画面。

接下俩看下游戏的结束过程。

Cocos2dx的结束控制是放在主循环中判断:

void CCDisplayLinkDirector::mainLoop(void)
{
if (m_bPurgeDirecotorInNextLoop)//退出主循环的标志
{
m_bPurgeDirecotorInNextLoop = false;
purgeDirector();//清空导演类
}
else if (! m_bInvalid)
{
drawScene();//渲染节点树

// release the objects
CCPoolManager::sharedPoolManager()->pop();//内存管理
}
}
很明显,想让游戏结束,就要让m_bPurgeDirecotorInNextLoop变量为true。而当调用CCDirector::end()方法时便会触发此过程:

void CCDirector::end()
{
m_bPurgeDirecotorInNextLoop = true;
}
(也上面说过当没有导演类中的场景栈中没有场景时便会调用end方法。)

接下来便是purgeDirector方法:

void CCDirector::purgeDirector()
{
//清空定时器
getScheduler()->unscheduleAll();

// don't release the event handlers
// They are needed in case the director is run again
//清空触摸分发器
m_pTouchDispatcher->removeAllDelegates();

if (m_pRunningScene)//如果还有场景在运行,清空此场景
{
m_pRunningScene->onExitTransitionDidStart();
m_pRunningScene->onExit();
m_pRunningScene->cleanup();
m_pRunningScene->release();
}
//防止指向其他地址
m_pRunningScene = NULL;
m_pNextScene = NULL;

//清空场景栈
m_pobScenesStack->removeAllObjects();
//停止继续渲染
stopAnimation();
//清空显示左下角的显示文本
CC_SAFE_RELEASE_NULL(m_pFPSLabel);
CC_SAFE_RELEASE_NULL(m_pSPFLabel);
CC_SAFE_RELEASE_NULL(m_pDrawsLabel);

//清空CCLabelBMFont文本类的缓存数据
CCLabelBMFont::purgeCachedData();

//清空所有缓存
ccDrawFree();
CCAnimationCache::purgeSharedAnimationCache();
CCSpriteFrameCache::purgeSharedSpriteFrameCache();
CCTextureCache::purgeSharedTextureCache();
CCShaderCache::purgeSharedShaderCache();
CCFileUtils::purgeFileUtils();
CCConfiguration::purgeConfiguration();

//清空CCUserDefault和CCNotificationCenter
CCUserDefault::purgeSharedUserDefault();
CCNotificationCenter::purgeNotificationCenter();

ccGLInvalidateStateCache();

CHECK_GL_ERROR_DEBUG();

//清空视图
m_pobOpenGLView->end();
m_pobOpenGLView = NULL;

//删除导演
release();
}


其中主要看m_pobOpenGLView->end()的实现。这个方法每个平台都有自己不同的实现方式,这里选择Android平台。



void CCEGLView::end()
{
//终止进程
terminateProcessJNI();
}
#define  CLASS_NAME "org/cocos2dx/lib/Cocos2dxHelper"
void terminateProcessJNI() {
JniMethodInfo t;

if (JniHelper::getStaticMethodInfo(t, CLASS_NAME, "terminateProcess", "()V")) {
t.env->CallStaticVoidMethod(t.classID, t.methodID);
t.env->DeleteLocalRef(t.classID);
}
}
通过JNI调用org.cocos2dx.lib.Cocos2dxHelper类中的terminateProcess方法

public static void terminateProcess() {
android.os.Process.killProcess(android.os.Process.myPid());
}
可以看到,Android的结束方式便是Android系统自身杀死运行中的Cocos2dx游戏进程。

至此,整个Cocos2dx游戏在Android的运行始末便结束了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐