スポンサーサイト

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

熱帯魚 RRE.A キングコブラハイドーサルの稚魚のお引越し

キングコブラの稚魚も大分大きくなってきたため、本水槽へ引越しすることにした。

うちのグッピー水槽は30cm*4本+60cmの水槽マンションになっている。
一つ30cm水槽を空にしてそこに引越しすることにした。

で、引っ越した後。
20121225_235109.jpg
いっぱいいて上手く写真が取れない(`・ω・´)

体長は3cm~4cm程度まで成長。
うっすらと模様も出てきている( ´∀`)

数えていないが多分30数匹いるのかな?
スポンサーサイト

テーマ : 熱帯魚
ジャンル : ペット

熱帯魚 ソリッドレッドグラスベリーの出産

やっと念願だったグラスベリーの稚魚が産まれた(・∀・)
20121221_051728.jpg

実はグラスベリーは、自分が初めて買った高めなグッピー(=゚ω゚=)
今回産まれたのは3代目のグッピー

初めてグラスベリーのペアを購入したのが1年ほど前。
購入してすぐに出産。20数匹ほど産まれたのだがハリ病などでほとんどが☆に(´・ω・`)
最終的に生き残ったのが2匹(´・ω・`)
しかも購入したペアが出産まもなく☆に(´・ω・`)

本当にこのときはショックだった(´・ω・`)

この生き残った2匹を大切に育てていたのだが、運よく生き残った2匹が雄と雌だった(・∀・)

その2匹が大きくなり、先日無事に稚魚を産んだ(・∀・)
数えたら4匹。
今回もキングコブラと同様の稚魚水槽で飼育することに。
グッピーの稚魚をハリ病にさせない水槽

無事に大きくなってくれ(・∀・)


ちなみに瀕死の状態から復活した雄と雌
20121221_051733.jpg
20121221_051800.jpg


テーマ : 熱帯魚
ジャンル : ペット

熱帯魚 90cm水槽の自作底面フィルター

90cm水槽を購入することにしたのだが、まず、ろ過をどうしようか悩んだ。
もちろん前提として自作。
自作になると底面フィルターが無難かな。

今回は90cmという大きい水槽のため、底の面積も十分ある。
全体的に塩ビ管を巡らせるが、せっかくなのでさらに濾材を敷き詰めることにした。

全体に敷き詰めたいので多めの2リットルを購入した。



敷き詰める塩ビ管にはいたるところに穴を開けておく。
設置した画像。
401289_254210294653625_1796145556_n.jpg

で、問題なのが水の循環をどうやるのか。
普通なら底面フィルターではエアーリフトを使用するのだが、これだけ大きいとエアーリフトだと全然水が循環しない。
というわけで、上部フィルター用のポンプを使用することにした。
別に他の水中ポンプでもいいのだが、たまたま上部フィルター用のポンプとなった。


購入したポンプと底面フィルターの塩ビ管を直結するのだ。



あとは、自作フィルターの上に、マットを敷き詰める。
こんなやつ。100均とかでも売っている。


その上に、ソイルを敷き詰める。
今回は底面フィルターを利用し、さらに水草水槽にするため底を厚めにする。
最低でも5cmはほしい。

計算してみたら、10リットルのソイル2袋あれば大丈夫らしい。
一番安いソイルをさがしたら、よく使っている「コントロソイル」が安い。

これを2袋購入した。

全部入れたらちょうどいい厚さになった(・∀・)20120618_095748.jpg


20120203_220010.jpg


上部フィルターのポンプで、いいかんじに水は循環している。
ソイルも厚いため、ろ過能力も相当なものだろう。と思う。




テーマ : 熱帯魚
ジャンル : ペット

Android アプリ内課金の実装方法

Androidでのアプリ内課金の実装方法を記述する。
すごく長くなってしまう(´・ω・`)

が、自分がアプリ内課金を実装しようと思って、いろいろネット上を探してみるが、どれも中途半端な記事しかなかったため、自分が全てを書くことにする。


ちなみに、アプリ内課金を実装する際に、参考にした参考書がこれだ

この参考書の「アプリ内課金」の部分を参考に構築したら、なんとかアプリ内課金の実装ができた。
この参考書オススメです。



■前提条件

まずアプリ内課金を使用するには、Androidのバージョンが2.3以上必要になる。
これは、アプリ内課金の処理を「Google Play Store(旧Androidマーケット)」が代行しているためである。
実はこの「Google Play Store」はAndroidのバージョン2.3からのみ対応しており、2.2以下のバージョンでは、旧Androidマーケットしか利用できないのだ。
※最初に買ったスマホXperiaは2.2までしかバージョンアップできないから困ったもんだ(´・ω・`)

しかし、2012年12月現在では、ほとんどのスマホが2.3以上になっているため、あまり問題はないと思われる。
古い端末でテストなどが出来ないことだけ注意が必要。

■ポイント
先に言ってしまうが、Androidでアプリ内課金を実装するには、多くのコーディングが必要になってくる。
しかし、一度作成したクラスなどは、別のアプリケーションでも流用できるため、1回作ってしまえば、次回からのアプリ構築は簡単なもの。

setumei1222.jpg


■開発クラス(ファイル)一覧
・パッケージ(自分の好きなパッケージでいいよ)
Base64.java
Base64DecoderException.java
BillingReceiver.java
BillingService.java
CatalogEntry.java
Consts.java
PurchaseCallback.java
PurchaseController.java
Security.java

・パッケージ「com.android.vending.billing] これは固定です。
IMarketBillingService.aidl


それではいよいよコーディングの中身に入る。
■Base64.java

Base64.javaはこのままコピペでOK。修正する部分はなし。


public class Base64 {
/** Specify encoding (value is {@code true}). */
public final static boolean ENCODE = true;

/** Specify decoding (value is {@code false}). */
public final static boolean DECODE = false;

/** The equals sign (=) as a byte. */
private final static byte EQUALS_SIGN = (byte) '=';

/** The new line character (\n) as a byte. */
private final static byte NEW_LINE = (byte) '\n';

/**
* The 64 valid Base64 values.
*/
private final static byte[] ALPHABET =
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
(byte) '9', (byte) '+', (byte) '/'};

/**
* The 64 valid web safe Base64 values.
*/
private final static byte[] WEBSAFE_ALPHABET =
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
(byte) '9', (byte) '-', (byte) '_'};

/**
* Translates a Base64 value to either its 6-bit reconstruction value
* or a negative number indicating some other meaning.
**/
private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
-5, -5, // Whitespace: Tab and Linefeed
-9, -9, // Decimal 11 - 12
-5, // Whitespace: Carriage Return
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
-9, -9, -9, -9, -9, // Decimal 27 - 31
-5, // Whitespace: Space
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
62, // Plus sign at decimal 43
-9, -9, -9, // Decimal 44 - 46
63, // Slash at decimal 47
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
-9, -9, -9, // Decimal 58 - 60
-1, // Equals sign at decimal 61
-9, -9, -9, // Decimal 62 - 64
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
-9, -9, -9, -9, -9, -9, // Decimal 91 - 96
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
-9, -9, -9, -9, -9 // Decimal 123 - 127
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
};

/** The web safe decodabet */
private final static byte[] WEBSAFE_DECODABET =
{-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
-5, -5, // Whitespace: Tab and Linefeed
-9, -9, // Decimal 11 - 12
-5, // Whitespace: Carriage Return
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
-9, -9, -9, -9, -9, // Decimal 27 - 31
-5, // Whitespace: Space
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
62, // Dash '-' sign at decimal 45
-9, -9, // Decimal 46-47
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
-9, -9, -9, // Decimal 58 - 60
-1, // Equals sign at decimal 61
-9, -9, -9, // Decimal 62 - 64
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
-9, -9, -9, -9, // Decimal 91-94
63, // Underscore '_' at decimal 95
-9, // Decimal 96
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
-9, -9, -9, -9, -9 // Decimal 123 - 127
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
};

// Indicates white space in encoding
private final static byte WHITE_SPACE_ENC = -5;
// Indicates equals sign in encoding
private final static byte EQUALS_SIGN_ENC = -1;

/** Defeats instantiation. */
private Base64() {
}

/* ******** E N C O D I N G M E T H O D S ******** */

