スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。



サンプル課金プログラムの強制終了(ServiceのonStart, onStartCommandでIntentがnullになる)

Google提供のサンプルプログラムが落ちる件。

このページで紹介されているサンプルプログラム。
http://developer.android.com/intl/ja/guide/google/play/billing/billing_integrate.html

まだ環境が整わないけどできるところだけでもと思ってサンプルプログラムを動かすと強制終了することがある。
原因を調査するとService.onStart()の引数Intentがnullになっているからだった。

なぜにnull?

調べてみたら以下のことが書かれていた。
http://android-developers.blogspot.jp/2010/02/service-api-changes-starting-with.html

For compatibility with existing applications, the default return code for applications that are targeting an earlier version of the platform is a special START_STICKY_COMPATIBILITY code that provides the old behavior of not calling onStart() with a null intent. Once you start targeting API version 5 or later, the default mode is START_STICKY and you must be prepared to deal with onStart() or onStartCommand() being called with a null Intent.



英語には自信ありませんが...
(かなり英文をぶったきって訳してます)

既存のアプリケーションの互換のためデフォルトでは若いAPIバージョン向けのアプリケーションではSTART_STICKY_COMPATIBILITYを返す。
(訳者:若くないAPIバージョンはこの後にでてくる)
これは引数IntentがnullでonStart()を呼び出さない昔の行為を解決するためのコードです。
あなたがAPIバージョン5以降をターゲットにしているとき,デフォルトの値はSTART_STICKYです。
このためあたなはonStart()かonStartCommand()で引数がnullで呼ばれることを準備しなければならない。


で課金のサンプルコードはonStart()で実装していたので,nullになっていたということです。

ではなぜnullになるのか...。

http://developer.android.com/intl/ja/reference/android/app/Service.html#START_STICKY

ここに書かれているように,サービスが死んでも再生成するよ。
でもIntentは保管していないから,再起動時はnullで呼ばれるよ。
ということです。

stickyってのは粘着性のっていう意味なので
ゾンビのように復活するつーことでしょうか...。


さっきのBlogに書いてあったSTART_STICKY_COMPATIBILITYは...

http://developer.android.com/intl/ja/reference/android/app/Service.html#START_STICKY_COMPATIBILITY

Constant to return from onStartCommand(Intent, int, int): compatibility version of START_STICKY that does not guarantee that onStartCommand(Intent, int, int) will be called again after being killed.



殺されても再びコールされることは保証されない。


最初からSTICKYにしたくなければ,こっちでええんじゃないかなぁ。

http://developer.android.com/intl/ja/reference/android/app/Service.html#START_NOT_STICKY

Constant to return from onStartCommand(Intent, int, int): if this service's process is killed while it is started (after returning from onStartCommand(Intent, int, int)), and there are no new start intents to deliver to it, then take the service out of the started state and don't recreate until a future explicit call to Context.startService(Intent). The service will not receive a onStartCommand(Intent, int, int) call with a null Intent because it will not be re-started if there are no pending Intents to deliver.



onStartCommand()から戻った後に開始しているときにサービスが殺されたなら,
そして新しいIntentが届いていないなら,
そのときは開始状態からサービスを取り出して(移行して?)
将来,明示的にstartService()されるまで再生成しないよ。
サービスは引数Intentをnullにして呼び出すonStartCommand()を受信しないでしょう。
なぜならばサービスを再生成しないからです。
(もし保留している配送されたIntentがないときは)


つーことで課金のサンプルを自分のコードで使うときはBillingServiceのonStart()を
onStartCommand()に置き換えて,戻り値はSTART_STICKY_COMPATIBILITYもしくはSTART_NOT_STICKYにすればよいと思います。

うぅ早く課金テストしたい。
スポンサーサイト

テーマ : ゲーム開発
ジャンル : コンピュータ




Android 開発本を読みました


本格アプリを作ろう! Androidプログラミングレシピ本格アプリを作ろう! Androidプログラミングレシピ
(2011/12/22)
Dave Smith、Jeff Friesen 他

商品詳細を見る


図書館で1ヶ月前に借りた1週間ほど前に来たもので
課金処理の作業を停止している,間が空いた今
ちょうどいいタイミングだったので読んでみた。

