This document written: 2014-05-23 .. 2022-01-21

『Android ゲームプログラミング A to Z』実装に使う Android API 群(中編)

前編ではグラフィックス関連以外のものを見たので、中編では残るグラフィックス関連を取り上げる。

グラフィックス(出力)

WAKE_LOCK

uses-permission で指定した WAKE_LOCK を行う。Context から得た SystemService の一種である PowerManager オブジェクトから FULL_WAKE_LOCK モードで WakeLock オブジェクトを取得する。これは onCreate ですればよい。そして実際に WakeLock を on / off するのは、onResume で aquire() メソッドを、onPause で release() メソッドを発動することによる。

しかし、以上の方法は古いもので、現在(API-17 以降)では推奨されていない。代替手段としては、Activity の getWindow() メソッドWindow オブジェクトを取得し、addFlags() メソッドで FLAG_KEEP_SCREEN_ON を指定する方法である(API-17 未満でも使える)。こちらのやり方の場合、uses-permission は必要がなく、onResume / onPause で on / off する必要もない。

フルスクリーン

Activity の requestWindowFeature() メソッドで FEATURE_NO_TITLE を指定してアプリのタイトルバー(新しい Android API ではアクションバー)を非表示にする。

さらに、Android のホーム画面側の、ステータスバー(画面上部)と、ナビゲーションバー(画面下部)を隠すために、Activity の getWindow() メソッドWindow オブジェクトを取得し、setFlags() メソッドで FLAG_FULLSCREEN を指定する。

ちなみに、Android 4.4 (API-19) からは、上記の方法を用いずとも、Window オブジェクトから DecorView を取得し、setSystemUiVisibility() メソッドで、SYSTEM_UI_FLAG_FULLSCREEN、SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN、SYSTEM_UI_FLAG_HIDE_NAVIGATION、SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION、SYSTEM_UI_FLAG_IMMERSIVE_STICKY を組み合わせて指定すれば、ステータスバーとナビゲーションバーを自動で出入りするようできる。

UI スレッドにおけるシングルスレッド処理(View でレンダリング処理)

UI スレッドにおいてシングルスレッドでグラフィックスも何もかも含めて処理するのであれば、View オブジェクトの onDraw() メソッドを使えばよい。これはあくまでもイベント駆動型の受動的メソッドであり、View オブジェクトの再描画時に呼ばれるものなので、任意で再描画を行うには、当の View オブジェクトに対して invalidate() メソッドを発動して逐次処理させる必要がある。

Canvas / Bitmap / Typeface

これらはデータ上の論理的な画像オブジェクトである。論理的な画面である Canvas に描いて生成(レンダリング)した画像データを、View(前述)や SurfaceView(後述)に送ることで、実際の画面上の出力を得ることができる。ちなみに Canvas を使わない、別の論理的な画像データの生成(レンダリング)方法としては、OpenGL がある。

Bitmap や Typeface は、その Canvas 上に描かれる画像オブジェクトの部品である。BitmapBitmapFactory というファクトリー・メソッドを使って生成するのが普通である。TypefacePaint オブジェクトを通じて、Canvas にレンダリングする。

UI スレッドとは別のスレッドにおけるマルチスレッド処理(SurfaceView でレンダリング処理)

シングルスレッドの場合、UI 処理の影響を描画処理がモロに影響を受けてしまうので、処理落ちなどによる画面描画の速度の変動などが発生する。これを避けるためには、画像処理用スレッドを UI スレッドから分けて別スレッドにすることによるマルチスレッド化が常套的手法である。

別スレッドを使って画面処理(レンダリング)を行いたい場合には、SurfaceView オブジェクトを使う。SurfaceView は、SurfaceHolder を通じて、排他ロック/アンロックを行って描画処理を行う。

どちらかというと、Android 固有の SurfaceView 特有の手法に関する部分よりも、Thread オブジェクトを使った Java のマルチスレッドプログラミングが、初心者には理解に悩むポイントかもしれない。ここは言葉で説明するよりも、実際のサンプルコードを見るべきである。著者のサンプル SurfaceViewTest をベースに、最新の API に対応したフルスクリーン化ロジックと WakeLock を盛り込んだサンプルをここに示す:


import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.os.Build;

public class SurfaceViewTest extends Activity {
	private FastRenderView renderView;

	@SuppressLint("InlinedApi")
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// タイトルバー(アクションバー)を非表示化
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		// フルスクリーン化
		if (Build.VERSION.SDK_INT < 19) {
			// API-18 までの場合
			getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
		} else {
			// API-19 以降の場合(ステータスバーとナビゲーションバーを出し入れできる)
			getWindow().getDecorView().setSystemUiVisibility(
					View.SYSTEM_UI_FLAG_FULLSCREEN
					| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
					| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
					| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
					| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
		}

		// WakeLock に対する代替手法
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

		renderView = new FastRenderView(this);
		setContentView(renderView);
	}

	protected void onResume() {
		super.onResume();
		renderView.resume();
	}

	protected void onPause() {
		super.onPause();
		renderView.pause();
	}

	private static class FastRenderView extends SurfaceView implements Runnable {
		private Thread renderThread = null;
		private SurfaceHolder holder;
		volatile private boolean running = false;

		public FastRenderView(Context context) {
			super(context);
			holder = getHolder();
		}

		public void resume() {
			running = true;
			renderThread = new Thread(this);
			renderThread.start();
		}

		public void run() {
			while (running) {
				if (!holder.getSurface().isValid()) {
					continue;
				} else {
					Canvas canvas = holder.lockCanvas();
					canvas.drawRGB(255, 0, 0);
					holder.unlockCanvasAndPost(canvas);
				}
			}
		}

		public void pause() {
			running = false;
			while (true) {
				try {
					renderThread.join();
					Log.d("thread", "joined successfully");
					return;
				} catch (InterruptedException e) {
					Log.d("thread", "failed joinning");
					// リトライする
				}
			}
		}
	}
}

読解『A to Z』