/**
* Encodes up to three bytes of the array <var>source</var>
* and writes the resulting four Base64 bytes to <var>destination</var>.
* The source and destination arrays can be manipulated
* anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays
* are large enough to accommodate <var>srcOffset</var> + 3 for
* the <var>source</var> array or <var>destOffset</var> + 4 for
* the <var>destination</var> array.
* The actual number of significant bytes in your array is
* given by <var>numSigBytes</var>.
*
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param numSigBytes the number of significant bytes in your array
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @param alphabet is the encoding alphabet
* @return the <var>destination</var> array
* @since 1.3
*/
private static byte[] encode3to4(byte[] source, int srcOffset,
int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
// 1 2 3
// 01234567890123456789012345678901 Bit position
// --------000000001111111122222222 Array position from threeBytes
// --------| || || || | Six bit groups to index alphabet
// >>18 >>12 >> 6 >> 0 Right shift necessary
// 0x3f 0x3f 0x3f Additional AND

// Create buffer with zero-padding if there are only one or two
// significant bytes passed in the array.
// We have to shift left 24 in order to flush out the 1's that appear
// when Java treats a value as negative that is cast from a byte to an int.
int inBuff =
(numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
| (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
| (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);

switch (numSigBytes) {
case 3:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
return destination;
case 2:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
destination[destOffset + 3] = EQUALS_SIGN;
return destination;
case 1:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = EQUALS_SIGN;
destination[destOffset + 3] = EQUALS_SIGN;
return destination;
default:
return destination;
} // end switch
} // end encode3to4

/**
* Encodes a byte array into Base64 notation.
* Equivalent to calling
* {@code encodeBytes(source, 0, source.length)}
*
* @param source The data to convert
* @since 1.4
*/
public static String encode(byte[] source) {
return encode(source, 0, source.length, ALPHABET, true);
}

/**
* Encodes a byte array into web safe Base64 notation.
*
* @param source The data to convert
* @param doPadding is {@code true} to pad result with '=' chars
* if it does not fall on 3 byte boundaries
*/
public static String encodeWebSafe(byte[] source, boolean doPadding) {
return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
}

/**
* Encodes a byte array into Base64 notation.
*
* @param source the data to convert
* @param off offset in array where conversion should begin
* @param len length of data to convert
* @param alphabet the encoding alphabet
* @param doPadding is {@code true} to pad result with '=' chars
* if it does not fall on 3 byte boundaries
* @since 1.4
*/
public static String encode(byte[] source, int off, int len, byte[] alphabet,
boolean doPadding) {
byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
int outLen = outBuff.length;

// If doPadding is false, set length to truncate '='
// padding characters
while (doPadding == false && outLen > 0) {
if (outBuff[outLen - 1] != '=') {
break;
}
outLen -= 1;
}

return new String(outBuff, 0, outLen);
}

/**
* Encodes a byte array into Base64 notation.
*
* @param source the data to convert
* @param off offset in array where conversion should begin
* @param len length of data to convert
* @param alphabet is the encoding alphabet
* @param maxLineLength maximum length of one line.
* @return the BASE64-encoded byte array
*/
public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
int maxLineLength) {
int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
int len43 = lenDiv3 * 4;
byte[] outBuff = new byte[len43 // Main 4:3
+ (len43 / maxLineLength)]; // New lines

int d = 0;
int e = 0;
int len2 = len - 2;
int lineLength = 0;
for (; d < len2; d += 3, e += 4) {

// The following block of code is the same as
// encode3to4( source, d + off, 3, outBuff, e, alphabet );
// but inlined for faster encoding (~20% improvement)
int inBuff =
((source[d + off] << 24) >>> 8)
| ((source[d + 1 + off] << 24) >>> 16)
| ((source[d + 2 + off] << 24) >>> 24);
outBuff[e] = alphabet[(inBuff >>> 18)];
outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
outBuff[e + 3] = alphabet[(inBuff) & 0x3f];

lineLength += 4;
if (lineLength == maxLineLength) {
outBuff[e + 4] = NEW_LINE;
e++;
lineLength = 0;
} // end if: end of line
} // end for: each piece of array

if (d < len) {
encode3to4(source, d + off, len - d, outBuff, e, alphabet);

lineLength += 4;
if (lineLength == maxLineLength) {
// Add a last newline
outBuff[e + 4] = NEW_LINE;
e++;
}
e += 4;
}

assert (e == outBuff.length);
return outBuff;
}


/* ******** D E C O D I N G M E T H O D S ******** */


/**
* Decodes four bytes from array <var>source</var>
* and writes the resulting bytes (up to three of them)
* to <var>destination</var>.
* The source and destination arrays can be manipulated
* anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays
* are large enough to accommodate <var>srcOffset</var> + 4 for
* the <var>source</var> array or <var>destOffset</var> + 3 for
* the <var>destination</var> array.
* This method returns the actual number of bytes that
* were converted from the Base64 encoding.
*
*
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @param decodabet the decodabet for decoding Base64 content
* @return the number of decoded bytes converted
* @since 1.3
*/
private static int decode4to3(byte[] source, int srcOffset,
byte[] destination, int destOffset, byte[] decodabet) {
// Example: Dk==
if (source[srcOffset + 2] == EQUALS_SIGN) {
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12);

destination[destOffset] = (byte) (outBuff >>> 16);
return 1;
} else if (source[srcOffset + 3] == EQUALS_SIGN) {
// Example: DkL=
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18);

destination[destOffset] = (byte) (outBuff >>> 16);
destination[destOffset + 1] = (byte) (outBuff >>> 8);
return 2;
} else {
// Example: DkLE
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
| ((decodabet[source[srcOffset + 3]] << 24) >>> 24);

destination[destOffset] = (byte) (outBuff >> 16);
destination[destOffset + 1] = (byte) (outBuff >> 8);
destination[destOffset + 2] = (byte) (outBuff);
return 3;
}
} // end decodeToBytes


/**
* Decodes data from Base64 notation.
*
* @param s the string to decode (decoded in default encoding)
* @return the decoded data
* @since 1.4
*/
public static byte[] decode(String s) throws Base64DecoderException {
byte[] bytes = s.getBytes();
return decode(bytes, 0, bytes.length);
}

/**
* Decodes data from web safe Base64 notation.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param s the string to decode (decoded in default encoding)
* @return the decoded data
*/
public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
byte[] bytes = s.getBytes();
return decodeWebSafe(bytes, 0, bytes.length);
}

/**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
* @param source The Base64 encoded data
* @return decoded data
* @since 1.3
* @throws Base64DecoderException
*/
public static byte[] decode(byte[] source) throws Base64DecoderException {
return decode(source, 0, source.length);
}

/**
* Decodes web safe Base64 content in byte array format and returns
* the decoded data.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param source the string to decode (decoded in default encoding)
* @return the decoded data
*/
public static byte[] decodeWebSafe(byte[] source)
throws Base64DecoderException {
return decodeWebSafe(source, 0, source.length);
}

/**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @return decoded data
* @since 1.3
* @throws Base64DecoderException
*/
public static byte[] decode(byte[] source, int off, int len)
throws Base64DecoderException {
return decode(source, off, len, DECODABET);
}

/**
* Decodes web safe Base64 content in byte array format and returns
* the decoded byte array.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @return decoded data
*/
public static byte[] decodeWebSafe(byte[] source, int off, int len)
throws Base64DecoderException {
return decode(source, off, len, WEBSAFE_DECODABET);
}

/**
* Decodes Base64 content using the supplied decodabet and returns
* the decoded byte array.
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @param decodabet the decodabet for decoding Base64 content
* @return decoded data
*/
public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
throws Base64DecoderException {
int len34 = len * 3 / 4;
byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
int outBuffPosn = 0;

byte[] b4 = new byte[4];
int b4Posn = 0;
int i = 0;
byte sbiCrop = 0;
byte sbiDecode = 0;
for (i = 0; i < len; i++) {
sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
sbiDecode = decodabet[sbiCrop];

if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
if (sbiDecode >= EQUALS_SIGN_ENC) {
// An equals sign (for padding) must not occur at position 0 or 1
// and must be the last byte[s] in the encoded value
if (sbiCrop == EQUALS_SIGN) {
int bytesLeft = len - i;
byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
if (b4Posn == 0 || b4Posn == 1) {
throw new Base64DecoderException(
"invalid padding byte '=' at byte offset " + i);
} else if ((b4Posn == 3 && bytesLeft > 2)
|| (b4Posn == 4 && bytesLeft > 1)) {
throw new Base64DecoderException(
"padding byte '=' falsely signals end of encoded value "
+ "at offset " + i);
} else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
throw new Base64DecoderException(
"encoded value has invalid trailing byte");
}
break;
}

b4[b4Posn++] = sbiCrop;
if (b4Posn == 4) {
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
b4Posn = 0;
}
}
} else {
throw new Base64DecoderException("Bad Base64 input character at " + i
+ ": " + source[i + off] + "(decimal)");
}
}

// Because web safe encoding allows non padding base64 encodes, we
// need to pad the rest of the b4 buffer with equal signs when
// b4Posn != 0. There can be at most 2 equal signs at the end of
// four characters, so the b4 buffer must have two or three
// characters. This also catches the case where the input is
// padded with EQUALS_SIGN
if (b4Posn != 0) {
if (b4Posn == 1) {
throw new Base64DecoderException("single trailing character at offset "
+ (len - 1));
}
b4[b4Posn++] = EQUALS_SIGN;
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
}

byte[] out = new byte[outBuffPosn];
System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
return out;
}
}



■Base64DecoderException.java


public class Base64DecoderException extends Exception {
public Base64DecoderException() {
super();
}

public Base64DecoderException(String s) {
super(s);
}

private static final long serialVersionUID = 1L;
}


■Consts.java
主に課金処理についての定数が定義されている。
基本下記をコピペで問題ない