・前提条件
 Java言語仕様を理解していること。

・掲載要約
 Android2.3を中心に解説。
 GUIの構築手順についてはほとんどのっていない。
 他アプリ, Android OS, 外部システムとの連携方法について
 多くページを割いている。(約半分)

 他アプリとの連携
  ・Intentを使った連携
   BroadcastReceiverや暗黙的Intentなど。
  ・データ連携
   ContentProviderの使い方と実装方法(呼び出される方も含めて)

 外部システムとの連携
  ・ネットワークを使った連携
   HTTP通信, Bluetooth, XML, JSONなどの実装方法。
   通信では必須になる非同期実装(AsyncTask)について。

 Android OSとの連携
  ・ステータスバーへの通知方法(NotificationManager)の実装方法
  ・スケジューリング(指定時刻, 周期起動)方法(AlarmManager)の実装方法
  ・電話帳との連携方法など

その他にも永続化(SQLite, Preference, Assets), マップや各種センサーを使ったプログラム
JarライブラリをAndroidプロジェクトで使う方法, NDKの導入方法など。

入門書を読んだ後に読む本という印象。
そのためか図が少ないので,自分で実装するかサンプルコードを動かしてみないと
どういったものか理解しにくいのではないかと思う。

前書きに書いてあったが,実作業時に読み返すことを考慮して
小さな項目に分けてある。

GUIまわりの入門編しか読んだことの無い人,
Android OS特有の連携方法を知りたい人,
そういった方にうってつけの本だと思う。

テーマ : 図書館で借りた本
ジャンル : 本・雑誌




課金処理

Androidでの課金はまずサンプルコードのダウンロードから始まる。

http://developer.android.com/intl/ja/guide/google/play/billing/billing_integrate.html

リファレンスには攻撃を避けるために
このサンプルコードを使うべきではないと
書いてあるけど,かなりよくできているので
流用する人は多いのではないかと思う。

サンプルコードはいくつか疑問点があるので
(強制終了もいくつかみられるので,そのまま使うわけには行かなさそう)
動かしたいのだけどまだ準備が整っていないので。。。

9月いっぱいで完成予定だったけど
課金処理の準備でどうも間に合わなさそう。

10月中にはリリースしたいなぁ。

テーマ : ゲーム開発
ジャンル : コンピュータ




課金のテスト

課金のテストするためにGalaxy S2を買ってきた。
23000円。。。
出費した分とりもどせる...気はしませんがwww
これも勉強代ということで。。。

アプリ作るのにかかったお金くらい回収できればいいけどなぁ。
アプリの魅力次第...だよな。

アプリ内課金の動きを知るために
くぼたまアプリさんの「新!あげぱん探偵ヒラメキ!」のアイテムを買ってみました。

ちゃんと買えたw
キャリアの月額料金と一緒に払えるんだねぇ。

実装は結構めんどくさそう。

最近はちょっといろいろあって凹み気味ですが,
くじけずにがんばります。

テーマ : ゲーム開発
ジャンル : コンピュータ




最近は...

最近ブログを書いてなかったけど,
移植作業は順調に進んでいます。

今回はスマホになったということでゲームクリアしたときに
クリア内容をTwitterなどでつぶやけるようにしました。

本当は自前のサーバを用意してネットランキング対応としたいところですが,
サーバを借りると毎月お金がかかるので,貧乏人の自分としては
Twitter連携がぎりぎりw

アプリでお金が稼げるようになったら
サーバ運用も視野に入るのでしょうが...。
しばらくはTwitterなどのお金のかからない機能と連携していくこととします。

技術的な話ですが...
Twitter連携はIntentのsetTypeに「application/twitter」と入れれば良い
と高をくくっていたのですが,このタイプでは公式のTwitterアプリは反応してくれませんでした。

今回はメッセージと画面を保存したPNGフィアルをアップしたかったので,
setTypeにはMIMEの「text/plain」「image/png」を指定しました。
とはいってもIntentには一種類のtypeしか設定できない
(もしかすると他のやりかたがあるのかもしれないけど...)
ので,自分であーだこーだしてなんとか実装。

