LiveWallpaperをつくる -デバッグ編-

LiveWallpaperの動作確認用のActivityを用意して開発するためのメモ。
前回、LiveWallpaperを作りましたが、デバッグや動作確認を行うのが結構めんどいため、Androidアプリとして開発を行えるようにします。

概要

手順としては、以下のとおりです。

  1. Androidアプリにするため、Activityクラスを作成
  2. AndroidManifest.xmlに、アプリとしての設定を記述

手順

MainActivityの作成

Activity継承クラス、MainActivityを作成します。
また、描画をSerfaceViewに行うため、SerfaceViewを設定したlayout/main.xmlを準備します。

package com.ttshrk.livewallpaper_example;

import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.os.Handler;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity {

	private final Handler mHandler = new Handler();
	private SurfaceView mSurfaceView;
	boolean mVisible = true;
	private int mMillisPerFrame;

	private final Runnable mDrawer = new Runnable() {
		public void run() {
			drawFrame();
		}
	};

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFormat(PixelFormat.TRANSLUCENT);

		setContentView(R.layout.main);
		mMillisPerFrame = 1000 / 30;

		mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView1);
		// 描画開始
		mHandler.postDelayed(mDrawer, 0);
	}

	public void drawFrame() {
		final SurfaceHolder holder = mSurfaceView.getHolder();

		Canvas c = null;
		try {
			c = holder.lockCanvas();
			if (c != null) {
				// draw something
				update();
				c.drawColor(Color.BLACK);
				draw(c);
			}
		} finally {
			if (c != null)
				holder.unlockCanvasAndPost(c);
		}

		mHandler.removeCallbacks(mDrawer);
		if (mVisible) {
			mHandler.postDelayed(mDrawer, mMillisPerFrame);
		}
	}

	void update() {
	}

	void draw(Canvas canvas) {
	}
}

SerfaceViewに描画を行うだけのActivityです。
タイトルの表示をけして、画面いっぱいに表示しています。
onCreate、update、drawに確認を行いたいモジュールを設定してください。
(widthやheightなど必要なプロパティは適宜設定してください。)


次にレイアウトxmlです。

  • layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/surfaceView1"
		android:layout_height="fill_parent" android:layout_width="fill_parent"></SurfaceView>
</LinearLayout>

これも、画面いっぱいにSurfaceViewを貼り付けただけです。

AndroidManifest.xmlの修正

AndroidManifest.xmを修正します。
Androidアプリとして起動できるよう、ApplicationにMainActivityを設定します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.ttshrk.livewallpaper_example"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="7" />
    <!--<uses-feature android:name="android.software.live_wallpaper" />-->

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

<!-- 
    <application
        android:label="@string/app_name"
        android:icon="@drawable/icon" android:debuggable="true">

        <service
            android:label="@string/app_name"
            android:name=".ExampleWallpaer"
            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>
-->
</manifest>

とりあえず、Wallpaperサービスを残してあります。
これも、applicationタグを追加しただけです。


だいたい以上です。
描画を行う部分をクラスにして設定すれば壁紙をあぷりとして実行できると思います。


おまけ

Handler#delayPostのdelay値は結構いい加減なので、FPSを一定にしたいときは、以下のようにするといいと思います。

  • MainActivity.java 抜粋
	public void drawFrame() {
		final SurfaceHolder holder = mSurfaceView.getHolder();

		Canvas c = null;
		try {
			c = holder.lockCanvas();
			if (c != null) {
				// draw something
				update();
				c.drawColor(Color.BLACK);
				draw(c);
			}
		} finally {
			if (c != null)
				holder.unlockCanvasAndPost(c);
		}

		mHandler.removeCallbacks(mDrawer);
		if (mVisible) {
			long delay = delay();
			mHandler.post(mDrawer, delay);
		}
	}

	private long delay() {
		long currentDelay = mMillisPerFrame - (int) mMilliTimeMesure.getElapsed();
		mMilliTimeMesure.reset();
		// 遅延は無視
		if (currentDelay < 0)
			return 0L;
		return currentDelay;
	}

1フレームごとにかかった経過時間を測定して、自前でsleep処理を行います。
IS01でも、30FPSくらいなら問題なく動作します。


MilliTimeMeasureですが、こんな感じの経過時間を取得するだけのクラスを用意します。

  • MilliTimeMeasure.java
public class MilliTimeMeasure {
	private long lastTime;
	
	public MilliTimeMeasure() {
		reset();
	}
	
	private long getCurrent() {
		return System.currentTimeMillis();
	}
	
	public void reset() {
		lastTime = getCurrent();
	}
	
	public long getElapsed() {
		return getCurrent() - lastTime;
	}
}


サンプルコード
livewallpaper_example2.zip 直