public class Consts {
// MarketBillingServiceにバインドするためのインテント
public static final String MARKET_BILLING_SERVICE_ACTION =
"com.android.vending.billing.MarketBillingService.BIND";

// 課金リクエストの種類
// (BillingServiceを起動するためのインテント)
public static final String ACTION_CONFIRM_NOTIFICATION =
"montarosu.montarosu.beetles.CONFIRM_NOTIFICATION";
public static final String ACTION_GET_PURCHASE_INFORMATION =
"montarosu.montarosu.beetles.GET_PURCHASE_INFORMATION";
public static final String ACTION_RESTORE_TRANSACTIONS =
"montarosu.montarosu.beetles.RESTORE_TRANSACTIONS";

// アプリ内課金リクエストに設定するパラメータ
public static final String BILLING_REQUEST_METHOD = "BILLING_REQUEST";
public static final String BILLING_REQUEST_API_VERSION = "API_VERSION";
public static final String BILLING_REQUEST_PACKAGE_NAME = "PACKAGE_NAME";
public static final String BILLING_REQUEST_ITEM_ID = "ITEM_ID";
public static final String BILLING_REQUEST_NOTIFY_IDS = "NOTIFY_IDS";
public static final String BILLING_REQUEST_NONCE = "NONCE";

// 同期レスポンス
public static final String BILLING_RESPONSE_RESPONSE_CODE = "RESPONSE_CODE";
public static final String BILLING_RESPONSE_PURCHASE_INTENT = "PURCHASE_INTENT";
public static final String BILLING_RESPONSE_REQUEST_ID = "REQUEST_ID";
public static long BILLING_RESPONSE_INVALID_REQUEST_ID = -1;

// 非同期レスポンス
public static final String ACTION_NOTIFY = "com.android.vending.billing.IN_APP_NOTIFY";
public static final String ACTION_RESPONSE_CODE =
"com.android.vending.billing.RESPONSE_CODE";
public static final String ACTION_PURCHASE_STATE_CHANGED =
"com.android.vending.billing.PURCHASE_STATE_CHANGED";

// 非同期レスポンスに含まれるパラメータ
public static final String NOTIFICATION_ID = "notification_id";
public static final String INAPP_SIGNED_DATA = "inapp_signed_data";
public static final String INAPP_SIGNATURE = "inapp_signature";
public static final String INAPP_REQUEST_ID = "request_id";
public static final String INAPP_RESPONSE_CODE = "response_code";

// レスポンスコードの定義
public enum ResponseCode {
RESULT_OK,
RESULT_USER_CANCELED,
RESULT_SERVICE_UNAVAILABLE,
RESULT_BILLING_UNAVAILABLE,
RESULT_ITEM_UNAVAILABLE,
RESULT_DEVELOPER_ERROR,
RESULT_ERROR;

// インデックスから対応するレスポンスコードを返す
public static ResponseCode valueOf(int index) {
ResponseCode[] values = ResponseCode.values();
if (index < 0 || index >= values.length) {
return RESULT_ERROR;
}
return values[index];
}
}

// 購入状態の定義
public enum PurchaseState {
PURCHASED,
CANCELED,
REFUNDED;

// インデックスから対応するレスポンスコードを返す
public static PurchaseState valueOf(int index) {
PurchaseState[] values = PurchaseState.values();
if (index < 0 || index >= values.length) {
return CANCELED;
}
return values[index];
}
}
}


■PurchaseCallback.java

public abstract class PurchaseCallback {
public abstract void onPurchaseComplete();
}


■PurchaseController.java

public class PurchaseController {
private static PurchaseCallback sPurchaseCallback;

public static void regist(PurchaseCallback callback) {
sPurchaseCallback = callback;
}

public static void unregist(PurchaseCallback callback) {
sPurchaseCallback = null;
}

public static void purchaseComplete() {
if (sPurchaseCallback != null) {
sPurchaseCallback.onPurchaseComplete();
}
}
}


■Security.java
ここでは、自分のアプリKeyを設定しなければならない。
設定する箇所は下記を参照していただきたい。

public class Security {
private static final String TAG = "Security";

private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
private static final SecureRandom RANDOM = new SecureRandom();

/**
* This keeps track of the nonces that we generated and sent to the
* server. We need to keep track of these until we get back the purchase
* state and send a confirmation message back to Android Market. If we are
* killed and lose this list of nonces, it is not fatal. Android Market will
* send us a new "notify" message and we will re-generate a new nonce.
* This has to be "static" so that the {@link BillingReceiver} can
* check if a nonce exists.
*/
private static HashSet sKnownNonces = new HashSet();

/**
* A class to hold the verified purchase information.
*/
public static class VerifiedPurchase {
public PurchaseState purchaseState;
public String notificationId;
public String productId;
public String orderId;
public long purchaseTime;
public String developerPayload;

public VerifiedPurchase(PurchaseState purchaseState, String notificationId,
String productId, String orderId, long purchaseTime, String developerPayload) {
this.purchaseState = purchaseState;
this.notificationId = notificationId;
this.productId = productId;
this.orderId = orderId;
this.purchaseTime = purchaseTime;
this.developerPayload = developerPayload;
}
}

/** Generates a nonce (a random number used once). */
//ノンスを生成して登録
public static long generateNonce() {
long nonce = RANDOM.nextLong();
sKnownNonces.add(nonce);
return nonce;
}

public static void removeNonce(long nonce) {
sKnownNonces.remove(nonce);
}

public static boolean isNonceKnown(long nonce) {
return sKnownNonces.contains(nonce);
}

/**
* Verifies that the data was signed with the given signature, and returns
* the list of verified purchases. The data is in JSON format and contains
* a nonce (number used once) that we generated and that was signed
* (as part of the whole data string) with a private key. The data also
* contains the {@link PurchaseState} and product ID of the purchase.
* In the general case, there can be an array of purchase transactions
* because there may be delays in processing the purchase on the backend
* and then several purchases can be batched together.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
*/
public static ArrayList verifyPurchase(String signedData, String signature) {
if (signedData == null) {
logout.out(new Security().getClass(),"onCreate()", "data is null");
return null;
}
logout.out(new Security().getClass(),"onCreate()", "signedData: " + signedData);
boolean verified = false;
if (!TextUtils.isEmpty(signature)) {
/**
* Compute your public key (that you got from the Android Market publisher site).
*
* Instead of just storing the entire literal string here embedded in the
* program, construct the key at runtime from pieces or
* use bit manipulation (for example, XOR with some other string) to hide
* the actual key. The key itself is not secret information, but we don't
* want to make it easy for an adversary to replace the public key with one
* of their own and then fake messages from the server.
*
* Generally, encryption keys / passwords should only be kept in memory
* long enough to perform the operation they need to perform.
*/
String base64EncodedPublicKey = "お主専用のKeyを設定するでおじゃる";
PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
verified = Security.verify(key, signedData, signature);
if (!verified) {
logout.out(new Security().getClass(),"onCreate()", "signature does not match data.");
return null;
}
}

JSONObject jObject;
JSONArray jTransactionsArray = null;
int numTransactions = 0;
long nonce = 0L;
try {
jObject = new JSONObject(signedData);

// The nonce might be null if the user backed out of the buy page.
nonce = jObject.optLong("nonce");
jTransactionsArray = jObject.optJSONArray("orders");
if (jTransactionsArray != null) {
numTransactions = jTransactionsArray.length();
}
} catch (JSONException e) {
return null;
}

//生成したノンスの確認
if (!Security.isNonceKnown(nonce)) {
logout.out(new Security().getClass(),"onCreate()", "Nonce not found: " + nonce);
return null;
}

//JSONを分解
ArrayList purchases = new ArrayList();
try {
for (int i = 0; i < numTransactions; i++) {
JSONObject jElement = jTransactionsArray.getJSONObject(i);
int response = jElement.getInt("purchaseState");
PurchaseState purchaseState = PurchaseState.valueOf(response);
String productId = jElement.getString("productId");
String packageName = jElement.getString("packageName");
long purchaseTime = jElement.getLong("purchaseTime");
String orderId = jElement.optString("orderId", "");
String notifyId = null;
if (jElement.has("notificationId")) {
notifyId = jElement.getString("notificationId");
}
String developerPayload = jElement.optString("developerPayload", null);

// If the purchase state is PURCHASED, then we require a
// verified nonce.
if (purchaseState == PurchaseState.PURCHASED && !verified) {
continue;
}
purchases.add(new VerifiedPurchase(purchaseState, notifyId, productId,
orderId, purchaseTime, developerPayload));
}
} catch (JSONException e) {
logout.out(new Security().getClass(),"onCreate()", "JSON exception: "+ e);
return null;
}
//ノンスの削除
removeNonce(nonce);
return purchases;
}

/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey Base64-encoded public key
* @throws IllegalArgumentException if encodedPublicKey is invalid
*/
public static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
logout.out(new Security().getClass(),"onCreate()", "Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Base64DecoderException e) {
logout.out(new Security().getClass(),"onCreate()", "Base64 decoding failed.");
throw new IllegalArgumentException(e);
}
}