ACTION_CHOOSER使用したかったけど,これも1つの種類しか...。

Android版でナイスなスコアを出した方はぜひつぶやいちゃってください!!

テーマ : ゲーム開発
ジャンル : コンピュータ




フォントサイズの検証

フォントサイズの検証するためにテストアプリを作ってみた。
知人の端末で実行してみてもらったけど,自分のと少し違う結果になった。

コードは割愛して結果は以下のとおり。
(ってコードないからわからないよねw)

FontTest.png

フォントタイプはMONOSPACEを設定して描画。
ともに12dp(12pxをdensityでscaleした)にして描画。

その後,「私」「A」「a」「4」「1」の文字幅を計測。
その結果を画面上に描画。

全角はPaint.setTextSize()に設定した値であるのに対して,
半角は機種によってまちまち。

ただ同じ結果なのは全角の半分のサイズではないということ。
携帯のときは全角のぴったり半分だったのが,このような結果なので
Androidではレイアウトがずれてしまう問題が起きていたということ。


そして気になる点がもう一つ。
日本語の表示Y位置が機種によって違うということ。
106SHはベースラインに近い位置に描画されているが,
Galaxy S1はdescentに近い位置に描画されている。

日本語はbaselineからのascentの距離でフォントの高さを求めようと考えていたけど
そういうわけにはいかないようです。

半角英数字記号を全角の半分のサイズにして描画する処理については
機種に関係なく動いていることがわかったので,
この方式で半角英数字記号を描画していこうと思う。

移植の際は画面構成を考え直す必要がありそうですね。

テーマ : ゲーム開発
ジャンル : コンピュータ




フォントサイズに泣き

iko2をAndroidへ移植していて最も困ったことと言えばフォント。

携帯時代のフォントは12x12と各社共通だったので
レイアウトはこれを基準にすることでクリアしていました。

しかしAndroidではフォントが機種によってまちまち。
さらに固定ピッチ(MONOSPACE)にしても、
半角が全角の半分ではなく若干横に広いということがわかりました。

このため携帯時代のレイアウトをそのまま使用することができないため,
試行錯誤が必要になります。

いまのところ考えているのが

日本語用のフォント
英数字用のフォント(半角用)

で分けてみようかと思っています。
しかし表示するものによってフォントを変えるのは結構大変...。

画面を洗い出して再設定しないと> <

思わぬところで時間がかかりそうです。

テーマ : ゲーム開発
ジャンル : コンピュータ




Android版のiko2は...

Android版のiko2ではオリジナルのときに使用していた音や曲が使えないので,
新しいものに差し替えています。

オリジナルの時の雰囲気を残しつつ,
Androidだから鳴らせる高音質なものを使用しています。

だからアプリのサイズもデブ〜にw
今の時点で携帯版の10倍になりやした。

音と曲はWeb上に公開してくれている方々のものを使用しています。
ほんと神様みたいな人がいるんですねw

ということでAndroid版もやってみる人は音の変化を楽しんでみてね。

テーマ : ゲーム開発
ジャンル : コンピュータ




MIDP -> Android その9

SqliteのDB保存先

Sqliteで永続化しています。
検証が終わったので本番プロジェクトで実装始めました。

そこで問題。
行を追加しているのにも関わらず,
SELECTしても結果がとれない。

ちゃんとテーブルに保存されているのか確認したいので
直接dbにアクセスしようとしたんだけど。。。

dbの保存先は

/data/data/<パッケージ名>/databases

の配下。
ただし/dataフォルダはAndroidのセキュリティでアクセスできないようになっている。
(root化していれば別だけど)

シェルで「adb shell」とうって実機に接続し,
直接DBのパスを打てばアクセスすることはできるようだ。

しかし,こちらのサイトに書かれているように
http://typea.info/blg/glob/2012/01/android-sqlite-sql.html
http://d.hatena.ne.jp/kurukuru-papa/20111030/1319950036

sqlite3コマンドが入っていない実機もあり,
自分が持っている106SHも入っていませんでした> <

cpコマンドもなかったのですが,mvコマンドがあったので使おうとしたら「Cross deveice link」で怒られた。
adb pullもできないし。

