Androidのアプリ開発関連のブログ

Android-x86を利用する

Android-x86は、x86 PC上で動作するオープンソースのAndroid OSです。
http://www.android-x86.org/
上記が公式サイトのURLで無料でダウンロードして利用することができます。
インストーラーISOが用意されており、初期状態でGoogle Playも入っています。
x86 PC上で動作しますので、Virtualboxなどのx86 PCエミュレーター上にインストールすることもできます。
インストールの際に実機と同じでGoogleアカウントを作成できますので、アプリ内購入のテストアカウントの作成にもおすすめです。

x86なので、ハードウェア仮想化支援が利用できればエミュレーター上でも快適な速度で動作します。
残念な点としては、Virtualboxで利用する場合は「マウス統合」機能が利用できません。
また、OpenGL ESが実装されているようなのですが、手元ではOpenGLを利用したゲームなどはエラーが出て実行できません。


インストールはインストーラーが用意されているので簡単ですが、
インストールするデバイスの選択の箇所でデバイスに対して手動でパーティションを作成してからインストールしないとGRUBインストールの箇所でエラー表示もなしに止まってしまうので注意です。
インストール後はAndroidの初期設定となりますが、Wifi設定を飛ばそうとすると注意が出ますが、有線LANがあるならWifiはSKIPして問題ありません。


Virtualbox上にインストールしている場合のADBとの連携は、ADBのゲスト側はポート5555になりますので、
Virtualboxのポートフォワーディングで母艦のポート1つをゲストのポート5555に割り当てるか、ホストオンリーのネットワークアダプタを追加するなどし、
母艦上で、
$ adb connect ip:port
とゲストのポート5555に対応したアドレスを指定してconnectをするとdevicesで確認できるようになります。


logcatでのログ表示の際にサウンド・デバイスのエラーが大量に表示されてしまう場合は、
デバイス側のダイヤル音やタッチ音等の項目を全てOFFにすることで、たぶん出なくなります。

アプリ内購入の実装でハマってる

アプリ内購入の実装でハマってる。

販売アカウントの作成をしてアイテムを作ってテストしようとしたのだが、
出版社はこのアイテムを購入できません
とエラーが出てテストが出来ない。
どうも、開発に使用してるアカウントだと購入は出来ないらしい。
ベータテストなら実際には決済は生じないらしいのだが、それすらさせてくれない。
これ厳しすぎだろ・・・

というわけで、Android-x86をVirtualbox上に用意してインストールの際にアカウントを作ってテスターに登録したのだが、
サーバーからの情報の取得中にエラーが発生しました
で、ダメ・・・

大体はできてると思うんで、テストしないでアップしちゃおうか・・・


AdMob用の google-play-services_lib を更新したらGenymotionでエラー落ちするようになったのはAndroid-x86でも生じたのだが、
google-play-services_libを使うのに Support Library v4 ってのが必要になってるらしく、android-support-v4.jar を google-play-services_lib/libs に入れてビルドしたらGenymotionでもAndroid-x86でもAdMob表示できた。

Android 4.04の実機でAdMobがエラー落ちするのは、ログ見るとなんかのコーデック関連のバイナリがエラー吐いてるような。
中華タブのバグな気がする。
前はエラーにならなかったはずなんだが・・・

アプリ内購入の実装

[テスト用アカウントの作成]
いきなりですが、アプリ内購入の実装においてはプログラム以上にアカウントの準備が難題となります。
開発用のアカウントと別に個人用のGoogleアカウントを所有している場合などは問題になりませんが、
開発用アカウントでは 「出版社はこのアイテムを購入できません」と言われて購入テストが行えません。
開発用アカウントとは別にテスト用にGoogleアカウントを用意し、カード登録までする必要があります。
端末は実機を用意するのは難しいと思いますが、Android-x86 を使うのが良いと思います。
カード登録は必要ですが、Developer Consoleのアカウント設定からテスト用に権限を与えたアカウントでは決済ダイアログにテストと表示され決済は行われません。

[Google Play Billing Libraryをインストール]
Android SDKからGoogle Play Billing Libraryをインストール。
環境によるが、/opt/android-sdk/extras/google/play_billing とかにインストールされている。
アプリ内購入を導入するプロジェクトに /src/com/android/vending/billing ディレクトリを作成して IInAppBillingService.aidl をコピー。
別のプロジェクトでも共用できるようにlibプロジェクトを作成してそこに置くのが良いと思います。