/**
* Verifies that the signature from the server matches the computed
* signature on the data. Returns true if the data is correctly signed.
*
* @param publicKey public key associated with the developer account
* @param signedData signed data from server
* @param signature server signature
* @return true if the data and signature match
*/
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
logout.out(new Security().getClass(),"onCreate()", "signature: " + signature);
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
logout.out(new Security().getClass(),"verify()", "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
logout.out(new Security().getClass(),"verify()", "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
logout.out(new Security().getClass(),"verify()", "Invalid key specification.");
} catch (SignatureException e) {
logout.out(new Security().getClass(),"verify()", "Signature exception.");
} catch (Base64DecoderException e) {
logout.out(new Security().getClass(),"verify()", "Base64 decoding failed.");
}
return false;
}
}


コード途中にある、変数「base64EncodedPublicKey」にアプリ毎のライセンスKeyを設定する。
※ポイント
2012年12月では、この設定するライセンスKeyがアプリごとに設定することになった。
前まではアカウントに対してライセンスKeyは一つだけだったのだが。。
ライセンスKeyの生成にはアプリをデベロッパコンソール上にアップする必要があるため、
ライセンスKeyの生成は後述する。

■CatalogEntry.java
CatalogEntryでは、実際の課金アイテムのIDなどをリスト化している。

public class CatalogEntry {
public String productId; // プロダクトID
public int nameId; // コンテンツ名
public int resId; // リソースID
public String fileName; // 保存ファイル名

public CatalogEntry(String productId, int nameId, int resId, String fileName) {
this.productId = productId;
this.nameId = nameId;
this.resId = resId;
this.fileName = fileName;
}

/***
* コンテンツリスト
*/
public static final CatalogEntry[] CATALOG = new CatalogEntry[] {
new CatalogEntry("coin100", R.string.app_name, -1, ""),
new CatalogEntry("coin400", R.string.app_name, -1, ""),
new CatalogEntry("coin1000", R.string.app_name, -1, ""),
};
}

課金アイテムを購入するために、Google PlayをCallするときに渡すアイテムIDが、「productId」となる。
なので、ここでリスト化するproductIdは、実際にGooglePlay上にも登録する。
ほかに「nameId、resId、fileName」とあるが、参考にしたファイルをそのまま使用していた名残であり、実際には必要ない。productIdのみあればとりあえずOK。
登録は後述する

■BillingReceiver.java
課金サービス用のレシーバ機能一式

public class BillingReceiver extends BroadcastReceiver {
/***
* 非同期応答メッセージ(ブロードキャストインテント)を受信する
*/
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
logout.out(this.getClass(),"onReceive()","action:"+action);
if (Consts.ACTION_NOTIFY.equals(action)) {
// トランザクション情報の通知IDを取得する
String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);

// トランザクション情報を取得する
notify(context, notifyId);

} else if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
// 署名済みトランザクション情報と署名を取得する
String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);

// トランザクション情報を処理する
purchaseStateChanged(context, signedData, signature);

} else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
// リクエストIDとレスポンスコードを取得する
long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE, ResponseCode.RESULT_ERROR.ordinal());

// レスポンスを処理する
checkResponseCode(context, requestId, responseCodeIndex);

}
}

/***
* トランザクション情報を取得する
* (BillingServiceにGET_PURCHASE_INFORMATIONリクエストを送信させる)
*/
private void notify(Context context, String notifyId) {
logout.out(this.getClass(),"notify()","notifyId:"+notifyId);
Intent intent = new Intent(Consts.ACTION_GET_PURCHASE_INFORMATION);
intent.setClass(context, BillingService.class);
intent.putExtra(Consts.NOTIFICATION_ID, notifyId);
context.startService(intent);
}

/***
* トランザクション情報を処理する
* (BillingServiceに署名済みトランザクション情報と署名を渡して処理させる)
*/
private void purchaseStateChanged(Context context, String signedData, String signature) {
logout.out(this.getClass(),"purchaseStateChanged()","signedData:"+signedData);
logout.out(this.getClass(),"purchaseStateChanged()","signature:"+signature);
Intent intent = new Intent(Consts.ACTION_PURCHASE_STATE_CHANGED);
intent.setClass(context, BillingService.class);
intent.putExtra(Consts.INAPP_SIGNED_DATA, signedData);
intent.putExtra(Consts.INAPP_SIGNATURE, signature);
context.startService(intent);
}

/***
* レスポンスを処理する
* (BillingServiceにリクエストIDとレスポンスコードを渡してを処理させる)
*/
private void checkResponseCode(Context context, long requestId, int responseCodeIndex) {
logout.out(this.getClass(),"checkResponseCode()","requestId:"+requestId);
logout.out(this.getClass(),"checkResponseCode()","responseCodeIndex:"+responseCodeIndex);
Intent intent = new Intent(Consts.ACTION_RESPONSE_CODE);
intent.setClass(context, BillingService.class);
intent.putExtra(Consts.INAPP_REQUEST_ID, requestId);
intent.putExtra(Consts.INAPP_RESPONSE_CODE, responseCodeIndex);
context.startService(intent);
}
}



■BillingService.java
これが一番重要なクラス。アプリ内課金処理がgoogle Playで実行された後に、呼び出されるクラス。

public class BillingService extends Service implements ServiceConnection {
private static IMarketBillingService mService;
Context context = null;
// 処理待ちのリクエスト
private static BillingRequest mPendingRequests = null;

public void setContext(Context context) {
attachBaseContext(context);
this.context = context;
//billingHandler = new Handler(billBack);
}



/***
* すべてのリクエストに共通する処理を定義したスーパークラス
*/
abstract class BillingRequest {
// リクエストID
protected long mRequestId;

// リクエストを処理するスレッド
abstract protected long run() throws RemoteException;

// リクエストを処理する
public boolean runRequest() {
// MarketBillingServiceに接続済みならそのままリクエストを処理する
if (runIfConnected()) {
return true;
}

// MarketBillingServiceに接続されていないので接続する
if (bindToMarketBillingService()) {
// 接続が成功したらリクエストをPendingRequestsに追加する
mPendingRequests = this;
return true;
}

return false;
}

// MarketBillingServiceに接続済みならリクエストを処理する
public boolean runIfConnected() {
if (mService != null) {
try {
// リクエストを処理する
mRequestId = run();

// 送信したリクエストのリクエストIDをログに表示する
logout.out(this.getClass(),"runIfConnected()","sent request (requestId:"+mRequestId+")");

return true;
} catch (RemoteException e) {
onRemoteException(e);
}
}
return false;
}

protected void onRemoteException(RemoteException e) {
mService = null;
}

// 基本Bundleを生成する
protected Bundle makeRequestBundle(String method) {
logout.out(this.getClass(),"makeRequestBundle()","method:"+method);
logout.out(this.getClass(),"makeRequestBundle()","getPackageName:"+getPackageName());
Bundle request = new Bundle();
request.putString(Consts.BILLING_REQUEST_METHOD, method);
request.putInt(Consts.BILLING_REQUEST_API_VERSION, 2);
request.putString(Consts.BILLING_REQUEST_PACKAGE_NAME, getPackageName());
return request;
}
}

/***
* MarketBillingServiceにバインドする
*/
private boolean bindToMarketBillingService() {
logout.out(this.getClass(),"bindToMarketBillingService()","MarketBillingServiceにバインドする");
// MarketBillingServiceにバインドする
boolean bindResult = bindService(new Intent(
Consts.MARKET_BILLING_SERVICE_ACTION), this,
Context.BIND_AUTO_CREATE);

if (bindResult) {
return true;
}
return false;
}

/***
* REQUEST_PURCHASEリクエストを送信する
*/
class RequestPurchase extends BillingRequest {
public final String mProductId;

public RequestPurchase(String itemId) {
logout.out(this.getClass(),"RequestPurchase()","REQUEST_PURCHASEリクエストを送信する:"+itemId);
mProductId = itemId;
}

@Override
protected long run() throws RemoteException {
logout.out(this.getClass(),"run()","run()");
// 基本Bundleを作成する
Bundle request = makeRequestBundle("REQUEST_PURCHASE");

// BundleにプロダクトIDを追加する
request.putString(Consts.BILLING_REQUEST_ITEM_ID, mProductId);
logout.out(this.getClass(),"run()","mProductId:"+mProductId);

// MarketServiceに送信する
Bundle response = mService.sendBillingRequest(request);

// レスポンスに含まれているPendingIntentを取得する
PendingIntent pendingIntent = response.getParcelable(Consts.BILLING_RESPONSE_PURCHASE_INTENT);
logout.out(this.getClass(),"run()","pendingIntent:"+(pendingIntent == null ? "null!!!":""));
if (pendingIntent == null) {
return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
}

// 取得したPendingIntentを使ってチェックアウト画面を起動する
Intent intent = new Intent();
startBuyPageActivity(pendingIntent, intent);

return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
}
}

/***
* GET_PURCHASE_INFORMATIONリクエストを送信する
*/
class GetPurchaseInformation extends BillingRequest {
long mNonce;
final String[] mNotifyIds;

public GetPurchaseInformation(String[] notifyIds) {
mNotifyIds = notifyIds;
}

@Override
protected long run() throws RemoteException {
logout.out(this.getClass(),"run()","ノンスを生成する");
// ノンスを生成する
mNonce = Security.generateNonce();

logout.out(this.getClass(),"run()","mNonce:"+mNonce);
logout.out(this.getClass(),"run()","mNotifyIds:"+mNotifyIds);
// GET_PURCHASE_INFORMATIONリクエストにノンスと通知IDを設定して送信する
Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
request.putLong(Consts.BILLING_REQUEST_NONCE, mNonce);
request.putStringArray(Consts.BILLING_REQUEST_NOTIFY_IDS, mNotifyIds);
Bundle response = mService.sendBillingRequest(request);

return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
}

@Override
protected void onRemoteException(RemoteException e) {
super.onRemoteException(e);

// 例外が発生したらノンスを削除
Security.removeNonce(mNonce);
}
}

/***
* CONFIRM_NOTIFICATIONSリクエストを送信する
*/
class ConfirmNotifications extends BillingRequest {
final String[] mNotifyIds;

public ConfirmNotifications(String[] notifyIds) {
mNotifyIds = notifyIds;
}

@Override
protected long run() throws RemoteException {
logout.out(this.getClass(),"run()","mNotifyIds:"+mNotifyIds);
Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
request.putStringArray(Consts.BILLING_REQUEST_NOTIFY_IDS, mNotifyIds);
Bundle response = mService.sendBillingRequest(request);

return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
}
}

// GET_PURCHASE_INFORMATIONリクエストを処理する
public boolean requestPurchase(String productId) {
return new RequestPurchase(productId).runRequest();
}

// PURCHASE_STATE_CHANGEDリクエストを処理する
private boolean getPurchaseInformation(String[] notifyIds) {
return new GetPurchaseInformation(notifyIds).runRequest();
}

// CONFIRM_NOTIFICATIONリクエストを処理する
private boolean confirmNotifications(String[] notifyIds) {
return new ConfirmNotifications(notifyIds).runRequest();
}

@Override
public void onStart(Intent intent, int startId) {
String action = intent.getAction();
logout.out(this.getClass(),"onStart()","action:"+action);
// GET_PURCHASE_INFORMATIONリクエストを処理する
if (Consts.ACTION_GET_PURCHASE_INFORMATION.equals(action)) {
String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
getPurchaseInformation(new String[] { notifyId });

// PURCHASE_STATE_CHANGEDリクエストを処理する
} else if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);

purchaseStateChanged(signedData, signature);

// CONFIRM_NOTIFICATIONリクエストを処理する
} else if (Consts.ACTION_CONFIRM_NOTIFICATION.equals(action)) {
String[] notifyIds = intent.getStringArrayExtra(Consts.NOTIFICATION_ID);
confirmNotifications(notifyIds);

// レスポンスを処理する
} else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
// RequestIDとResponseCodeをログに表示する
long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE, ResponseCode.RESULT_ERROR.ordinal());
ResponseCode responseCode = ResponseCode.valueOf(responseCodeIndex);
logout.out(this.getClass(),"onStart()","requestId:"+requestId+" / "+"responseCode:"+responseCode);
}
}