結局アプリからdbをSDカードに保存して,
そのデータをadb pullしてsqlite3コマンドで確認しました。

なんかもっといい方法があるといいんだけど...。

テーマ : ゲーム開発
ジャンル : コンピュータ




MIDP -> Android その8 SqliteでのBLOBの扱い

昨日検証していて問題になったことがありました。
ContentValuesを使用したupdate処理ではSQL関数が使用できない。
これはContentValuesのputでSQL関数を使用しても文字列として扱われるためです。

調べてみると,その回避策としては以下のことがあがっていました。

1.フィールドにDEFAULT値を設定して回避する。
2.生のSQL文で実行する。(execSQL()を使う)

sqliteの仕様でDEFAULTにはSQL関数が使用できないので,
今回のようなjuilanday関数を使用したデフォルト値は設定できません。
(sqliteのバージョンアップによって使用できるようになるかもしれません)

ということで残りは2.の生のSQL文を書くという方法です。
ではBLOBデータはどうやって展開すればいいのか?

とりあえずAndroidのsqliteクラスはどうやって扱っているか調べてみました。

結局nativeメソッドで処理されていてJavaコードからはわかりませんでした。
androidのコード解析はnative側もみないとだめですね〜><

とりあえず,展開している部分のコードポインタをあげときます。

android/database/sqlite/SQLiteConnection.java

	private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
        final int count = bindArgs != null ? bindArgs.length : 0;
        if (count != statement.mNumParameters) {
            throw new SQLiteBindOrColumnIndexOutOfRangeException(
                    "Expected " + statement.mNumParameters + " bind arguments but "
                    + bindArgs.length + " were provided.");
        }
        if (count == 0) {
            return;
        }

        final int statementPtr = statement.mStatementPtr;
        for (int i = 0; i < count; i++) {
            final Object arg = bindArgs[i];
            switch (DatabaseUtils.getTypeOfObject(arg)) {
                case Cursor.FIELD_TYPE_NULL:
                    nativeBindNull(mConnectionPtr, statementPtr, i + 1);
                    break;
                case Cursor.FIELD_TYPE_INTEGER:
                    nativeBindLong(mConnectionPtr, statementPtr, i + 1,
                            ((Number)arg).longValue());
                    break;
                case Cursor.FIELD_TYPE_FLOAT:
                    nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
                            ((Number)arg).doubleValue());
                    break;
                case Cursor.FIELD_TYPE_BLOB:
                    nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
                    break;
                case Cursor.FIELD_TYPE_STRING:
                default:
                    if (arg instanceof Boolean) {
                        // Provide compatibility with legacy applications which may pass
                        // Boolean values in bind args.
                        nativeBindLong(mConnectionPtr, statementPtr, i + 1,
                                ((Boolean)arg).booleanValue() ? 1 : 0);
                    } else {
                        nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
                    }
                    break;
            }
        }
    }

    private static native void nativeBindBlob(int connectionPtr, int statementPtr,
            int index, byte[] value);

というころで生のSQL文でBLOBデータを設定する方法を試してみました。
BLOBの設定方法にもいくつかあるようですが,
今回は「x''」という形式を使用してみました。

package com.example.sqliteblobtest;

import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;

/**
 * BLOBデータの扱いをテストするためのクラス
 */
public class SqliteBlobTest extends Activity implements Runnable
{
	static final private String DB_NAME = "test.db";
	
	static final private String BLOB_DATA_STRING = "BLOBテスト用に使用する文字列";
	
	private BlobTestOpenHelper openHelper;
	private Thread thread;
	private byte[] BLOB_DATA;