[権限を追加]
AndroidManifest.xmlに以下の権限を追加。
<uses-permission android:name="com.android.vending.BILLING" />

[ビルドしてアップロード]
アイテムの作成は Google Play Developer Console から行えますが、
"com.android.vending.BILLING" の権限を追加したapkが配信されていないとアイテムの作成が行えません。
製品版ではなく、ベータ版かアルファ版での配信で問題ありません。

[Google ペイメント販売アカウントの作成]
アイテムの作成には「Google ペイメント販売アカウント」が必要なので、「Google Play Developer Console > アプリ内アイテム」から作成する。
住所,電話番号,ウェブサイトなどの情報の入力が必要。

[アイテムの作成]
Google Play Developer Console > アプリ内アイテム
からアイテムの作成ができます。
[管理対象のアイテム][定期購入]の2種類がありますが、通常のアイテムは[管理対象のアイテム]です。
アイテムIDや説明、価格などを設定し、[有効]にすると有効になります。
アイテムは1度購入すると2回目の購入はできませんが、購入済みアイテムを消費させることが可能です。(消費すると再購入が可能)

[import]
import com.android.vending.billing.IInAppBillingService;
import android.content.ServiceConnection;
import android.content.ComponentName;
import android.os.IBinder;
import android.app.PendingIntent;
上記5つが必要そう。1個目のは IInAppBillingService.aidl で導入できる。
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import java.util.ArrayList;
import java.lang.Thread;
import android.os.Handler;
import java.lang.Runnable;
import org.json.JSONObject;
↑この辺りも使うと思います。

[変数定義]
IInAppBillingServiceとServiceConnection
onCreate外でも参照できるようにclass変数として上記2つを定義する必要がある。
    IInAppBillingService mService;
    ServiceConnection mServiceConn=new ServiceConnection(){
        public void onServiceDisconnected(ComponentName name){
            mService=null;
        }
        public void onServiceConnected(ComponentName name,IBinder service){
            mService=IInAppBillingService.Stub.asInterface(service);
        }
    };
サンプルコードだとこんな感じで、定義してる。
ServiceConnectionの方は接続時と切断時に行いたい処理を書ける。

[onDestroy()]
        if(mService!=null){
            unbindService(mServiceConn);
        }
こんな感じで、接続中ならunbindServiceしないとダメらしい。
サンプルではonDestroy()に記述するようになっている。

[onCreate()]
        Intent serviceIntent=new Intent("com.android.vending.billing.InAppBillingService.BIND");
        serviceIntent.setPackage("com.android.vending");
        bindService(serviceIntent,mServiceConn,Context.BIND_AUTO_CREATE);
接続はこんな感じ。
サンプルではonCreate()でやってる。

[購入済みアイテムの問い合わせ]
mService=IInAppBillingService.Stub.asInterface(service);
try{
   Bundle res=mService.getPurchases(3,getPackageName(),"inapp",null);
}catch(Exception e){
   return;
}
getPurchases() で取得できるBundleに入っている key:"INAPP_PURCHASE_ITEM_LIST" が購入済みアイテムのアイテムIDが入った ArrayList<String> となっています。
"INAPP_PURCHASE_ITEM_LIST" 以外に、"RESPONSE_CODE","INAPP_PURCHASE_DATA_LIST","INAPP_DATA_SIGNATURE_LIST","INAPP_CONTINUATION_TOKEN"がBundle内に入っています。
自前サーバーで厳密に管理する場合以外は "INAPP_PURCHASE_ITEM_LIST" のみをチェックすれば良い気がします。
getPurchases()は通信が発生すると思いますのでUIスレッドと別のスレッドでリクエストする必要があります。
第1引数の3はIn-app Billingのバージョン。
第2引数はパッケージ名。
第3引数は購入型アイテムの場合は"inapp"。
第4引数は不明。

