Androidアプリ OpenGLのライブ壁紙を作る

AREarthライブ壁紙をリリースしてから、1日経過した。まだ、ダウンロード数は0である。
やはり、宣伝しないとだれも気付いてくれない。
それは、さておき、本日は、ライブ壁紙の作り方についてである。
Androidのアプリには、普通のアプリと「ライブ壁紙」の2種類がある。アプリは、使いたいときに起動して使う。ライブ壁紙は、ラウンチャーの壁紙用のアプリである。単なる壁紙は、画像データのみであるが、ライブ壁紙はプログラムとなっていて、アプリをインストールすると壁紙として使用できるようになる。ライブ壁紙をインストールして選択すると、「動く壁紙」になるわけである。
スクリーンショット-3.png  スクリーンショット-4.png  スクリーンショット-5.png
さて、ライブ壁紙を作ろう、というのが本日の趣旨である。
普通のアプリなら、アクティビティを作って、その中で何かを表示させたり、ビューやレイアウトを使ってGUIを構築すればよい。
ライブ壁紙はというと、アクティビティじゃなくて、WallpaperServiceの派生クラスを作ってやる必要がある。ふーん。WallpaperServiceは、アクティビティと異なり、ラウンチャー(ホーム画面)が表示されている時だけ動いていればよいので、状態遷移がアクティビティよりは単純。
あてずっぽうではあるが、Serviceの派生クラスなんだろうな。きっと。Serviceは画面を持たない、バックグラウンドで動作するようなアプリを作るときに、使用するクラス。
WallpaperServiceをextendsして派生クラスを作って、onCreateEngineでWallpaperService.Engineのインスタンスを作って、returnで戻せばよいようである。
こんな感じ。

public class MyWallpaperService extends WallpaperService {
@Override
public Engine onCreateEngine() {
return new MyEngine();
}
}

MyWallpaperServiceを作ってみた。
エンジンの方も作らないといけない。MyEngineは、WallpaperService.Engineの派生クラスとして、以下のように定義すればよいらしい。

class MyEngine extends WallpaperService.Engine {
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
}
}

むむむ。SurfaceViewなのね。
SurfaceViewに描画するときって、どうするんだっけ...
OpenGL使うときは、どうするのよ...
と、ここまで調べて行き詰ってしまった。
そもそも、なんで「ライブ壁紙を作ろう」と思ったかというと、AREarthroidのライブ壁紙版を作りたかったから。
AREarthroidは、OpenGLのSurfaceViewを使っている。単なるSurfaceViewならそのまま、描画ルーチンを持ってくれば、よさそうだが、OpenGLのGLSurfaceViewを持ってこれるのか...
アクティビティみたいに、簡単にはいかないのか...
多分、簡単なやり方があるんだろうなぁ... という安易な気持ちで検索していく。
WallpaperServiceとOpenGLで検索。
なんか、的中はしなかったが、いろいろ調べていくと、GLWallpaperServiceなるものを発見。
どこが正式なサイトなのかよくわからないので、URLは載せないが、検索したらすぐに、見つかると思う。
で、GLWallpaperServiceを使用することに。
GLWallpaperService.jarがあったので、これをlibsフォルダに入れる。
WallpaperService を GLWallpaperServiceに変更する。

public class MyWallpaperService extends GLWallpaperService {
@Override
public Engine onCreateEngine() {
return new MyEngine();
}
}

エンジンの方は、GLWallpaperService.GLEngineからextendsで作成する。
onCreateでレンダラーを作って、setRendererしてやると描画できる。基本、GLSurfaceView.Rendererのラッパーみたいなので、setEGLConfigChooserとか、setRenderer、setRenderModequeueEventなどが使える。
queueEventについては、こちらを参照。
setRenderModeの記事は、この記事を参照。

class MyEngine extends GLWallpaperService.GLEngine {
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
setEGLConfigChooser(8, 8, 8, 8, 16, 0);
setRenderer(new MyRenderer());
}
}

MyRendererでレンダリングする。GLWallpaperService.Rendererインタフェースを実装する必要がある。GLWallpaperService.Renderer = GLSurfaceView.Renderer みたいなので、Rendererで必要なメソッドを実装する。

class MyRenderer implements GLWallpaperService.Renderer {
public void onDrawFrame(GL10 gl) {
// ここで描画
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}
}

ライブ壁紙は、アクティビティではなく、サービスなので、AndroidManifest.xmlにserviceを定義する。
AndroidManifest.xml

<application android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:theme="@style/AppTheme">
<service android:label="MyLiveWallpaper" android:name=".MyWallpaperService"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data android:name="android.service.wallpaper"
android:resource="@xml/wallpaper" />
</service>
</application>

ライブ壁紙を選択する際に必要になる。ライブ壁紙の定義をxml/wallpaper.xmlを作成して、行う。ファイル名は、AndroidManifest.xmlで指定したものに合わせる。
res/xml/wallpaper.xml

<?xml version="1.0" encoding="utf-8"?>
<wallpaper
xmlns:android="http://schemas.android.com/apk/res/android"
android:thumbnail="@drawable/ic_launcher"
android:description="@string/app_name"
/>

これで、一応は、表示させることができた。
しかし、そのままAREarthroidのレンダラーを使うと、ビットマップや3Dデータでメモリを使い過ぎ。エンジンが頻繁に生成されている模様。
アクティビティが起動するとライブ壁紙は非表示となる。このとき、WallpaperServiceのonVisibilityChangedが発生するが、GLWallpaperServiceでは、visibilityをみて、onResume、onPauseメソッドを呼び出すようになっている。
ライブ壁紙が非表示なら、GLSurfaceViewのインスタンスは必要ないので、破棄される。描画用のGLThreadも破棄されるので、テクスチャ用のビットマップやVBOも破棄されてしまうらしく、ライブ壁紙が再表示されると、挙動がおかしい。
onResumeで、ビットマップのロードを行うことでこの問題を回避した。
関連記事

OpenGLの記事を最初から読みたいなら、以下からどうぞ。
Androidアプリ開発 OpenGLを使う GLSurfaceView

投稿者プロフィール

asai
asai
システムエンジニア
喋れる言語:日本語、C言語、SQL、JavaScript