private void purchaseStateChanged(String signedData, String signature) {

logout.out(this.getClass(),"purchaseStateChanged()","☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆");
ArrayList<Security.VerifiedPurchase> purchases;

// 受信したトランザクション情報の整合性を検証する
purchases = Security.verifyPurchase(signedData, signature);
if (purchases == null) {
return;
}

logout.out(this.getClass(),"purchaseStateChanged()","サイズ:"+purchases.size());
// トランザクション情報を処理する
ArrayList<String> notifyList = new ArrayList<String>();
for (VerifiedPurchase vp : purchases) {
if (vp.notificationId != null) {
// トランザクションごとに通知IDを保持する
notifyList.add(vp.notificationId);
}
logout.out(this.getClass(),"purchaseStateChanged()","vp.productId:"+vp.productId);

// トランザクションごとに対象のコンテンツをコピーする
for (CatalogEntry catalog : CatalogEntry.CATALOG) {
if (catalog.productId.equals(vp.productId)) {
//Utils.copyMusic(this, catalog.resId, catalog.fileName);
if(vp.productId.equals("coin100")){
                            なにかしらの処理を記述
}
if(vp.productId.equals("coin400")){
                            なにかしらの処理を記述
}
if(vp.productId.equals("coin1000")){
                            なにかしらの処理を記述
}
}
}
}

// トランザクション情報を取得できた場合は、CONFIRM_NOTIFICATIONSリクエストを送信する
if (!notifyList.isEmpty()) {
String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
confirmNotifications(notifyIds);
}

}

Handler.Callback msgCall = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(context, msg.obj.toString(), Toast.LENGTH_LONG).show();
return false;
}
};

/***
* MarketBillingService接続時に処理できなかったリクエストを処理する
*/
private void runPendingRequests() {
if (mPendingRequests != null) {
if (mPendingRequests.runIfConnected()) {
mPendingRequests = null;
} else {
bindToMarketBillingService();
return;
}
}
}

@Override
public IBinder onBind(Intent intent) {
return null;
}

public void onServiceConnected(ComponentName name, IBinder service) {
// IMarketBillingServiceインタフェースへの参照を作成する
mService = IMarketBillingService.Stub.asInterface(service);

// 接続されたタイミングでリクエストを処理する
runPendingRequests();
}

public void onServiceDisconnected(ComponentName name) {
mService = null;
}

public void unbind() {
unbindService(this);
}

/***
* チェックアウト画面を表示する
*/
void startBuyPageActivity(PendingIntent pendingIntent, Intent intent) {
try {
this.startIntentSender(pendingIntent.getIntentSender(), intent, 0, 0, 0);
} catch (Exception e) {
//
}
}
}

基本上記をコピペすればよい。

メインとなる、課金処理が完了された後に呼ばれるのが
「purchaseStateChanged」メソッドとなる。
記述内の「vp.productId」に購入したproductIdが格納されてくるため、CatalogEntry と比較を行うことで、どの課金アイテムが購入されたかがわかる。
あとは、そのアイテムごとにどのように処理をするのかを記述すればよい。

※APIバージョンについて
Consts.BILLING_REQUEST_API_VERSIONにて設定している「API_VERSION」について
2012年12月10日からバージョン3まで存在している。

ライセンスKeyがアプリ毎に設定する使用になってから、このAPI_VERSIONが2、もしくは3ではないと上手く動作しないようだ。

調べてみると、ライセンスKeyを新しいアプリ毎のKeyを設定し、API_VERSIONが2で、上手く動作しているらしい。


■IMarketBillingService.aidlの作成
パッケージ「com.android.vending.billing」を作成し、その配下に「IMarketBillingService.aidl」というファイルを作成する
内容は以下

/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.android.vending.billing;

import android.os.Bundle;

interface IMarketBillingService {
/** Given the arguments in bundle form, returns a bundle for results. */
Bundle sendBillingRequest(in Bundle bundle);
}

ファイルを作成すると、自動でgenフォルダに
パッケージ:com.android.vending.billing
ファイル:IMarketBillingService.java
が作成される。
aidlファイルについての詳細は省略する


以上がアプリ内課金に必要なclassとなる。

■アプリ内課金の実行ロジックについて


では実際にアプリ内課金を実行する方法を記述する

//課金サービスの生成とチェック
BillingService mBillingService = new BillingService();
mBillingService.setContext(this);
//アプリ内課金処理開始
mBillingService.requestPurchase(CatalogEntry.CATALOG[0].productId)


アプリ内課金の実行は簡単。
作成した「BillingService」クラスを生成して、メソッド「requestPurchase」をCallする際に、購入したアイテム用のproductIdを渡してあげるだけ。

メソッド「requestPurchase」をCallすることで、GooglePlayStoreが起動し、決済画面が表示される。
決済が完了すると、「BillingService」クラスの、メソッド「purchaseStateChanged」がCallされるというわけだ。
このメソッドに渡されるproductIdが、Callする際に引数で渡したproductIdになっているというわけです。


以上が、アプリ内課金の実装とサービスをCallするためのコードとなる。

次にマニフェストファイルの設定について

■マニフェストファイル

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.android.vending.BILLING" />


<service android:name="billing.montarosu.montarosu.billings.BillingService" />
<receiver android:name="billing.montarosu.montarosu.billings.BillingReceiver">
<intent-filter>
<action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
<action android:name="com.android.vending.billing.RESPONSE_CODE" />
<action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
</intent-filter>
</receiver>



■ライセンスKeyの生成

Security.javaで必要なライセンスKeyの生成について記述する。
デベロッパコンソールにアプリをアップする必要があるため、とりあえずデベロッパコンソールにアップする。
ステータスを「非公開」にすれば誰にも見られないため、とりあえずapkファイルを作成してみる。
apkファイルの作成は省略する。

デベロッパコンソールにアプリをアップし、詳細画面の左側「サービスと API」を開と、
右側にライセンスKeyが表示されるので、そのKeyをSecurityクラスの変数「base64EncodedPublicKey」に追記すればよい。