[有効なアイテム一覧の取得]
mService=IInAppBillingService.Stub.asInterface(service);
Bundle req=new Bundle();
ArrayList<String> list=new ArrayList<String>();
list.add("item_id");
req.putStringArrayList("ITEM_ID_LIST",list);
try{
   Bundle res=mService.getSkuDetails(3,getPackageName(),"inapp",req);
}catch(Exception e){}
getSkuDetails() で有効なアイテム一覧が取得できます。
こちらも通信が発生すると思われるのでスレッドで。
引数は getPurchases() と似た感じですが、第4引数に検索対象のアイテムIDの入った ArrayList<String> をkey:"ITEM_ID_LIST" で格納したBundleを渡します。
レスポンスはBundleですが、Bundle内にはリクエストの key:"ITEM_ID_LIST" に対応した key:"DETAILS_LIST" の ArrayList<String>  が入っており、中身のStringはJSON形式です。
JSONの内容はDeveloper Consoleで設定したアイテム情報ですが、 "price" でユーザーの地域に合わせた通過表記の価格が取得できます。

[購入リクエスト]
try{
   Bundle req=mService.getBuyIntent(3,getPackageName(),"hide_ad","inapp","item_id");
   startIntentSenderForResult(((PendingIntent)req.getParcelable("BUY_INTENT")).getIntentSender(),1001,new   Intent(),Integer.valueOf(0),Integer.valueOf(0),Integer.valueOf(0));
}catch(Exception e){}
購入リクエストはIntentを投げて行います。
ダイアログが出てくるだけなのでActivityは閉じませんが、onActivityResult() で結果が帰ってきます。
onActivityResult() で取得できるIntentに情報が入っていますが、自前サーバーで管理しないならrequestCodeとresultCodeのみ確認すれば良い気がします。

チェックリストのバグとか・・・

昨日配信開始して今朝記事に書いたチェックリストに実機でデータの整合が取れてないバグがあったが、
onDestroy()時に保存処理を書いてたのだが、onDestroy()は端末回転時は呼ばれるがメニューからアプリを終了した場合はonDestroy()なしでonPause()の後に死ぬぽい。
そのため、メニューから終了した場合は保存されずに、回転した時だけ保存されてたぽい。
Genymotionでは操作しにくいから回転のテストしかしてなかった。

元々onSaveInstanceState()とonRestoreInstanceState()で状態の復元をしてたが、
終了時もonSaveInstanceState()は呼ばれるようなのだが、いずれにしろストレージ(SQLite)に保存しなければいけないので、
どうせ保存するならonCreate()の際に毎回DB読み込みしちゃえばいいと思ったので、
onPause()で保存、onCreate()で常に読み込み。とした。

あと、EditTextの背景色が#FFFFFFではないことに気づいたので背景色を統一した。
配信開始したばっかなのに1日でバージョンコード5になったw


Android 4.04でAdMobがクラッシュするのは原因不明。
他のアプリもダメ。
AdMobとAnalyticsを統合したことでダメになったような・・・

今朝のAdMob最新版への更新で解決しなかったからGenymotionでテストしようとしたのだが、
前のバージョンではテストできてたはずなのだが、最新版にしたら、
java.lang.NoClassDefFoundError: Failed resolution of: Landroid/support/v4/util/SimpleArrayMap;
とか言われて起動しなくなった。
これは、android.support.v4.util.SimpleArrayMapの機能らしく、
Android1.6に不足機能を追加するための追加APIぽいのだが、それが無いと言われる。
そもそもGenymotionはAndroid 5.0で使ってるので不要なものだと思うのだが・・・
実機では問題無さそうなので、放置して今日は実機でテストした。


まあ、これでとりあえずチェックリストは終わりと思う。
他にはちょっと難しいと思うのだが、HTMLエディターとSSHコンソールを作ろうか検討している。

チェックリスト、とりあえず完成したんだが・・・

作成してたチェックリストは、とりあえず完成して配信開始した。



のだが、Genymotionだけで確認しながら作り終えて、配信してから実機で確認したら何かおかしい・・・
ListViewに追加したら一番下の行が消えたり、回転すると消えたりとか・・・
で、別の実機で試そうと4.04のタブに入れてみたが、過去に配信済みのアプリも含めて起動直後に落ちる現象が・・・
それはAdMobが原因ぽいんだが解決法不明。とりあえずAdMobを最新版に変えてビルドしなおしてみた。

最新版がダウンロードできるようになってから確認する。


アプリの方は横向き時はサイドバーつけようかとも思ったが、大したアプリじゃないしとりあえず共通レイアウトでいいや。
ってことにした。