まずはメインループ2013/11/02

前回、テーマを「Android NDKのプログラミング」としましたが、これではちょっと曖昧なのでとりあえず目標を「2Dゲームの作成」として、必要なものを順番に作っていくことにします。

そこで一番初めの今回は、
・初期化
・一定間隔でメインの処理
という全体の流れ(だけ)を作成します。

それでは、なにはなくともアクティビティ
BaseActivity.java
package sys;

import android.app.Activity;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.opengles.GL10;


/********************
    アクティビティ
 ********************/
public class BaseActivity extends Activity
{
	static {
		System.loadLibrary("native");
    }


	private BaseView		surface_view;				// ビュー
	private BaseRenderer	renderer;					// レンダラー

	private ScheduledExecutorService	executor;		// 定期実行管理
	private ScheduledFuture<?>			future;


	public native void		initNative(int _w, int _h);			// native部初期化
	public native boolean	updateNative();						// native部稼働


	/**********
	    開始
	 **********/
	@Override
	protected void	onCreate(Bundle _savedInstanceState)
	{
		super.onCreate(_savedInstanceState);

		renderer		= new BaseRenderer();							// レンダラー作成
		surface_view	= new BaseView(getApplication(), renderer);		// ビュー作成
		setContentView(surface_view);

		executor = Executors.newSingleThreadScheduledExecutor();		// 定期実行管理
	}

	/**********
	    終了
	 **********/
	@Override
	protected void	onDestroy()
	{
		super.onDestroy();
		executor.shutdown();
	}

	/**************
	    一時停止
	 **************/
	@Override
	protected void	onPause()
	{
		super.onPause();
		if ( future != null ) {					// 定期実行停止
			future.cancel(false);
			future = null;
		}
		surface_view.onPause();
    }

	/**********
	    再開
	 **********/
	@Override
	protected void	onResume()
	{
		super.onResume();
		surface_view.onResume();
	}


	/************
	    ビュー
	 ************/
	class BaseView extends GLSurfaceView
	{
		public BaseView(Context _context, BaseRenderer _renderer)
		{
			super(_context);
			setEGLContextClientVersion(2);					// OpenGL ES 2.0使用
			setEGLConfigChooser(new ConfigChooser());
			setRenderer(_renderer);
			setRenderMode(RENDERMODE_WHEN_DIRTY);
		}

		class ConfigChooser implements GLSurfaceView.EGLConfigChooser
		{
			@Override
			public EGLConfig	chooseConfig(EGL10 egl, EGLDisplay display)
			{
				final
				int[]	configAttribs =
				{
					EGL10.EGL_RED_SIZE,		8,
					EGL10.EGL_GREEN_SIZE,	8,
					EGL10.EGL_BLUE_SIZE,	8,
					EGL10.EGL_ALPHA_SIZE,	8,
					EGL10.EGL_NONE
				};

				int[]	num_config = new int[1];

				egl.eglChooseConfig(display, configAttribs, null, 0, num_config);

				EGLConfig[]		configs = new EGLConfig[num_config[0]];

				egl.eglChooseConfig(display, configAttribs, configs, num_config[0], num_config);

				return	configs[0];
			}
		}
	}


	/****************
	    レンダラー
	 ****************/
	class BaseRenderer implements GLSurfaceView.Renderer
	{
		public void	onSurfaceCreated(GL10 _gl, EGLConfig _config) {}

		public void	onSurfaceChanged(GL10 gl, int width, int height)
		{
			initNative(width, height);						// native部初期化

			if ( future == null ) {							// 定期実行開始
				future = executor.scheduleAtFixedRate(new Runnable()
				{
					@Override
					public void	run()
					{
						surface_view.requestRender();		// 描画リクエスト
					}
				},
				0, 1000/30, TimeUnit.MILLISECONDS);
			}
		}

		public void	onDrawFrame(GL10 gl)
		{
			if ( !updateNative() ) {						// native部稼働
				finish();
			}
		}
	}
}

/***************** End of File ***************************************************/

アクティビティのonCreateでサーフェスビューとレンダラーを作成するとか、System.loadLibraryでnative側のプログラムを読み込むとかいろいろありますが、この辺はNDKのプロラムでは共通事項なので説明は省きます。シンプルに作っているので、見れば大体わかると思います。

さて、プログラムの流れを追っていくと、
BaseActivityの onCreate → onResume
BaseViewの onResume
BaseRendererの onSurfaceCreated → onSurfaceChanged
と進みます。
ここでまず、nativeの関数 initNativeを呼んでnative側を初期化しておきます。
次に、「一定間隔でメインの処理」を行うための設定をします。

ScheduledExecutorServiceのメソッド
ScheduledFuture<?> scheduleAtFixedRate(
              Runnable command,
              long initialDelay,
              long period,
              TimeUnit unit)
時間単位 unitで、initialDelay後から delay毎にタスク commandを実行します。

このプログラムでは、
・1000/30ms毎に
・surface_view.requestRender()を実行する
となります。
BaseViewのコンストラクタで setRenderMode(RENDERMODE_WHEN_DIRTY)としているので、surface_viewの requestRender()により rendererの onDrawFrame()が呼ばれます。
よって、1000/ms毎に、nativeの関数 updateNativeが呼ばれることになります。

それでは、native側のプログラム
native.cpp

#include <jni.h>
#include <android/log.h>


static int	cnt = 0;


#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,  "INFO",  __VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, "ERROR", __VA_ARGS__)

extern "C"
{
JNIEXPORT void JNICALL		Java_sys_BaseActivity_initNative(JNIEnv*, jobject, jint, jint);
JNIEXPORT jboolean JNICALL	Java_sys_BaseActivity_updateNative(JNIEnv*, jobject);
}

/************
    初期化
 ************/
JNIEXPORT void JNICALL	Java_sys_BaseActivity_initNative(JNIEnv*, jobject, jint _width, jint _height)
{
	LOGI("initNative (%d, %d)", _width, _height);
}

/**********
    稼働
 **********/
JNIEXPORT jboolean JNICALL	Java_sys_BaseActivity_updateNative(JNIEnv*, jobject)
{
	if ( cnt % 60 == 0 ) {
		LOGI("updateNative %4d", cnt/60);
	}
	cnt++;
    return	JNI_TRUE;
}

/**************** End of File *************************************************/

今回はログを表示するだけで、何もしません。

なお、初期化処理といいつつ initNativeは起動時だけではなくサスペンドからのレジューム時などにも呼ばれますので注意してください。

プロジェクト一式は、こちら