原创文章,转载请注明连接 http://blog.csdn.net/hoytgm/article/details/32715655
Android主要都是Java代码,但是我们建立OpenGLES工程,主要还是调用C/C++代码,所以我们需要使用Jni(Java Native Interface)。这里代码的东西比较少,很多都是贴一些Java相关的,本人Java不太熟,所以很多地方就不解释了哈,要是有更好的代码,欢迎指正哈。
根目录下建立AndroidManifest.xml文件,内容照贴
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hoytgm" >
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name="hoytgm"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
然后就是Android.mk,这个是Android下面的makefile,大家应该比我懂吧
#
# Build
#
LOCAL_PATH := $(call my-dir)
# Build apk
include $(CLEAR_VARS)
LOCAL_MODULE_PATH := $(LOCAL_PATH)/bin/
LOCAL_PACKAGE_NAME := hoytgm
LOCAL_SRC_FILES := \
$(call all-subdir-java-files)
LOCAL_JNI_SHARED_LIBRARIES := \
libhoytgm
LOCAL_MODULE_TAGS := eng optional
include $(BUILD_PACKAGE)
# Build native shared object
include $(CLEAR_VARS)
LOCAL_MODULE := libhoytgm
LOCAL_SRC_FILES := \
jni/jniapi.cpp \
./main.cpp
LOCAL_C_INCLUDES := \
./
LOCAL_SHARED_LIBRARIES := \
liblog \
libEGL \
libGLESv2 \
libandroid
LOCAL_CFLAGS := \
-Wall \
-DANDROID
LOCAL_MODULE_TAGS := eng optional
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
建立三个文件夹和相应的文件,参照我的层次哈,打括号是指目录
(jni) -> jniapi.cpp
(src) -> (com)--> (hoytgm) -> hoytgm.java
(res) -> (drawable)
(res) -> (layout) -> main.xml
(res) -> (values) -> strings.xml
然后是main.xml文件的内容
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<SurfaceView
android:id="@+id/surfaceview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#ffffffff"
android:textSize="60dp"
/>
</RelativeLayout>
和strings.xml的内容
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">hoytgm</string>
</resources>
package com.hoytgm;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.View.OnClickListener;
import android.util.Log;
然后实现一个SurfaceHolder的类,作为主要的activity
public class hoytgm extends Activity implements SurfaceHolder.Callback {}
在类里面添加一个成员用于打印标志
private static String TAG = "### hoytgm: ";
重载create函数
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate()");
setContentView(R.layout.main);
SurfaceView surfaceView = (SurfaceView)findViewById(R.id.surfaceview);
surfaceView.getHolder().addCallback(this);
surfaceView.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
}
});
}
重载几个状态的函数
@Override
protected void onStart() {
super.onStart();
Log.i(TAG, "onStart()");
nativeOnStart();
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, "onResume()");
nativeOnResume();
}
@Override
protected void onPause() {
super.onPause();
Log.i(TAG, "onPause()");
nativeOnPause();
}
@Override
protected void onStop() {
super.onStop();
Log.i(TAG, "onStop()");
nativeOnStop();
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
Log.i(TAG, "surfaceChanged()");
nativeSetSurface(holder.getSurface());
}
public void surfaceCreated(SurfaceHolder holder) {
Log.i(TAG, "surfaceCreated()");
}
public void surfaceDestroyed(SurfaceHolder holder) {
nativeSetSurface(null);
}
这里大家会发现一些native*()的函数,这些就是需要jniapi.cpp去实现的函数,这里把这些函数的声明也加到我们的这个类里面,并加载这个库
public static native void nativeOnStart();
public static native void nativeOnResume();
public static native void nativeOnPause();
public static native void nativeOnStop();
public static native void nativeSetSurface(Surface surface);
static {
System.loadLibrary("hoytgm");
}
好了,到这里,我们的hoytgm.java就完成了,java相关的代码也结束了,可以开始写jniapi.cpp文件了,先一次性贴出整个文件的代码吧,因为这里就是按照之前的路径和jni的格式把hoytgm.java里面的几个native开头的函数实现了,然后自己创建一个线程用于做Opengl es相关的渲染工作。
/*
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <jni.h>
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/jni.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#undef LOG_TAG
#define LOG_TAG "### hoytgm:"
#define MAX_SIZE 1024
#define LOG_I(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOG_E(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
ANativeWindow* navWindow = 0;
EGLDisplay eglDisplay = 0;
EGLContext eglContext = 0;
EGLSurface eglSurface = 0;
pthread_t mainThreadId = 0;
extern bool Render();
extern void fini();
extern bool init();
enum
{
KEYCODE_BACK = 4
};
enum RenderMessage
{
MSG_NONE = 0,
MSG_WINDOW_SET,
MSG_RENDER_LOOP_EXIT,
};
RenderMessage rendermsg;
void destroy()
{
LOG_I("destroy()");
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(eglDisplay, eglContext);
eglDestroySurface(eglDisplay, eglSurface);
eglTerminate(eglDisplay);
eglDisplay = EGL_NO_DISPLAY;
eglSurface = EGL_NO_SURFACE;
eglContext = EGL_NO_CONTEXT;
}
void renderLoop()
{
bool done = false;
LOG_I("renderLoop()");
static unsigned int framecount = 0;
while(!done)
{
if(framecount < 1)
{
LOG_I("no render occurs, sleep 500 ms");
usleep(500);
}
switch(rendermsg)
{
case MSG_WINDOW_SET:
init();
break;
case MSG_RENDER_LOOP_EXIT:
done = true;
fini();
destroy();
break;
default:
break;
}
rendermsg = MSG_NONE;
if(eglDisplay)
{
done = Render();
framecount++;
if(!eglSwapBuffers(eglDisplay, eglSurface))
{
if(framecount % 60 == 0)
{
LOG_E("eglSwapBuffers() returned error: %d", eglGetError());
}
}
}
}
}
void* threadCallback(void* )
{
renderLoop();
LOG_I("thread exit 0");
pthread_exit(0);
return 0;
}
extern "C" {
JNIEXPORT void JNICALL Java_com_hoytgm_hoytgm_nativeOnStart(JNIEnv* jenv, jobject obj)
{
LOG_I("nativeOnStart()");
return;
}
JNIEXPORT void JNICALL Java_com_hoytgm_hoytgm_nativeOnResume(JNIEnv* jenv, jobject obj)
{
LOG_I("nativeOnResume()");
pthread_create(&mainThreadId, 0, threadCallback, 0);
LOG_I("thread id: %ld", mainThreadId);
return;
}
JNIEXPORT void JNICALL Java_com_hoytgm_hoytgm_nativeOnPause(JNIEnv* jenv, jobject obj)
{
LOG_I("nativeOnPause()");
rendermsg = MSG_RENDER_LOOP_EXIT;
return;
}
JNIEXPORT void JNICALL Java_com_hoytgm_hoytgm_nativeOnStop(JNIEnv* jenv, jobject obj)
{
LOG_I("nativeOnStop()");
rendermsg = MSG_RENDER_LOOP_EXIT;
return;
}
JNIEXPORT void JNICALL Java_com_hoytgm_hoytgm_nativeSetSurface(JNIEnv* jenv, jobject obj, jobject surface)
{
if(surface != 0)
{
navWindow = ANativeWindow_fromSurface(jenv, surface);
LOG_I("Got window %p", navWindow);
rendermsg = MSG_WINDOW_SET;
}
else
{
LOG_I("Releasing window");
ANativeWindow_release(navWindow);
}
}
} // extern "C"
到这里了,差不多就可以工作了吧?当然不行。。。不信你去根目录(有Android.mk)下编译,保证不过~~~因为我们还缺一个main.cpp,这个文件在Android.mk里面已经写好了, 但是我们没有创建并且实现它。而且,我们后面要做的所有工作将在这个文件里面完成,上面写的所有东西,以后几乎都不会碰了。根目录下创建main.cpp文件,这个里面部分渲染工作和第一章讲的都一样,唯一不同的是创建EGL。我觉得Andriod下面创建EGL的方法更有助于我们直接的去了解EGL的参数的创建过程,IOS上面一直觉得怪怪的。。。。。。
首先,我们需要定义一个数组来决定我们需要建立一个怎么样的EGL,也就是定义RGBA的bit大小,需不需要Depth,Stencil,要不要开MSAA等等。同时还需要定义一个额外的数组来告诉显卡我们需要哪个版本。
const EGLint attribs[] =
{
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_SAMPLES, 0,
EGL_NONE,
};
EGLint attribListContext[] =
{
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
egldisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(egldisplay == EGL_NO_DISPLAY)
{
output_log("eglGetDisplay() returned error: 0x%x\n", eglGetError());
return false;
}
if(!eglInitialize(egldisplay, 0, 0))
{
output_log("eglInitialize() returned error: 0x%x\n", eglGetError());
return false;
}
我们需要知道当前的Display支持多少种配置,不出意外的话,我们刚才列出的那个属性就应该有对应的配置
/* Get how many configs does the device support */
if(!eglGetConfigs(egldisplay, NULL, 0, &num_config))
{
output_log("eglGetConfigs(NULL) returned error: 0x%x\n", eglGetError());
return false;
}
好了,我们现在传入刚才的属性数组,看下能得到多少个满足这个条件的配置,也就是很重要的eglChooseConfig
if(!eglChooseConfig(egldisplay, attribs, eglConfig_array, num_config, &supportConfigCount))
{
output_log("eglChooseConfig() returned error: 0x%x\n", eglGetError());
return false;
}
通过eglChooseConfig,我们就能得到我们支持的一个config id,用这个id,我们就能够创建一个eglcontext。eglcontext是一个非常重要的句柄,我们所需要画的任何东西,任何OpenGLES相关的操作,都需要在一个context下面才能工作。
if(!(eglcontext = eglCreateContext(egldisplay, eglConfig, 0, attribListContext)))
{
output_log("eglCreateContext() returned error: 0x%x\n", eglGetError());
return false;
}
有了context,我们就差一个surface了,这个surface就相当于我们的backbuffer,所有画的操作和结果都是默认放在这个surface上面的
if(!(eglsurface = eglCreateWindowSurface(egldisplay, eglConfig, navWindow, 0)))
{
output_log("eglCreateWindowSurface() returned error: 0x%x\n", eglGetError());
return false;
}
有了eglcontext和eglsurface了,我们再调用eglMakeCurrent将eglcontext设置为当前,我们就可以开始做GLES的工作了。
if(!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext))
{
output_log("eglMakeCurrent() returned error: 0x%x\n", eglGetError());
return false;
}
到这里为止,我们在Android上面的工作基本就结束了。剩下的三个函数,init(), Render()和fini()都和IOS上面差不多了。有兴趣的同学可以去我的资源上面下载Android工程的源码并编译,会生成一个apk,就可以安装到虚拟机或者Android手机上面看结果。贴下代码的资源地址 http://download.csdn.net/detail/hoytgm/7532901
好了,Android工程的建立就到这里了。后面demo的代码主要都是在IOS上面实现的,用Android开发的同学们照着代码搬到Android应该没问题吧???调用都是相同的话,如果搬代码过程有什么问题,欢迎大家交流。