setumei21.jpg


■アプリ内課金用のアイテムをデベロッパコンソールへ登録する

さー残りもわずか。
肝心の購入用アイテムをデベロッパコンソール上に登録する。

ライセンスKeyの画面で、左側「アプリ内サービス」をクリックし、「新しいサービスを追加」をクリックする。
まず、サービスのタイプには複数ある
1.管理対象の商品
2.管理対象外の商品
3.定期購入

3の定期購入はまだやったことが無いからスルー。
1、2についてだが、
1の「管理対象」をいうのは、1回だけ購入できるというもの。
例でいうと、RPGにある「伝説の剣」など、そのアプリ内で1回だけ購入させたいもののこと。
2の「管理対象外」というのは1の逆で、何回でも購入できるもの
例でいうと、RPGの回復系アイテムみたいなものだ。

「サービス ID 」には、プログラムで設定している「productId」を設定する。
コンソール側に設定するサービスIDとプログラム側で引数で渡すproductIdが一致することで、PlayStoreで決済が可能になるのだ。

最後に、設定を完了する際に「公開」として保存すること。
「公開」にしなければサービスが有効にならないため。
これは、本アプリ側が「非公開」でも問題ない。

本アプリを「非公開」、サービスを「公開」に設定することで、まだアプリ自体を「公開」にしなくても、デバッグができるようになるのだ。

デベロッパコンソール側での設定は以上となる。

これで、アプリ内課金の実装については完成となる。

次はデバッグ方法について。
これはまた別に書く。
疲れた。

テーマ : android
ジャンル : コンピュータ

Android SDカードの確認と容量の確認

Android端末で使用しているSDカード(外部記憶)について

端末をPCへ接続してPCにSDカードをマウントしている状態だと、端末上からは「使用不可(アンマウント)」状態になってしまうため、アプリ内でSDカードを使用している場合は気をつけなければならない(エラーになる)。

■SDカードのマウント確認

boolean rst = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());


戻り値としてtrue,falseが戻ってくる。
SDカードが端末上でアンマウント状態だと、SDカードが使用できないため「false」が返ってくる。

アプリとしては、falseが返ってきた場合に、アプリを強制終了させるとよいかも。。

if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
alert.setCancelable(false);
alert.setTitle("Error");
alert.setMessage("SD Card Error");
alert.setPositiveButton("close", new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int which) {
activity.finish();
}
});
alert.show();
return false;
}


■SDカードの容量の確認
SDカードの容量の確認

File sd = Environment.getExternalStorageDirectory();
int size = 0;
StatFs fs = new StatFs(sd.getAbsolutePath());
size = fs.getBlockSize() * fs.getAvailableBlocks();

端末の容量確認

File hon = Environment.getDataDirectory();
fs = new StatFs(hon.getAbsolutePath());
int size2 = fs.getBlockSize() * fs.getAvailableBlocks();


どちらもバイトで返ってくるため、わかりやすく1024で割るなりして、kバイト、mバイトへの変換が必要かもしれない。

テーマ : android
ジャンル : コンピュータ

Mac Book AIr がスリープ状態から勝手に復帰してしまう問題の対策

普段、Mac Book Airを持ち歩いているのだが、一つ困ったことがある。

それは、「スリープ状態からいつの間にか勝手に復帰している」ということ

家について、さぁ〜やろうかな とMacを鞄から取り出すと、既に本体が熱い。
開いてみると既に充電が3割しか無い。

100%状態でスリープしていたはずなのに、いつの間にか復帰していた、
さらに充電もなくなっている。
これは非常に困る。
つーかふざけんなよ。

しかし、調べてみると解決策があった。


■ Bluetoothの設定を変更する。

Bluetoothの設定にスリープを勝手に復帰してしまう設定がある

システム環境設定 → Bluetooth を開いてみる。
「詳細設定」ボタンをクリックする。
 2012-

「Bluetoothデバイスが・・・」のチェックを外す
スクリーンショット 2012-12-17 2

スリープ状態でも、近場でBluetoothを検知したら勝手に復帰しまっせという
なんとも自分にとっては迷惑な機能(´・ω・`)

■省エネルギーの設定

Bluetoothの他に「省エネルギー」の設定も確認が必要

先ほどと同じように
システム環境設定 → 省エネルギー をクリック
スクリーンショット 2012-12-17 224145
「wifiネットワークアクセス・・・」のチェックを外す。

つまりはスリープ状態でもwifi検知したら勝手に復帰するからな
というまたしても自分にとっては迷惑な機能(´・ω・`)

これで勝手にスリープから復帰することは無くなるのかな?

Android Java ファイルのダウンロード

Androidにて
ネット上にあるファイルを、自分の端末上に保存(ダウンロード)する方法は、基本的にJavaと同じ。



public static boolean Download(String url,String path){
FileOutputStream out = null;
InputStream in = null;
try{
URL url2 = new URL(url); // ダウンロードする URL
URLConnection conn = url2.openConnection();
in = conn.getInputStream();

File file = new File(path); // 保存先
out = new FileOutputStream(file, false);
byte[] bytes = new byte[512];
while(true){
int ret = in.read(bytes);
if(ret == 0) break;
out.write(bytes, 0, ret);
}

return true;
}catch(Exception ex){
logout.out(new Com().getClass(), "Download()", ex);
return false;
}finally{
try{ if(out!=null) out.close(); }catch(Exception ex){}
try{ if(in!=null) in.close(); }catch(Exception ex){}
}
}


ただし、Androidでは外部記憶「SDカード」にファイルを出力する際には、マニフェストファイルに以下の記述が必要になる。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
これをマニフェストファイルに追記する。

テーマ : android
ジャンル : コンピュータ

Android 音楽・BGMを再生する(MediaPlayer)

Androidで音楽ファイルを再生するには、「MediaPlayer」を利用します。

使用できる音楽ファイルは2種類あります。

・リソースファイルから再生する
・ローカルファイルから再生する

■リソースファイルから再生する方法


MediaPlayer mp = MediaPlayer.create(this, R.raw.music);
mp.start(); //再生

再生するリソースファイルは「res/raw」に格納する

■ローカルファイルから再生する方法

MediaPlayer mp = new MediaPlayer();
mp.setDataSource("ファイルのパス");
mp.prepare();
mp.start(); //再生

「prepare」は再生するための事前準備。start()の前に実行しておく。

停止する方法

mp.stop(); //停止


テーマ : android
ジャンル : コンピュータ

熱帯魚 RRE.A キングコブラハイドーサルの稚魚(4週間後)



RRE.Aキングコブラハイドーサルの稚魚も産まれてから4週間経つ。

毎日確認してるが多分1匹も☆になっていない(・∀・)
ハリ病も無しで30数匹みんな元気だ
グッピーの稚魚をハリ病にさせない水槽で作った稚魚用水槽のおかげである。

体長も3cmほどに成長してきているため、今週末にでも本水槽に移動させることを検討中。

テーマ : 熱帯魚
ジャンル : ペット

カレーパン TOPVALUのカレードナツ

20121208_135600.jpg


週末買い物に行くイオンにカレーパンがあった。
「TOPVALUのカレードーナツ」

TOPVALUは安くていいと思うが、こと食品に関しては微妙な感じ。
特にアルコール類はまずい。

安いため、第三のビールを買って飲んだことがあるが
ただ苦いだけ
だった
喉ごしもなにもあったもんじゃない。
これはただの苦い水だ。
「安いんだから文句いうな」だと思うのだが、いくら安くても最低限「おいしい」と思えないと
商品としては無価値になるんではないかと思う。
「美味しい」じゃなくていい「おいしい」でいいのだ。

という経験があるため、あまり期待せず購入したのだが。

やはり期待通りだった(=゚ω゚=)


ほのかにカレーが香る具。
よくあるカレーパンの生地。
つまりはあまりカレーの味がしないカレーパン。

自分の中ではカレーパンという名前がついていることに少し疑問が残る(´・ω・`)

満足度:★☆☆☆☆
予想通りです

テーマ : パン
ジャンル : グルメ

WindowsからMacへのリモートディスクトップ接続

WindowsからMacへリモートディスクトップ接続する方法について
有名なソフトに「Splashtop」がある。

接続先となるMacに「Splashtop Streamer」をインストールし、
接続するWindowsに「Splashtop Remote Desktop」をインストールする。

この「Splashtop」がすごいのは、AndroidやiPhoneからWindowsやMacにリモートディスクトップ接続が可能なこと
これはすごい(・∀・)


■Macに「Splashtop Streamer」をインストール
まずは、リモートディスクトップ接続したいMacに「Splashtop Streamer」をインストールします。
http://www.splashtop.com/streamer/downloadstart

アクセスすると自動でMac用のアプリがダウンロードされます(されるはず)

ダウンロードしたファイルを解凍してインストールを行います。

インストールが完了したら
「基本設定」→「その他」でセキュリティコードを設定します。
このセキュリティコードは、外部から接続する際のパスワードになります。

以上でMac側の設定は終わりです。

■Windowsに「Splashtop Remote Desktop」をインストール

Windowsには「Splashtop Remote Desktop」をインストールします。
http://www.splashtop.com/ja/splashtop2?from=product#otherproducts-tab
上記サイトから対応するOSのアプリをダウンロードしてインストールします。
(※このサイトにたどり着くまでが大変だった。。(´・ω・`))