	@Override public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_sqlite_blob_test);

		try
		{
			BLOB_DATA = BLOB_DATA_STRING.getBytes("UTF-8");
		}
		catch(Exception ex)
		{
			Log.e(getClass().getSimpleName(), "onCreate() Caught exception.", ex);
			BLOB_DATA = null;
			return;
		}

		openHelper = new BlobTestOpenHelper(getApplicationContext(), DB_NAME);
		
		thread = new Thread(this);
		thread.start();
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see android.app.Activity#onPause()
	 */
	@Override protected void onPause()
	{
		try
		{
			thread.join();
			thread = null;
		}
		catch(Exception ex)
		{
			Log.e(getClass().getSimpleName(), "onPause() Caught exception.", ex);
		}
		
		super.onPause();
	}

	/**
	 * BLOBデータのテスト用データベースクラス 
	 */
	static private class BlobTestOpenHelper extends SQLiteOpenHelper
	{
		public BlobTestOpenHelper(Context context, String dbName)
		{
			super(context, DB_NAME, null, 1);
		}

		@Override public void onCreate(SQLiteDatabase db)
		{
			db.execSQL("CREATE TABLE savedata1(" + 
		                      "id INTEGER PRIMARY KEY," + 
					          "created," + 
		                      "updated," + 
					          "data BLOB)");

			db.execSQL("CREATE TABLE savedata2(" + 
                              "id INTEGER PRIMARY KEY," + 
			                  "created REAL NOT NULL," + 
//                              "updated REAL NOT NULL DEFAULT julianday(CURRENT_TIMESTAMP,'localtime')," + //これだとエラーになる 
                              "updated REAL NOT NULL DEFAULT CURRENT_TIMESTAMP," + // DEFAULTではSQLの関数が使用できないのでこちらは成功する。 
			                  "data BLOB)");

			db.beginTransaction();

			try
			{
				for(int i = 0; i < 10; i++)
					db.execSQL("insert into savedata1(id, created, updated) values(null, julianday(CURRENT_TIMESTAMP,'localtime'), julianday(CURRENT_TIMESTAMP,'localtime'));");

				db.setTransactionSuccessful();
			}
			catch(Exception ex)
			{
				Log.e(getClass().getSimpleName(), "");
			}
			finally
			{
				db.endTransaction();
			}
		}

		@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
		{
		}
	}

	@Override public void run()
	{
		SQLiteDatabase db = openHelper.getWritableDatabase();

		db.beginTransaction();
		
		try
		{
			{
				ContentValues values = new ContentValues();

				// この方法ではupdatedの値が文字列として更新されてしまう
				values.put("updated", "julianday(CURRENT_TIMESTAMP,'localtime')");
				values.put("data", BLOB_DATA);
				
				db.update("savedata1", values, "id=2", null);
			}
			
			// 生のSQL文を作る
			{
				StringBuilder sb = new StringBuilder();
				sb.append("UPDATE savedata1 SET updated=julianday(CURRENT_TIMESTAMP,'localtime'), data=");
				
				sb.append("x'");
				
				for(byte b : BLOB_DATA)
					sb.append(String.format("%02x", b));

				sb.append("'");
				sb.append(" WHERE id=3;");
				
				String sql = sb.toString();
				db.execSQL(sql);
				
				Log.d(getClass().getSimpleName(), "SQL -> " + sql);
			}
			
			db.setTransactionSuccessful();
		}
		catch(Exception ex)
		{
			Log.e(getClass().getSimpleName(), "run() Caught exception.", ex);
		}
		finally
		{
			db.endTransaction();
		}

		//----------------
		// 結果の読み込み
		//----------------
		
		db = openHelper.getReadableDatabase();
		
		Cursor c = null;
		
		try
		{
			String[] columns = {"id", "datetime(created)", "datetime(updated)", "updated", "data"};
			c = db.query("savedata1", columns, null, null, null, null, "id ASC");
			
			final int count = c.getCount();
			
			c.moveToFirst();
			
			for(int n=0; n < count; n++)
			{
				final int id = c.getInt(0);
				final String created = c.getString(1);
				final String updated = c.getString(2);
				final String rawUpdated = c.getString(3);
				final byte[] data = c.getBlob(4);
				
				String dataString = (data!=null) ? new String(data, "UTF-8") : "";
				
				Log.i(getClass().getSimpleName(), "[" + n + "] id:" + id + ", created:" + created + ", updated:" + updated + "(" + rawUpdated + "), data:" + dataString);
				
				if(!c.moveToNext())
					break;
			}
		}
		catch(Exception ex)
		{
			Log.e(getClass().getSimpleName(), "run() Caught exception while reading db.", ex);
		}
		finally
		{
			if(c!=null)
				c.close();
		}
	}
}