ダウンロードしてインストールすれば完了。

アプリを起動するとネットワーク上から「Splashtop Streamer」が実行されているPCを自動でリスト表示してくれます。
これは便利!!

先ほど設定したMacを見つけ、開くとパスワードを聞かれるので、先ほど設定したセキュリティコードを入力するとリモートディスクトップ接続ができます。

Mac VMware FusionのゲストOSへ外部PCからリモートディスクトップ接続

MacのVMware Fusion上のゲストOSへ、別のPCからリモートディスクトップで接続する方法です。

■ゲストOSのネットワーク接続の変更

ゲストOSのネットワーク接続環境は、Nat接続とブリッジ接続があります。

デフォルトではNat接続が設定されており、この設定だとゲストOSへ別のPCからリモートディスクトップ接続が出来ません。

まずは、ゲストOSのネットワークを「ブリッジ接続」に変更します。

・ブリッジ接続設定

メニューの「仮想マシン」→「設定」→「ネットワークアダプタ」を開きます。
「物理ネットワークに直接接続」に変更します。
kasouimg2.jpg

・IPアドレスの確認
ゲストOS上のコマンドプロンプトで「ipconfig」を実行し、IPアドレスを確認します。

このIPで外部PCからリモート接続が可能になります。

■ゲストOS リモートディスクトップの有効化

もちろんゲストOS側ではリモートディスクトップの許可が必要です。
詳細な手順は以下を参照してください。
http://pasofaq.jp/windows/mycomputer/remotedesktop7.htm

WindowsのVMwareとMacのVMware FusionでゲストOSを共有する

■イメージ
kasouimg.jpg


家ではMacBookAirを使っており、会社ではWindowsXPを使っている。

Androidの開発を家でも会社でもやっているため、DropBoxを利用してworkspaceそのものを、Dropboxに含めることで家でも会社でも同じソースを続けて修正していた。

これは非常に便利だが、やはりいろいろと不便なこともある。
1.ソースは一緒だが環境が違うため、アプリをデバッグする際に、一度端末からアンインストールが必要になる
2.必要なソフトが両環境に必要(画像編集ソフトなど)
3.細かい環境が異なる

などなどある。

別にたいした問題でもないで、開発には別に支障はなかったのだが、先日、新しい案件で一緒になった上司と話しているときに、VMFusionの話しを聞いた。

その上司もMacBookAirを保持しており、いわゆる生粋のMacユーザ。ちなみに私はMacファンではないw

なにげなく打ち合わせの中でMacを取り出しているのを見ると。
ふむふむ・・・Macの中でWindowsが起動しています。
で話しを聞いているとVMFusionを使用してWindowsを立ち上げていると。
「そーなんだー」としか思っていなかったが、そこからの話しが面白かったw

1.VMは現在存在しているPCをそのまま仮想環境へ移行できる
2.ゲストOS自体のファイルを外付けHDDなどに保存すれば、OSそのものを持ち運びできる。

私には②が非常に興奮する内容だった。

DropBoxを利用する前は、Eclipse自体をUSBに保存して持ち運んでいたりしていたのだが、故障したり、やはり起動する環境によって、不便なことが多かった。

それが仮想化することで、環境自体を持ち運びできるようになるのである。
今までPC毎の環境で苦労していたことが無くなるのだ。

ちなみに上司が実演してくれたことは
・会社のPC環境をVMを利用してエクスポートする(OS、アプリ等全てが同じ構成)
 エクスポートした仮想環境は外付けHDDへ保存。
・会社のPCでVMPlayerを起動し、エクスポートした仮想環境を起動
・一旦サスペンド(?)して、VMを終了。外付けHDDをMACに接続し、MACのVMFusionから起動
 さっき一旦サスペンドした環境が再開できる

というものだった。


■WindowsのVMwareとMacのVMware FusionでゲストOSを共有する方法
だいぶ前置きが長くなってしまったが。。

■やりたいこと
やりたいことは、VMでつくった仮想環境を家のMacと会社のWindowsで共有して使いたいということ

○準備1 外付けHDDを購入しexFatでフォーマットする

仮想環境をMacとWindows両方で利用できるように、仮想環境を格納するHDDをexFatでフォーマットします。

まずは仮想環境を格納する外付けHDD

なるべく処理能力を向上させるために、USB3.0に対応するHDDを選んだ。
ただ、会社のPCはUSB3.0に対応しているた、Macは対応していないので残念。

容量は多いに越したことはないので500GBで安い上記HDDにした。
SSDならさらに早いだろうが、値段が高いからね~


○exFatでのフォーマット
Macでは、Windowsのフォーマット「NTFS」が利用できないため、両環境でアクセスできるフォーマット「exFat」を利用することにする。
exFatでのフォーマットはXPでは出来ないので、今回はWindows7を利用した。
Windows7以外でも出来るかも知れないが。

HDDをWindows7に接続したら、右クリック「フォーマット」でexFatを選択。完了。


■MacにVMFusionをインストール

Macに仮想環境を構築するソフトは複数あるが、お勧めなのがVMware Fusion
有料だがMacとゲストOSとのファイルのやり取りや、現行OSのエクスポートなど無料ソフトより機能が豊富。
値段も4,000円程度であるため、機能面を考えると安い買い物になるだろう。

VMwareは会社のサーバ構築でも利用しているし、VMに慣れておくことのは必須だと思う。
それにWindowにインストールするVMwarePlayerは無料だし。
同じ用にMicrosoftのHyperVも同様。


インストールは簡単。クリックしていけばVMは簡単に設定できる。

■VMware Fusion ゲストOSの設定
MacにVMのインストールが終わったら、exFatでフォーマットした外付けHDDを取り付ける。

次にゲストOSのインストール
通常はXPのインストールはCDになっているため、Macにはドライブが必要になるが、あいにく私はCDドライブは持っていない。
しかし、VMではOSをISO形式から読み込んでインストールする機能があるため、今回はISO形式でインストールすることにする。
※ISOとは仮想CDイメージである

○XPのインストールCDのISO化
CDをISO化するフリーソフトはたくさんある。
http://www.forest.impress.co.jp/article/2008/12/03/freedvdisomaker.html
ドライブがある、ほかのPCでXPのインストールCDをISO化しMac上にコピーする


XPのインストールCDがISO化できたら、MacでVMを起動させる。
ゲストOSの作成方法は、VMの説明書を読めば簡単にできる。

設定中の「ファイルの保存場所」で、外付けHDDを選択すればOK

インストールが終わったら、ゲストOSを起動して正常に動作することを確認する。


■WindowsにVMware Playerのインストール
以下からPlayerをダウンロード・インストールする
http://www.vmware.com/download/player/download.html

インストールはクリックしていけばよい。

インストールが終わったら、VMPlayerを起動する。
右側に「仮想マシンを開く」があるのでクリックし、仮想環境を構築した外付けHDDの中にある仮想環境を選択すれば起動できる。




以上で長がったらしく書いたが「WindowsのVMwareとMacのVMware FusionでゲストOSを共有する」方法について。
なんのことはない、所詮仮想のOSは「ファイル」であり、そのファイルを外付けHDDに格納して持ち歩いているだけなのである。




カレーパン ミニストップの「スパイス香る煮込み牛肉カレーパン」

20121203_064916.jpg

ミニストップで新しいカレーパンを発見(・∀・)

旨い旨い(´∀`)

中がしっかり入っていて、具が大きめ、ほのかなスパイス(´・∀・`)

ミニストップのカレーパンはハズレが少ない感じがする

満足度:★★★★☆

具がしっかりしているのは、普通の100円のカレーパンとの差なのか!?
「煮込み」だけあって、にんじんなんかも美味しい
ただ、牛肉についてはよくわからなかった(´・ω・`)
牛肉はいずこ~

テーマ : パン
ジャンル : グルメ

熱帯魚 ミニブッシープレコ 黒い親からアルビノが産まれる

うちで産まれたミニブッシープレコの稚魚の中に、白い稚魚が数匹いることがわかった。


写真の真ん中にいるやつ。

調べたら、アルビノミニブッシープレコなのかな?

ただ、うちで買っていたミニブッシープレコは全て黒いので、最初見たときは「どこから来たんだ?」と思った。

遺伝子の中にアルビノが入っていたのか?

とにかく無事大きくなってほしい(・∀・)

テーマ : 熱帯魚
ジャンル : ペット

熱帯魚 アマゾニアの立ち上がりについて

2ヶ月ほど前から、ビーシュリンプ水槽のソイルを
「アクアソイル-アマゾニア ノーマルタイプ」
に変更した。



よく聞くのが、「アマゾニアは難しい」ということ。
なんでも、水槽が立ち上がるまでに時間がかかるみたい。

で、実際やってみたのだが
本当に時間がかかる
という結果だった。

立ち上げ最初は、予想通りアンモニアの数値が以上に高い。
生物ろ過が機能するまでというが、これが2ヶ月経ってもまだまだ。

アマゾニアを使用する場合は、パイロットを投入し、3ヶ月は見たほうがいいと思う(´・ω・`)