// 結果
SQL -> UPDATE savedata1 SET updated=julianday(CURRENT_TIMESTAMP,'localtime'), data=x'424c4f42e38386e382b9e38388e794a8e381abe4bdbfe794a8e38199e3828be69687e5ad97e58897' WHERE id=3;
[0] id:1, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[1] id:2, created:2012-09-05 01:31:04, updated:null(julianday(CURRENT_TIMESTAMP,'localtime')), data:BLOBテスト用に使用する文字列
[2] id:3, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:05(2.45618e+06), data:BLOBテスト用に使用する文字列
[3] id:4, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[4] id:5, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[5] id:6, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[6] id:7, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[7] id:8, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[8] id:9, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[9] id:10, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:

テーマ : ゲーム開発
ジャンル : コンピュータ




MIDP -> Android その7

音量の設定

前回の記事でAudioManagerのsetStreamVolume()について書きましたが,
このメソッドはオーディオ全体のボリュームでした。

はやとちりしてしまってリファレンスをちゃんと読み返したら,
MediaPlayerでボリュームの設定ができることがわかりました。

public void setVolume (float leftVolume, float rightVolume)
http://developer.android.com/intl/ja/reference/android/media/MediaPlayer.html#setVolume(float, float)

AudioManagerと何が違うかと言えば,
これはMediaPlayer単位の音量設定になるということです。

AudioManagerがマスターボリュームで
MediaPlayerがプレイヤー(アプリ)単位ということになります。

MediaPlayerの音量設定を使えば,
効果音で使用しているSoundPoolの音量設定とは別に設定することができます。

これでオリジナルのとき同様にMUSIC, SEの音量が設定できるようになりました。


セーブデータ

MIDPではRecordStoreにデータをセーブできました。
AndroidではPreferenceなどが用意されていますが,
今回はSQLiteを使用した永続化処理を実装していきたいと考えています。

ということで今日からSQLiteの検証プログラムを実装し始めました。

時刻に関しては...
http://d.hatena.ne.jp/Fio/20081008/p1

SQLiteではDateTime型がないのでREALの整数として保存しておき,
読み込むときに日付文字列に置き換えるという方法。
この方法であれば文字列の違いによる,期待しないソートなどがなくなります。

SQLiteの基本操作方法
http://ichitcltk.hustle.ne.jp/gudon/modules/pico_rd/index.php?content_id=74

BLOBデータの更新・読み取り
http://d.hatena.ne.jp/the_yokochi/20110130/1296387568


上記の組み合わせでjulianday関数とBLOBを同時に更新する方法がみつからない。
明日の課題としよう。

テーマ : ゲーム開発
ジャンル : コンピュータ




MIDP -> Android その6 サウンド関連

MIDPではサウンド周りの共通クラスがないので,
キャリアごとに搭載されているサウンドチップにあわせたライブラリが用意されていました。

S!アプリとAndroidではサウンド周りが全く変わりました。
再生できるファイルタイプが増えたし,
S!アプリのときのように少ないファイル容量に悩まされずに
綺麗な音が使えるようになると思われます。


ボリュームの設定
Androidのボリューム設定は
AudioManagerの以下のメソッドで設定するそうです。

public void setStreamVolume (int streamType, int index, int flags)

http://developer.android.com/intl/ja/reference/android/media/AudioManager.html#setStreamVolume(int, int, int)

この第三引数はなんだろう?と思ってたのですが,
どうやらAudioManagerに定義されているFLAG_が付く定数のようです。

http://developer.android.com/intl/ja/reference/android/media/AudioManager.html#FLAG_PLAY_SOUND

例えば音量を変更したときに音を鳴らすとか。

こちらのサイトで音を変更したときにダイアログを表示するフラグについての説明しています。
https://sites.google.com/site/androidappzz/home/dev/volumesample


明日はSE音の実装しよ。

テーマ : ゲーム開発
ジャンル : コンピュータ




アクセス
あなたは
キーワード
カテゴリー
最近の記事
リンク
月別アーカイブ
ブロとも申請フォーム

この人とブロともになる

WEB検索
Google

RSSフィード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。