ちなみに、この水槽は底面ろ過+外部ろ過になっております

左右にある、空気が出ているパイプ(塩ビ管)が底面ろ過のパイプです。
底面ろ過は自作しており、100均に売っているカゴに穴を開けて、そこに塩ビ管をブッ差しただけ。
塩ビ管の中に、上からエアーストーンを放り込んで、底面フィルターの出来上がり(・∀・)

写真からは見えないが、さらに外部フィルターをつないでいる。
この外部フィルターはずっと使っていたものなので、中では生物ろ過が機能しているはず。


それでも、3ヶ月ほどようするのは、アマゾニアが栄養豊富だからなのか。。


アマゾニアはノーマルの9リットルを使用している。
底面ろ過のために、厚めに敷こうと思って、さらにパウダータイプも3リットル購入したのだが、
60cm水槽では、ノーマル9リットルで十分だった。
これだけで、暑さ5cmはある。


ちなみに、これは昨日購入したビーシュリンプ23匹

近所にあるモンスターアクアリウムでは、ビーシュリンプが1匹200円という脅威の安さ。
そこらへんのペットショップだと500円とかするからね(=゚ω゚=)

テーマ : 熱帯魚
ジャンル : ペット

Android Handlerを利用したスレッド処理

Handlerを利用したスレッド処理

 Android上で重い処理や時間のかかる処理を実行していると、画面がフリーズしてしまい操作できない
状態になることがあります。

この場合、新しいThreadを生成し、そのThreadに重い処理を任せるのが普通だと思いますが、
Androidでは一つだけ問題があります。それは新しいThreadからはウィジェット(GUI)の操作ができないということです。

Androidでのウィジェットの操作は、メインThreadからのみアクセス可能なため、別のThreadからアクセスすると
エラーが発生します。
そのため、別のThreadで重い処理の終わりに画面にメッセージを出力するということができません。

 そこで、別のThreadからウィジェットの操作を可能にするのが「Handler」となります。
あらかじめHanlderにCallbackメソッドを設定します。別ThreadからHandlerに対してアクションすることで、
HandlerのCallbackメソッドを呼び出し、そのCallbackメソッド内でウィジェットに対する処理を行うことができます。


■実行結果


■Handlerを利用したウィジェットの操作サンプル
サンプルでは、別のThreadからHandlerのCallbackメソッドを呼び出し、TextViewへメッセージの出力を行います。

・画面レイアウト
 今回は、Threadを起動するButtonオブジェクトと、処理終了後にメッセージを出力するTextViewオブジェクトを配置します
○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"     >
<Button  android:id="@+id/btn"     
android:layout_width="fill_parent"
    android:layout_height="wrap_content"     
android:text="Button"     />
<TextView  android:id="@+id/txt"     
android:layout_width="fill_parent"
    android:layout_height="wrap_content"     />
</LinearLayout>


・Activityクラス
 今回は、別Threadで処理を実行している際に、ユーザにわかりやすいようにProgressダイアログを表示し、処理中であることをユーザに見せます。
ButtonオブジェクトにThreadの起動を行うonClickListenerを設定し、処理終了後にHandlerのCallbackメソッドを呼び出し、TextViewへメッセージを出力しています。
○HandlerTestアクティビティ

package handler.test;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class HandlerTest extends Activity {
ProgressDialog prg = null;
Handler myHandler = null;
TextView tv = null;

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

       //スレッド実行中に表示するプログレスダイアログ
       prg = new ProgressDialog(this);        
prg.setMessage("loading・・・");①

       //クリックイベントの設定
       ((Button) findViewById(R.id.btn)).setOnClickListener(btnclick);        
   //結果を表示するTextViewを取得
       tv = ((TextView) findViewById(R.id.txt));    
}

    //Buttonのクリックイベント
    OnClickListener btnclick = new OnClickListener() {
@Override
public void onClick(View v) {

prg.show();②

//Handlerの生成
myHandler = new Handler(event);③

//スレッド生成・実行
Thread th = new Thread(run);④
th.start();
}    
};

     //別スレッドで実行する処理内容
Runnable run = new Runnable() {
Override
public void run() {
try {
Thread.sleep(1000 * 3); //3秒 ⑤

//HandlerのCallbackを呼び出す
Message msg = new Message();⑥
msg.obj = "3秒たったお";
myHandler.sendMessage(msg);
} catch (Exception ex) {}
}
};

//Handlerから呼び出されるイベント
    Handler.Callback event = new Handler.Callback() {⑦
@Override
public boolean handleMessage(Message msg) {⑧
tv.setText(msg.obj.toString());
prg.dismiss();
return false;
}
};
}

①Thread処理中に表示するProgressダイアログの生成です。
  ここではメッセージのみ設定します。
②Clickイベントで、まずは処理中メッセージを表示します。
③別スレッドから呼び出すためのHandlerオブジェクトを生成します。
 引数には呼び出すCallbackイベントを設定します。
 ここで設定したCallbackイベントのOverrideしたhandleMessageメソッドが、HanlderのsendMessageなどで呼び出されます。
④Threadを生成します。引数には実行するRunnableを指定します。
⑤今回はシンプルに3秒停止のみとします。
⑥別スレッド内からウィジェットに対して操作するために、HanderのsendMessageをCallします。
 sendMessageは引数にMessage()オブジェクトの指定が可能です。
 HandlerのsendMessageが実行されることによって、Handler生成時に指定したCallbackイベントが実行されます。
 実際はCallbackイベントにOverrideしたhandleMessage()メソッドが実行されます。
⑦HandlerのCallbackイベントです。
⑧実際にCallbackから呼び出されるhandleMessage()メソッドをOverrideします。
 引数には、HandlerのsendMessageメソッドの呼び出し時に設定したMessageオブジェクトが格納されます。引数のMessageオブジェクトからのメッセージをTextViewへ表示し、Thread開始時に表示したProgressダイアログを閉じます(dismiss)。

■まとめ
 なんにでも当てはまるがユーザビリティを向上させるには、マルチスレッド化が重要。
Handlerを有効活用することで、ユーザに処理を意識させずに重い処理を行うことが可能となる。
Threadによる処理、Handlerによるウィジェットの操作を的確に使いこなせるようになることが大切。

■おまけ
 HandlerのCallbackとRunnableをクラスで実装したパターン

package handler.test;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Handler.Callback;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class HandlerTest2  extends Activity implements Callback, Runnable {
ProgressDialog prg = null;
Handler myHandler = null;
Thread myThread = null;
TextView tv = null;

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

         //スレッド実行中に表示するプログレスダイアログ
        prg = new ProgressDialog(this);        
prg.setMessage("loading・・・");

        //クリックイベントの設定
        ((Button) findViewById(R.id.btn)).setOnClickListener(btnclick);        
//結果を表示するTextViewを取得
        tv = ((TextView) findViewById(R.id.txt));

//Handlerの生成
myHandler = new Handler(this);
//スレッド生成・実行
myThread = new Thread(this);    
}     //Buttonのクリックイベント
OnClickListener btnclick = new OnClickListener() {
@Override
public void onClick(View v) {
prg.show();
myThread.start();
}    
};
@Override
public void run() {
try {
Thread.sleep(3000);

//HandlerのCallbackを呼び出す
Message msg = new Message();
msg.obj = "3秒たったお";
myHandler.sendMessage(msg);
} catch (Exception ex) {}
}

@Override
public boolean handleMessage(Message msg) {
tv.setText(msg.obj.toString());
prg.dismiss();
return false;
}

}

テーマ : android
ジャンル : コンピュータ

カレーパン ファミリーマートのとろけるチーズ カレードナツ

ファミリーマートでカレーパンを発見したのでメモ。

20121128_070942.jpg

「とろけるチーズ カレードナツ」
なんともおいしそうじゃないか。

パンは、あまり脂っこくなく食べやすい感じ。
今のカレーパンは全体的に脂っこくないのかな。

中身に関してだが、中身が少なすぎる(´・ω・`)

カレー自体は美味しいのだが、パンと中身の比率が悪いため、カレー味のするパンを食べている感じ。
もちろん、間違いなく中身は入っている。
しかし、パン生地が厚いためなのか、こりゃ水分が欲しくなる。ちょっと喉に詰まるかんじ(´・ω・`)

満足度:★★☆☆☆
味は問題ない。ただ、パンと中身の比率が悪いため、喉につまる感じがしてなんともいえない(;´Д`)

テーマ : パン
ジャンル : グルメ

おすすめアプリ
カテゴリ
最新記事
リンク
アクセスカウンター
アクセス解析
imobile
i-mobile
i-mobile
i-mobile
i-mobile
i-mobile
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR
Amazon
Androidお勧め参考書
EC studio
商品
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。