2012年12月31日月曜日

In-app Billing Version3 翻訳(In-app Billing Reference編)

EGGの曽川です。

リファレンスの翻訳です。
原文はこちらです。
まだ幾つかドキュメントはありますが、In-app Billing Version3に関するドキュメントは一旦終了します。

アプリ内課金リファレンス
このドキュメントはアプリ内課金バージョン3を使った技術的なリファレンスです。

サーバのレスポンスコード

次の表はGooglePlayからアプリに対して送られる、サーバのレスポンスコードの全てです。
GooglePlayは同期的にレスポンスコードを送ります。(レスポンスBundleRESPONSE_CODEキーにマッピングされているint型の値)
アプリは全てのレスポンスコードを処理しなければなりません。

表1.アプリ内課金バージョン3のAPI呼び出しのレスポンスコード一覧
レスポンスコード 説明
BILLING_RESPONSE_RESULT_OK 0 成功
BILLING_RESPONSE_RESULT_USER_CANCELED 1 ユーザがバックキーを押した、またはダイアログをキャンセルした
BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE 3 課金APIのバージョンがリクエストしたタイプをサポートしていない
BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE 4 リクエストされたサービスは購入できない状態にある
BILLING_RESPONSE_RESULT_DEVELOPER_ERROR 5 APIに与えられた引数が無効。このエラーは署名が正しくない、アプリ内課金の設定がGooglePlayで行われていない、AndroidManifestに必要な権限がない。
BILLING_RESPONSE_RESULT_ERROR 6 APIの動作中に致命的なエラーが起こった。
BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED 7 アイテムが既に購入されているので失敗した。
BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED 8 アイテムを所有していないために消費に失敗した。


APIリファレンス

アプリ内課金バージョン3のAPIは、バージョン3のサンプルアプリケーションに含まれるInAppBillingService.aidlファイルに定義されています。

getSkuDetails()メソッド
このメソッドはサービスIDに紐づくサービス詳細リストを返します。
GooglePlayから送られたレスポンスBundleは、クエリの結果がDETAILS_LISTキーをマップとするArrayListに格納されています。
詳細リストのそれぞれのString内には、1つのサービス(JSON形式)のサービス詳細が含まれます。
サービス詳細のJSON文字列のフィールドを、表2にまとめています。

表2.getSkuDetailsリクエストから返されるサービスアイテム詳細のJSONフィールドの説明
キー 説明
productId 商品のサービスID
type アプリ内の購入タイプで、値は必ず「inapp」
price 通貨記号を含むアイテムの価格のフォーマット。価格には消費税は含まれていない。
title サービスのタイトル
description サービスの説明

getBuyIntent()メソッド
このメソッドは、RESPONSE_CODEキーに紐づくint型のレスポンスコードと
BUY_INTENTキーに紐づいたアプリ内アイテムの購入フローを起動するPendingIntentを返します。
PendingIntentを受け取ると、GooglePlayは注文データでレスポンスIntentを送ります。
レスポンスIntentから返されるデータを表3にまとめています。

表3.アプリ内課金のバージョン3の購入リクエストからのレスポンスデータ
キー 説明
RESPONSE_CODE 購入が完了した場合は0それ以外はエラー
INAPP_PURCHASE_DATA 注文詳細を含むJSON形式の文字列。(表4のJSONフィールドの説明を参照)
INAPP_DATA_SIGNATURE 開発者の秘密鍵で署名された購入データの署名を含む文字列

表4は注文のレスポンスデータを返すJSONフィールドを説明しています。

表4.INAPP_PURCHASE_DATAのJSONフィールドの説明
フィールド 説明
orderId トランザクションのためのユニークな注文ID。Google Walletの注文IDに対応します。
packageName 購入の起点となったアプリのパッケージ
productId アイテムのサービスID。全てのアイテムはサービスIDを持っており、GooglePlayパブリッシャーサイト上のアプリのサービスリストで指定しなければいけません。
purchaseTime 購入された時間。エポック時間からのミリ秒(1970/01/01).
purchaseState 注文の購入状態。可能な値は0 (購入)、1 (キャンセル)、2 (払い戻し).
developerPayload 注文に関する補足情報を含む開発者が指定した文字列。getBuyIntentリクエストを行う際にこのフィールドの値を指定する事ができます。
purchaseToken 指定されたアイテムとユーザのペアのための購入を一意に識別するトークン

getPurchases()メソッド
このメソッドは、現在ユーザが保有する未消費のサービスを返します。
表5はBundleとして返されるのレスポンスデータのリストです。

表5.getPurchasesリクエストのレスポンスデータ

キー 説明
RESPONSE_CODE 0の場合は成功。それ以外はエラー。
INAPP_PURCHASE_ITEM_LIST StringArrayList:アプリからの購入サービスIDを含むリスト。
INAPP_PURCHASE_DATA_LIST StringArrayList:アプリからの購入詳細を含むリスト。(リスト内の各INAPP_PURCHASE_DATA項目に保存された詳細データのリストは表4を参照してください。)
INAPP_DATA_SIGNATURE_LIST StringArrayList:アプリからの購入の署名を含むリスト。
INAPP_CONTINUATION_TOKEN ユーザが保有するアプリ内サービスの次のセットを検索するための継続トークン含む文字列。
これはユーザが保有するサービスが非常に多い場合のみ、GooglePlayサービスがセットします。
継続トークンがレスポンスに存在している場合は、getPurchasesを別に呼び出し、受信した継続トークンを渡さなければいけません。
後のgetPurchasesの呼び出しは、さらなる購入やおそらく別のトークンを返します。

2012年12月30日日曜日

In-App Billing Version 3 翻訳(Implementing In-app Billing編)

EGGの曽川です。

アプリ内課金の実装方法について翻訳しました。
原文はこちらです。
誤訳等あればご指摘ください。
ドキュメントは文字のみなのでイメージを掴むたい方は、In-app Billing Version3 (サンプルを使う編)もご覧ください。

アプリ内課金の実装
GooglePlay上のアプリ内課金は、GooglePlayを使用したアプリ内課金のリクエストの送信や、アプリ内課金のトランザクション管理のための簡単でシンプルなインタフェースを提供します。
以下の情報はバージョン3のAPIを使用して、アプリからアプリ内課金サービスをどのように呼び出すかの基本をカバーしています。

注意:完成した実装を見て、アプリのテストする方法を学ぶためにSelling In-app Productsトレーニングクラスを見てください。
トレーニングクラスは完成しているアプリ内課金のサンプルアプリを提供します。
これは、重要なタスクの処理、接続のセットアップ、課金リクエストの送信、GooglePlayからのレスポンスの処理、メインアクティビティからアプリ内課金をコールするためのバックグラウンドスレッドの管理を含みます。

作業を開始する前に、アプリ内課金を実装する事が容易になる概念を理解するために、In-app Billing Overviewを読んでください。
アプリ内で課金を実行するために次の手順を実行してください。
  1. Billingライブラリプロジェクトを追加する。
  2. AndroidManifest.xmlの更新
  3. ServiceConnectionを作成し、IInAppBillingServiceをバインドします。
  4. アプリからIInAppBillingServiceにアプリ内課金のリクエストを送信します。
  5. GooglePlayからのアプリ内課金のレスポンスを処理します。

AIDLファイルをプロジェクトへ追加

サンプルアプリTrivialDriveは、GooglePlayのアプリ内課金サービスのインタフェースに定義されている
Android Definititon Language(AIDL)ファイルを含んでいます。
このファイルを追加すると、Androidのビルド環境はインタフェースファイル(IIAppBillingService.java)を作ります。
課金リクエストのIPCメソッド呼び出しを行うためにこのインタフェースを使います。
プロジェクトにアプリ内課金バージョン3のライブラリを追加するには:
  1. IInAppBillingService.aidlファイルをAndroidプロジェクトにコピーします。
    • もしEclipseを使用している場合は、IInAppBillingService.aidl/srcディレクトリにインポートします。
      Eclipseがプロジェクトをビルドした際に、インタフェースファイルを自動的に生成します。
    • もしEclipseプロジェクトを使用しない環境の場合は、
      /src/com/android/vending/billingディレクトリを作成し、IInAppBillingService.aidlをコピーします。
      AIDLファイルをプロジェクトに置き、IIAppBillingService.javaを生成するためにAntツールを使用します。
  2. アプリをビルドします。
    プロジェクトの/genディレクトリにIInAppBillingService.javaという名前で生成されたファイルがあるはずです。

AndroidManifestの更新

アプリ内課金は、アプリとGooglePlayサーバ間の全ての通信を処理するGooglePlayアプリに依存しています。
GooglePlayアプリを使用するには、適切なアクセス許可を要求しなければなりません。
AndroidManifest.xmlファイルにcom.android.vending.BILLINGパーミッションを追加する事でこれをおこなうことができます。
もし、アプリがアプリ課金のパーミッションを宣言せずに課金リクエストを試みる場合は、GooglePlayは要求を拒否しエラーを返します。

アプリに必要な権限を付与するには、AndroidManifest.xmlファイルに次の行を追加します。
<uses-permission android:name="com.android.vending.BILLING">


Service Connectionの作成

アプリは、GooglePlayとのメッセージのやり取りを容易にするServiceConnectionを持っていなければなりません。
少なくとも次のことを行ってください。

  • IInAppBillingServiceとのバインド
  • GooglePlayアプリへの課金リクエスト送信(IPCメソッド呼び出しなど)
  • 各課金リクエストで返される同期的なレスポンスメッセージの処理

IInAppBillingServiceのバインド
GooglePlayとアプリ内課金サービスの接続を確立するには、IInAppBillingServiceにActivityをバインドするServiceConnectionを実装します。
接続が確立された後、IInAppBillingServiceインスタンスの参照を取得する、onServiceDisconnectedonServiceConnectedメソッドをオーバライドします。
IInAppBillingService mService;

ServiceConnection mServiceConn = new ServiceConnection() {
   @Override
   public void onServiceDisconnected(ComponentName name) {
       mService = null;
   }

   @Override
   public void onServiceConnected(ComponentName name, 
      IBinder service) {
       mService = IInAppBillingService.Stub.asInterface(service);
   }
};

ActivityのonCreateメソッドで、bindServiceメソッドを呼び出すことでバインドを実行します。
アプリ内課金サービスの参照と、作成したServiceConnectionインスタンスをIntentメソッドとして渡します。
@Override
public void onCreate(Bundle savedInstanceState) {    
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);        
   bindService(new 
      Intent("com.android.vending.billing.InAppBillingService.BIND"),
          mServiceConn, Context.BIND_AUTO_CREATE);
これでGooglePlayサービスとのやり取りのためにmServiceの参照を使用できます。

重要Activityが終了したらアプリ内課金サービスからアンバインドすることを忘れないでください。
もし、アンバインドをしなければ、開いているサービス接続がデバイスのパフォーマンスを低下させます。
この例では、ActivityのonDestroyメソッドをオーバライドすることにより、アプリ内課金サービス接続のmServiceConnをどのようにアンバインドを実行するか示しています。
@Override
public void onDestroy() {
   if (mServiceConn != null) {
      unbindService(mServiceConn);
   } 
}
IInAppBillingServiceとバインドするサービス接続の完全な実装については、トレーニングクラスのSelling In-app Productsを参照してください。


アプリ内課金のリクエストを作成する

一度アプリがGooglePlayに接続されたら、アプリ内サービスの購入リクエストを開始できます。
GooglePlayはユーザが支払方法を入力するチェックアウトインタフェースを提供するので、アプリは直接支払いのトランザクションを操作する必要はありません。
アイテムが購入されると、GooglePlayはユーザがアイテムの所有権を持っていることと、ユーザが同じサービスIDのアイテムを消費するまで、アイテムの購入が発生しないようにすること判別します。
アプリ内でアイテムがどのように消費されるか制御することができ、GooglePlayに再度購入ができるように通知します。
またユーザに関する購入リストをすぐに取得するために、GooglePlayに問い合わせることができます。
これは例えば、ユーザがアプリを起動した時にユーザの課金を復元したい時に非常に便利です。

購入可能なアイテムの問い合わせ
あなたのアプリ内で、アプリ内課金バージョン3を使ってGooglePlayからアイテムの詳細の問い合わせができます。
アプリ内課金サービスにリクエストを渡すために、まず"ITEM_ID_LIST"のキーでサービスIDを持つ、StringのArrayListを含むBundleを生成します。(それぞれの文字列は購入可能なアイテムのサービスIDです。)
ArrayList skuList = new ArrayList();
skuList.add("premiumUpgrade");
skuList.add("gas");
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(“ITEM_ID_LIST”, skuList);
この情報をGooglePlayから取得するために、アプリ内課金バージョン3のgetSkuDetailsメソッドを呼び出すために、アプリ内課金バージョン("3")、アプリのパッケージ名、購入タイプ("inapp")と作成したBundleを渡します。
Bundle skuDetails = mService.getSkuDetails(3, getPackageName(), “inapp”, querySkus);
リクエストが成功した場合、返されたBundleは、BILLING_RESPONSE_RESULT_OK (0)のレスポンスコードを持ちます。

警告:メインスレッド上でgetSkuDetailsを呼んではいけません。このメソッドを呼び出すと、メインスレッドをロックするネットワークリクエストを行います。
代わりに別のスレッドを作成し、そのスレッド内からgetSkuDetailsメソッドを呼び出してください。

GooglePlayからのすべてのレスポンスコード見るには、In-app Billing Referenceを参照してください。

クエリの結果はDETAILS_LISTキーのStringのArrayListに格納されます。
購入情報はJSON形式の文字列に格納されます。
返されたサービス詳細情報の種類を確認するには、In-app Billing Referenceを参照してください。
この例では、先程のコードスニペットから返されたskuDetailsのBundleからアプリ内アイテムの価格を取得しています。
int response = skuDetails.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList responseList 
      = skuDetails.getStringArrayList("DETAILS_LIST");
   
   for (String thisResponse : responseList) {
      JSONObject object = new JSONObject(thisResponse);
      String sku = object.getString("productId");
      String price = object.getString("price");
      if (sku.equals(“premiumUpgrade”)) mPremiumUpgradePrice = price;
      else if (sku.equals(“gas”)) mGasPrice = price;
   }
}

アイテムの購入
アプリから課金リクエストを開始するには、アプリ内課金サービスのgetBuyIntentメソッドを呼び出します。
アプリ内課金APIバージョン("3")、アプリパッケージ名、アイテムを購入するためのサービスID、購入タイプ(inapp)、developerPayload文字列をメソッドに渡します。
developerPayload文字列は、購入情報と一緒にGooglePlayに返送して欲しい、追加の引数を指定するために使用されます。
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
リクエストが成功した場合、返されたBundleBILLING_RESPONSE_RESULT_OK (0)のレスポンスコードと、購入フローを開始するために使用出来るPendingIntentを持っています。
GooglePlayからのすべてのレスポンスコードを見るには、In-app Billing Referenceを参照してください。
次に、返されたBundleBUY_INTENTキーを使用して、PendingIntentを取得します。
PendingIntent pendingIntent = buyIntentBundle.getParcelable(“BUY_INTENT”);
購入トランザクションを完了するために、startIntentSenderForResultメソッドを呼び出し、作成したPendingIntentを使用します。
この例では、リクエストコードとしての1001という任意の値を使用しています。
startIntentSenderForResult(pendingIntent.getIntentSender(),1001, new Intent(), Integer.valueOf(0),Integer.valueOf(0),Integer.valueOf(0));
GooglePlayは、PendingIntentからアプリのonActivityResultメソッドにレスポンスを送信します。
onActivityResultメソッドはActivity.RESULT_OK (1)またはActivity.RESULT_CANCELED (0)の結果コードを持ちます。
レスポンスIntentに返された注文情報の種類を見るには、In-app Billing Referenceを参照してください。

注文の購入データは、レスポンスIntentINAPP_PURCHASE_DATAキーにマップされているJSON形式の文字列です。
例えば以下のとおりです。
'{ 
"orderId":"12999763169054705758.1371079406387615", 
"packageName":"com.example.app",
"productId":"exampleSku",
"purchaseTime":1345678900000,
"purchaseState":0,
"developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
“purchaseToken”:“rojeslcdyyiapnqcynkjyyjh”
}'
前の例の続きは、レスポンスIntentからレスポンスコード、購入データ、署名を取得します。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
   if (requestCode == 1001) {     
      int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
      String purchaseData = data.getStringExtra(“INAPP_PURCHASE_DATA”);
      String dataSignature = data.getStringExtra(“INAPP_DATA_SIGNATURE”);
        
      if (resultCode == RESULT_OK) {
         try {
            JSONObject jo = new JSONObject(purchaseData);
            String sku = jo.getString("productId");
            alert("You have bought the " + sku + ". Excellent choice, 
               adventurer!");
          }
          catch (JSONException e) {
             alert("Failed to parse purchase data.");
             e.printStackTrace();
          }
      }
   }
}
セキュリティの推奨事項:購入リクエストを送信すると、購入リクエストを一意に識別する文字列トークンを作成し、developerPayloadにこのトークンを含めます。
トークンとしてランダムに生成された文字列を使用することができます。
GooglePlayから購入レスポンスを受け取った時、返されたデータ署名、orderIddeveloperPayloadを確認してください。
セキュリティを強化するために、自身のセキュアなサーバ上でチェックを実行してください。
orderIdが以前に処理されていないユニークな値であり、developerPayload文字列が購入リクエスト以前送信したトークンと一致することを必ず確認してください。

購入アイテムの問い合わせ
アプリからユーザが行った購入に関する情報を取得するためには、アプリ内課金APIバージョン3サービスのgetPurchasesを呼び出します。
アプリ内課金APIバージョン("3")、アプリのパッケージ名、購入タイプ("inapp")をメソッドに渡します。
Bundle ownedItems = mService.getPurchases(3, getPackageName(), “inapp”, null);
GooglePlayサービスは、現在端末にログインしているユーザアカウントの購入のみ返します。
リクエストが成功した場合、返されたBundleはレスポンスコードとして0を持っています。
返されたBundleはサービスID、それぞれの購入の注文詳細リスト、それぞれの購入の署名が含まれています。

パフォーマンスを改善するために、getPurchasesが最初に呼び出された時、アプリ内課金サービスはユーザが所有している700の商品までしか返しません。
ユーザが多数の商品を所有している場合、GooglePlayは更に多くの商品を検索できることを示すために、返されたBundle内で文字列トークンにマップされたINAPP_CONTINUATION_TOKENキーを含んでいます。
アプリはそれ以降のgetPurchasesの呼び出しを行うために、引数としてこのトークンを渡します。
GooglePlayは、ユーザが所有するすべての商品がアプリに送られるまで、返されたBundle内に継続トークンを返し続けます。

getPurchasesによって返されるデータの詳細については、 In-app Billing Referenceを参照してください。
次の例はレスポンスからデータを取得する方法を示します。
int response = ownedItems.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList ownedSkus = 
      ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
   ArrayList purchaseDataList = 
      ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
   ArrayList signatureList = 
      ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE");
   String continuationToken = 
      ownedItems.getString("INAPP_CONTINUATION_TOKEN");
   
   for (int i = 0; i < purchaseDataList.size(); ++i) {
      String purchaseData = purchaseDataList.get(i);
      String signature = signatureList.get(i);
      String sku = ownedSkus.get(i);
  
      // 購入情報を使って何かします。
      // 例えば、ユーザが所有している商品の最新のリストの表示
   } 

   // continuationToken != nullの場合、getPurchasesを再度呼んで 
   // 更にアイテムを検索するためにトークンを渡します。
}

購入の消費
GooglePlayで購入したアイテムの所有権を追跡するために、アプリ内課金バージョン3のAPIを使用することができます。
アイテムが購入されると、「所有」であるとみなされ、GooglePlayから購入することはできません。
GooglePlayで再度購入可能な状態にする前に、消費リクエストを送らなければいけません。
すべての管理対象の商品は消耗可能です。
消費のメカニズムをどのように使用するかはあなた次第です。
一般的にユーザが複数回購入したいと思うであろう、一時的な利点を持つ商品のために消費を実装します。(例:ゲーム内通貨や装備)
一般的に、一度の購入で恒久的な効果を提供する商品のために消費は実装しません。(例:プレミアムアップグレード)

購入の消費を記録するためには、アプリ内課金サービスにcomsumePurchaseメソッドをを送り、削除するための購入識別の値であるpurchaseToken文字列を渡します。
purchaseTokenは、購入リクエストの成功後にGooglePlayサービスによってINAPP_PURCHASE_DATA文字列で返されたデータの一部です。
この例では、token変数にpurchaseTokenで識別された商品の消費を記録しています。
int response = mService.consumePurchase(3, getPackageName(), token);
警告:メインスレッドでconsumePurchaseメソッドを呼び出してはいけません。
のメソッドを呼び出すと、メインスレッドをロックするネットワークリクエストを行います。
代わりに別のスレッドを作成し、そのスレッド内からconsumePurchaseメソッドを呼び出してください。

ユーザに提供されたアプリ内商品のどのように制御と追跡するかはあなたの責任です。
例えば、ユーザがゲーム内通貨を購入した場合、プレイヤーの購入した通貨の量のインベントリを更新する必要があります。

セキュリティの推奨事項:ユーザへ消費可能なアプリ内購入の利益を提供する前に、消費リクエストを送らなければいけません。
アイテムの提供前にGooglePlayから消費成功レスポンスの受信を確認してください。

Except as noted, this content is licensed under Creative Commons Attribution 2.5.

2012年12月18日火曜日

In-app Billing Version3 (サンプルを使う編)

チームEGGの曽川です。

実装の翻訳に入る前に実際にどういう動きをするのか、サンプルを使って動かしてみたいと思います。
基本的には、以前のアプリ内課金の作業と似ているのでこちらも参考にしてください。
アプリのアップロード完了待ちの時間もあるので、確認までに2~3時間程度必要になる可能性があります。

GooglePlayでの作業
  1. 新デザインに切り替える
  2. 新しいアプリを追加
  3. サービスとAPIの欄にある公開鍵をメモる
  4. 設定でテスト用アカウントを追加
  5. 「設定」→「アカウントの詳細」→「テスト用のアクセス権がある Gmail アカウント」にテストで使う端末で登録しているGoogleアカウントを入力 ※デベロッパーコンソールの管理アカウント以外じゃないとダメ


アプリ側の作業
  1. SDKManagerを使用してGooglePlayBillingLibraryをダウンロード
  2. サンプルをEclipseにインポート
  3. {android-sdk}/google/play_billing/in-app-billing-v03/samples 上記ディレクトリの中の「TrivialDrive」
  4. パッケージの変更
  5. AndroidManifest.xmlの修正。パッケージとActivityの記述を修正する。 ※com.exampleで始まってはダメです。
  6. MainActivity.java修正
  7. MainActivity.javaのbase64EncodedPublicKeyを、先ほどメモした公開鍵を貼り付けて置き換える。
  8. 実行する
  9. この時点ではまだ「アイテムは見つかりませんでした。」と表示されます。
  10. アップロード用のkeystoreを使ってアプリを署名する
  11. デバッグ署名ではダメ。keystoreの作り方はこちら


もう一度GooglePlayでの作業
  1. アプリをアップロードする
  2. アップロード処理が完了するまでの2~3時間程度かかります。 ※公開はしなくていいですが、どうしても上手くいかない場合は公開することでうまくいく事があります。
  3. アプリ内サービスを追加する
  4. 「premium」と「gas」
  5. 端末にadbコマンドでアプリを入れる
  6. ※GooglePlayにアップしたものと、端末のアプリは同一バイナリである必要はありません。 パッケージ名と署名とバージョンコードさえ一致していれば大丈夫です。
  7. アプリを起動し、「BUY GAS」を押してみる
購入するとお金が発生するのでご注意を。
購入した場合は、デベロッパーコンソールからGoogleCheckoutに行き、払い戻しをしてください。

2012年12月13日木曜日

In-app Billing Version 3 翻訳(Version 3 API 構成編)

チームEGGの曽川です。

今回は更に実装に近い部分に入っていきます。
誤訳やよい表現がありましたらご指摘ください。
原文はこちらです。

アプリ内課金バージョン3

アプリ内課金バージョン3 APIは、アプリにアプリ内課金の組み込みことを簡単にします。
このバージョンの機能には、改良された同期フロー、消耗品の所有を追跡するAPI、アプリ内課金のローカルキャッシングを含みます。

サービスタイプ

サービスタイプ、SKU、価格、説明などをGooglePlayデベロッパーコンソールを使用してサービスを定義します。
詳細については、Administering In-app Billingを参照してください。
バージョン3のAPIは管理対象のアプリ内サービスタイプのみサポートします。

管理対象アプリ内サービス
管理対象のアプリ内サービスはGooglePlayによって所有情報が管理されるアイテムです。
ユーザがマネージドアプリ内アイテムを購入すると、GooglePlayはそれぞれの購入情報を保管します。
これにより、特定のユーザーが購入したアイテムの状態を復元するために、後でいつでもGoogle Playに問い合わせることができます。
この情報は、ユーザがアプリをアンインストールしても端末を変えてもGooglePlay上で永続的に保存されます。

バージョン3のAPIを使用している場合、アプリの管理対象のアイテムの消費を管理できます。
通常は複数回購入できるアイテム(ゲーム内通貨、燃料、魔法の呪文)の消費を実装します。
一度購入すると、管理対象のアイテムはGoolePlayに消費リクエストを送り、アイテムを消費するまで購入できません。
アプリ内サービスの消費の詳細については、「アイテムの消費」を参照してください。


アイテムの購入

バージョン3のAPIを使用した一般的な購入の流れは次の通りです。
  1. アプリは、使用しているアプリ内課金APIのターゲットバージョンがサポートされているか決定するためにisBliingSupportedリクエストをGooglePlayに送ります。
  2. アプリの開始やログインした時に、アイテムをユーザが所持しているか決定するためにGooglePlayに確認することを推奨します。
    ユーザのアプリ内課金を照会するためには、getPurchasesリクエストを送信します。
    リクエストが成功すれば、GooglePlayは購入したアイテムのサービスIDのリスト、各購入アイテムの詳細リスト、購入のための署名リストを含むBundleを返します。
  3. 通常、購入可能なサービスを知らせたいと思います。
    アプリ内サービスの詳細を照会するために、アプリはgetSkuDetailsリクエストを送信できます。
    照会するリクエストにサービスIDのリストを指定しなければなりません。
    リクエストが成功すれば、GooglePlayはサービス価格、タイトル、説明、購入タイプを含むサービス詳細のBundleを返します。
  4. アプリ内サービスがユーザに所有されていない場合、購入を開始することができます。
    購入リクエストを開始するために、アプリは。他のパラメータを一緒に購入するアイテムのサービスIDを指定して、getBuyIntentリクエストを送信します。
    デベロッパーコンソールで新しいアプリ内サービスを作成する際は、サービスIDを記録する必要があります。
    1. GooglePlayはアプリが使用するPendingIntent(購入の支払いをするUIを開始するため)を含むBundleを返します。
    2. アプリは startIntentSenderForResultメソッドを呼び出すことで、Pending Intentを起動します。
    3. 購入フローが終了すると(ユーザが正常にアイテムを購入した、または購入をキャンセルした)、GooglePlayはレスポンスインテントonActivityResultメソッドに送ります。
      onActivityResultの結果のコードは、課金が成功したかキャンセルしたかを示す結果コードを持っています。
      レスポンスインテントは、purchaseTokenの文字列(購入トランザクションを一意に識別するためのGooglePlayが生成)を含む購入されたアイテムの情報を含みます。
      Intentは購入の署名(開発者のプライベートキーで署名された)を含みます。
バージョン3のAPI呼び出しとサーバーの応答の詳細については、In-app Billing Referenceを参照してください。

図1.購入リクエストのための基本的なシーケンス

アイテムの消費
アプリ内サービスのユーザの所有を追跡するために、消費の仕組みを使用します。
バージョン3では、全てのアプリ内サービスは管理されます。
アプリ内で購入した全てのアイテムについてのユーザの所有権は、GooglePlayによって保持されることを意味します。
そしてアプリはユーザの購入情報を必要なときに照会できます。
ユーザがアイテムを正常に購入すると、購入がGooglePlayに保存されます。
一度アイテムを購入すれば、「所有」されているとみなされます。
「所有」された状態のアイテムは、GooglePlayから購入することはできません。
GooglePlayで再び購入可能な状態にする前に、「所有」アイテムの消費リクエストを送らなければいけません。
アイテムの消費は、「所有していない」状態に戻し、以前の購入データを破棄します。

ユーザが所有するサービスのリストを取得するためには、アプリからgetPurchasesをGooglePlayに送信します。
アプリはconsumePurchaseを送信することで消費リクエストを行うことができます。
リクエストの引数で、購入時はGooglePlayから得た、アイテムの一意となるpurchaseToken文字列を指定しなければなりません。
GooglePlayは消費が正常に記録されたかどうかを示すステータスコードを返します。

非消費/消費アイテム
アプリ内サービスを非消費/消費アイテムを決めるのはあなた次第です。

非消費アイテム
通常、アプリ内で一度購入すると永続的にメリットを提供するため、アイテムの消費を実装しません。
購入後、アイテムは永続的にユーザのGoogleアカウントに関連付けられます。
非消耗品の例としては、プレミアムアップグレードやレベルパックです。

消費アイテム
非消費アイテムとは対照的に、購入を複数回利用できるようにアイテムの消費を実装できます。
通常、消費アイテムは特定の一時的な効果を提供します。
例えば、ゲーム内キャラクターのライフ増加やインベントリ内に臨時でコインを獲得することです。
アプリ内で購入したアイテムのメリットや効果を供給することを、アプリ内サービスのプロビジョニング(支給)と呼ばれます。
あなたはアプリサービスがどのようにユーザに提供されるか、制御や追跡する責任があります。

重要:アプリ内で消費アイテムをプロビジョニング(支給)する前に、GooglePlayに消費リクエストを送り、消費が記録されたことを示す成功リクエストを受け取らなければいけません。

●アプリでの消費アイテム購入の管理
消費アイテムを購入するための基本的なフローは以下の通りです。

  1. getBuyIntentを呼び出し、購入フローを起動します。
  2. 購入が正常に完了したら、GooglePlayからレスポンスBundleを取得する。
  3. 購入に成功した場合は、consumePurchaseを呼び出し、購入を消費します。
  4. 消費が正常に完了したかどうかを示すGooglePlayからのレスポンスコードを取得します。
  5. 消費が完了したら、アプリのサービスのプロビジョニング(支給)をおこないます。

その後、ユーザがアプリを起動するかログインする時に、未決済の消費アイテムをユーザが所有するかチェックする必要があります。
もし、あるならばアイテムを消費し、支給してください。

  1. ユーザにより所有されているアイテムを照会するためにgetPurchasesリクエストを送ります。
  2. 消耗品がある場合は、comsumePurchaseを呼び出し、アイテムを消費します。この手順は、アプリが消費アイテムの購入注文を完了したが、停止もしくはアプリが消費リクエストを送信する機会の前に切断されたかもしれないので必要です。
  3. 消費が正常に終了したかどうかを示す、GooglePlayからのレスポンスコードを示します。
  4. 消費が成功していれば、アプリでサービスを支給してください。


図2.消費リクエストのための基本的なシーケンス

ローカルキャッシング

GooglePlayクライアントは現在、デバイス上でローカルにアプリ内課金情報をキャッシュするので、更に頻繁に情報を照会するためにバージョン3のAPIを使えます(例えばgetPurchaseの呼び出しを介して)。
以前のAPIのバージョンとは異なり、バージョン3の多くの呼び出しは、GooglePlayとのネットワーク接続を介する代わりにキャッシュのルックアップを通して処理されます。
これはAPIの応答時間が格段に上がっています。

Except as noted, this content is licensed under Creative Commons Attribution 2.5.

2012年12月12日水曜日

In-App Billing Version 3 翻訳(Android Developer Blog編)

チームEGGの曽川です。

アプリ内課金バージョン3が出たようです。
非常に簡単になったらしいので、少しずつ訳していきたいと思います。

今回はAndroid Developer Blogの記事です。
※翻訳ミス・改善等ありましたら、お知らせください。


●Google Developers Blog
アプリ内課金はGooglePlay(当時はAndroidマーケット)で発表されて以来、長い道のりを歩んできました。
1年半後にGooglePlay上の収益トップのアプリの大半がアプリ内課金を使用し、
数千の開発者は、体験版、仮想グッズと同時に定期購読でアプリを収益化しています。

アプリ内課金は今日さらに拡大しており、さらに強力で柔軟に成功するアプリを構築し続けることができます。
バージョン3では次の新機能が導入されています。
・設計の改善:アプリの記述、デバッグ、メンテナンスが簡単になります。
以前は数百行のコードが必要でしたが、わずか50行で実装可能です。
・さらに堅牢なアーキテクチャによりトランザクションの失敗を少なくする。
・高速なAPIコール用のローカルキャッシング
・購入の消費の管理やサービス情報のクエリのを使用するような待望の機能

アプリ内課金のバージョン3はアプリ内アイテム、定期購読(2013年2月から)、無料期間のある定期購読を利用可能です。
GooglePlayの最新版を実行しているAndroid2.2以上の端末でサポートされています。(90%以上のアクティブな端末)

以前のリリースしていた非同期構成の4つの異なったアプリコンポーネントを必要とする代わりに、新しいバージョンのAPIは、開発者が同期的なリクエストとレスポンスへの応答を1つのActivityで行えることを可能にしています。
これらは全てわずか数行で達成されます。
実装のコスト削減は、新しく課金ソリューションを実装する開発者の絶好の機会です。

●より簡単になった実装
バックグラウンドサービスを介して非同期通知を行う以前のモデルと比較して、
新しいAPIは同期しており、アプリに購入結果をすぐに送ります。
これはアプリのライフサイクル内で購入結果に対する非同期の応答が不要になり、開発者がアプリ内アイテムを販売するために書かなければならないコードを大幅に簡単にします。

購入画面を起動するためには、APIから購入インテントを取得し、起動するだけです。

Bundle bundle = mService.getBuyIntent(3, "com.example.myapp", MY_SKU, ITEM_TYPE_INAPP, developerPayload);

PendingIntent pendingIntent = bundle.getParcelable(RESPONSE_BUY_INTENT);
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
    // 購入フローの開始(GooglePlayのUIを表示します).
    // 結果はonActivityResult()を介して通知されます。.
    startIntentSenderForResult(pendingIntent, RC_BUY, new Intent(),
        Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
}

そして、購入結果をActivity#onActivityResultメソッドで処理します。
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == RC_BUY) {
        int responseCode = data.getIntExtra(RESPONSE_CODE);
        String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
        String signature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);

        // 購入情報をここで処理します。
        // (プレミアムアップグレードのような恒久的なアイテム[アップグレードによる効果の適用を意味する]
        // Xゴールドのコインのように消耗品アイテムのために
        // 一般的にアプリはここで購入の消費を開始します。)
    }
}

また、前バージョンからの違いは、全ての購入はGooglePlayで管理されます。
これは、購入したアイテムの所有をいつでも照会できることを意味します。
管理対象外のアイテムと同じ仕組みを実装するために、アプリケーションは購入の後直ちにアイテムを消費し、消費の成功と同時にアイテムのメリットを提供することができます。

●ローカルキャッシング
APIは、アプリが端末内のアプリ内課金情報を簡単に利用できるようにキャッシュするGooglePlayストアアプリの新機能を利用しています。
この機能を利用すると多くのAPIの呼び出しは、GooglePlayへ通信して接続する代わりにキャッシュのルックアップを通じてサービスを受けます。
これは大幅にAPIの応答速度を向上します。
例えば、アプリがこの呼び出しを利用して所有する項目を照会できます。

Bundle bundle = mService.getPurchases(3, mContext.getPackageName(), ITEM_TYPE_INAPP);
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
    ArrayList mySkus, myPurchases, mySignatures;
    mySkus = bundle.getStringArrayList(RESPONSE_INAPP_ITEM_LIST);
    myPurchases = bundle.getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST);
    mySignatures = bundle.getStringArrayList(RESPONSE_INAPP_PURCHASE_SIGNATURE_LIST);

    // アイテムの処理をここでおこなう
}

以前のバージョンでは、所有アイテムの紹介は負荷の高いサーバコールだったため、開発者は頻繁に行う事で落胆しました。
しかし、新しいバージョンのローカルキャッシュの実装はアプリは起動開始時にクエリを毎回実行でき、その後は必要に応じてクエリを実行できます。

●サービス情報
待望のAPIの機能が導入されています。
GooglePlayから直接商品情報を照会できます。
開発者はアイテムのタイトルや説明・価格をプログラム上で取得できます。
通貨変換や書式設定は必要ありません。
価格はロケールに応じてユーザの通貨で表示されフォーマットされます。

Bundle bundle = mService.getSkuDetails(3, "com.example.myapp", 
        ITEM_TYPE_INAPP, skus); // skus is a Bundle with the list of SKUs to query
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
    List detailsList = bundle.getStringArrayList(RESPONSE_SKU_DETAILS_LIST);
    for (String details : detailsList) {
        // 詳細はSKU詳細のJSON内にあります。(説明、価格)
        // SKU:最小管理単位
    }
}

これは、例えば開発者がデベロッパーコンソールで価格を更新して、ユーザに表示するアプリのコードを変更する必要なしに、このAPIを呼び出して更新された価格を表示(特別プロモーションやセールなど)できます。

●サンプルアプリケーション
APIに加えて、アプリ内課金を実装例のサンプルアプリをリリースしています。
また、JSON文字列やBundleから変換/復元されたデータ構造、署名の検証、バックグラウンド処理を自動的に管理できるユーティリティ(開発者がアプリケーションのUIスレッドからAPIを直接呼び出すことができるようにするため)の一般的に書かれる定型的なコードを実装するヘルパークラスも含んでいます。
Googleは実装のプロセスを簡素化するために、アプリ内課金に慣れていない方はサンプルコードを活用する事を非常に推奨します。
サンプルアプリケーションはAndroid SDK Managerを介してダウンロードする事が出来ます。

●アプリの固有キー
アプリ内課金バージョン3で導入された他の変更に加えて、ライセンスとアプリ内課金のキーの管理方法さらに改善しました。
キーは現在「デベロッパーごと」の代わりに「アプリごと」に設定されています。
これはGoogle Play デベロッパーコンソールプレビュー上にある、各アプリの"Services & APIs"ページで利用可能です。
既存のアプリは現在のキーで動作し続けます。

●始めましょう
新しいAPIを使用してアプリ内課金を実装するために、アプリ内課金の更新されたドキュメントから始まり、アプリ内サービスの販売のトレーニングクラスに行きましょう。
アプリ内課金バージョン3を使用するためにGooglePlayデベロッパーコンソールプレビューを使用する必要があります。

2012年12月7日金曜日

Google Maps Android API v2の使い方

EGGの曽川です。

Android公開当初からあまりアップデートのなかったGoogleMapsでしたが、
とうとうアップデートされ、v2という形になりました。
非常に使いやすくなったのでぜひ使ってみてください。
以下では地図の表示までを行います。
地図の表示までの作業は主に、「APIキーの生成」、「Androidプロジェクトの作成」の2つです。


●APIキー生成手順
このAPIキーがあればGoogleMapsが表示されます。
・キーストアのSHA1をメモる
--------------------
キーストアはどこにあるのか?
デバッグの場合
Eclipseの設定から[Android->Build->Default debug keystore]で指定されているものがデバッグ用のキーストア。


リリースの場合
自分で作成して保管しているはず
--------------------
SHA1を取得するコマンドを実行する
keytool -list -v -keystore [キーストア]
※デバッグキーストアの場合はパスワード不要

・Google API Consoleからプロジェクト作成
https://code.google.com/apis/console/

・左側の[Services]から[Google Maps Android API v2]をONにする

・API Accessから[Create new Android key]を押す

・空欄に以下を入力
[メモったキーストアのSHA1;アプリのパッケージ名]
例:45:B5:E4:6F:36:AD:0A:98:94:B4:02:66:2B:12:17:F2:56:26:A0:E0;com.example

・作成するとAPIKeyが表示されているのでメモる。
APIキー準備完了


●デモ(東京タワーとスカイツリーを別の地図上に表示する)
・SDK ManagerでGoogle Play servicesをインストール


・Google Play servicesのライブラリをEclipseにインポート
{android-sdk}/extras/google/google_play_services/libproject/google-play-services_lib
※GoogleMaps等を使うために必要なライブラリ(isLibrary形式)
・プロジェクトのライブラリとして加える
・Androidプロジェクトを作成
※プロジェクトのパッケージ名はGoogleAPIで指定したものと合わせること
・以下のファイルを作る
public class MainActivity extends FragmentActivity {

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

}

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#EDEDED"
    android:orientation="vertical" >
    <fragment
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="4dp"
        android:layout_weight="1"
        class="com.google.android.gms.maps.SupportMapFragment"
        map:cameraTargetLat="35.658599"
        map:cameraTargetLng="139.745443"
        map:cameraTilt="60"
        map:cameraZoom="18" />
    <fragment
        android:id="@+id/map2"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="4dp"
        android:layout_weight="1"
        class="com.google.android.gms.maps.SupportMapFragment"
        map:cameraTargetLat="35.7105827"
        map:cameraTargetLng="139.811487"
        map:cameraTilt="60"
        map:cameraZoom="18" />
</LinearLayout>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mapdemo"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <!-- 必要なパーミッション群 -->
    <!-- 通信に必須 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 地図キャッシュ -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- GPS取得に必須 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <!-- GoogleMaps表示 -->
    <uses-permission android:name="com.example.mapdemo.permission.MAPS_RECEIVE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <permission
        android:name="com.example.mapdemo.permission.MAPS_RECEIVE"
        android:protectionLevel="signature" />
    <!-- OpenGLES2.0 -->
    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <!-- GoogleMapsを使用するAPIキーとか -->
        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="ここにAPIキーを入れる" />
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
(※com.example.mapdemoを自分のパッケージ名で置き換えること)
(※applicationのmata-dataを自分のAPIキーで置き換えること)

デモ画像

地図複数同時表示や角度・場所の指定もレイアウトだけでできました。
コレは便利ですね。



2012年11月13日火曜日

Android アプリ クオリティガイドライン ことはじめ (前編)をまとめました。

チームEGGの曽川です。

今回は、キャプチャはありません。
AndroidDevelopersサイトのAppQualityのセクションの解説ですので、
ぜひサイトと対応させながら見てください。

公式のまとめはこちらです。


背景として端末の魅力はアプリによって引き出される。
アプリを使ってて「端末買って良かった」と思われるように成功を目指していただきたい。
クオリティが高いものはそれだけで差別化できる。

AndroidDevelopersサイトのAppQualityのセクション
CoreAppQuality
ベースとなるアプリの品質
TabletAppQuality
最近追加されたタブレットに関する内容。
Nexus7、Nexus10などどんどん増えてくるので、Googleは強く案内している。
ImprovingAppQuality
ユーザからのフィードバックに対するレスポンスに関するおすすめの事項

今日はCoreAppQualityについての話。
Visual Design and User Interaction
Androidデザインガイドラインに準拠してください。
より簡素化した説明。
ナビゲーション
・バックボタン
・ホームボタンを押したら戻る
・キーのカスタマイズは推奨されていない
・ノティフィケーションはうるさすぎないように

Functionality
・パーミッションはできるだけ少なく
・必要になる場合
descriptionに書く
実行している間に動的にダイアログ等で注意喚起を行う。

Install Location
AndroidManifest.xmlで外部保存可能なように書く(2.2以上なら可能)
internalOnlyだと苦情か来る
ほぼ必須

Audio
ロックスクリーンで音を鳴らさない。
ホームキーを押したときに音を止める。

UI and Graphics
ツール系は縦横対応する
下からキーボードを引き出すアプリもある
ゲームの場合は世界観によって考えて欲しい、対応しているとなおよし

User/app state
アプリを離れて戻ってきたときの制御
ゲームの状態等が保存されていいるか
GPS・Bluetooth等が切れるか
フォーカスやActivityのライフサイクルで管理して欲しい

Performance and Stability
早くて安定したアプリを作ってください
画面をタップして0.2秒未満で反応する
UIがサクサク動く事は非常に重要
WebViewの場合
⇒タッチした後にネットワークを見に行くのはもはや遅い
先読みやキャッシュを行う
画面を切り替えてからデータを用意する
⇒ユーザを待たせない

Stabllity
クラッシュしない、フリーズしないという基本的で深いこと

VisualQuality
画面の解像度に合わせて美しいビジュアルを提供する。
⇒電話機とタブレットでアプリの画像がぼやけたりしないようにする

GooglePlay
ポリシーを守る
コンテンツレーティングも正しくつける
必ずフィーチャグラフィックを入れる。これがないとフィーチャできない
Androidではない端末でのビデオや画像はやめてね。
ユーザがレポートを挙げた場合はサポートする
他にもテスト等の情報もある

こういうのを出す意味
コンテンツが良い+ガイドラインに沿っている
⇒フィーチャされやすくなる
ランキングが上がる
星の数が多くなるとランキングが上がる
ユーザに長く使われるアプリを作る
編集会議でも露出が上がるようになる

Q.ユーザーの声を聞くサービスとして、getsatisfact-ion.com や uservoice.com が紹介されていますが、日本で同様のサービスはありますか?
A.誰かやってほしい。
ユーザとのインタラクティブな対話ができるものを
やり取りの機能をGooglePlayでコメント返信機能は徐々に広めていく

Q.クオリティガイドラインの中で、守らないと、アプリとしてリリ-ースできない事項はありますか?
A.リリースはできるが品質が悪いとユーザから苦情が来る。
ポリシー違反は発見されると履歴に黒星が半永久的に残る。
一部のユーザに公開して、ユーザのフィードバックをもらいながらアプリを成長させていくのもよく見る

Q.クオリティガイドラインに記載されている内容で、特に日本のマ-ーケットで重要視される内容や、日本と海外の状況と違う事柄があ-れば、教えてください。
A.日本は厳しいので、日本でいければ大丈夫。
タブレットにするとレイアウトが崩れるのが多い。
海外はタブレットに対応している事が多い。
国境の違いはあまりないように思う。
Webゲームでの縦スクロールは日本でのみ受け入れられる
海外はスクロールのカルチャーが受け入れられにくい
テイストの部分(カルチャライズ)
アジアは賑やかなデザインが好まれる
西洋は飾りのないはすっきりしたデザインが好まれる
Androidのガイドラインは中立の立場

補足
Yukio Andoさんが日本語訳を作成していますので、ぜひご覧を。
"Core App Quality Guidelines"
"Improving App Quality"

2012年10月21日日曜日

Androidを開発して気づいた5つの誤解

チームEGGの曽川です。

「ICT ERA + ABC 2012 東北」で「Androidを開発して気づいた5つの誤解」と言うタイトルでLT発表をしてきましたので資料を公開します。

資料はこちらです。

これまでチームEGGとしては「よるにゃん」、「待ち受けトラッパー」をリリースしてきましたが、どうすればいいのか手探り状態でした。
そういった、「どうやってユーザをファンするの?」にしたり「どうやってダウンロード数伸ばせばいいかな?」と言うところに焦点を当てています。
ぜひご覧くださればと思います。

2012年10月17日水曜日

GoogleAnalyticsを利用したエラー報告

チームEGGの曽川です。

アプリの強制終了の報告はGoogle Playのデベロッパーコンソールでも確認できますが、ユーザに送ってもらわなければなりません。
この1クッションの手順が結構面倒なため、デベロッパーコンソールにはなかなか送ってもらえません。
そのため、GoogleAnalyticsを使用して自動的に送ってもらうようなサンプルを紹介します。
サンプルコードは以下のとおりです。
Androidのバージョン、端末名、エラー内容を送信します。

/**
 * 原文のスタックトレースを送信します。
 * 
 * @author yoshihide-sogawa
 * 
 */
public class AnalyticsRetracableUncaughtExceptionHandler implements UncaughtExceptionHandler {

    /** {@link Context} */
    private Context mApplicationContext;

    /** Androidのデフォルトの{@link UncaughtExceptionHandler} */
    private UncaughtExceptionHandler mExceptionHandler;

    /** 改行の代用文字(Analyticsで改行文字が使用できないため) */
    private static final String BR = "=";

    /**
     * コンストラクタ
     * 
     * @param applicationContext
     *            {@link Context}
     */
    public AnalyticsRetracableUncaughtExceptionHandler(final Context applicationContext) {
        mApplicationContext = applicationContext;
        mExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void uncaughtException(final Thread thread, final Throwable ex) {

        // 非同期でアナリティクストラックを実行
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(final Void... voids) {
                try {
                    // トラックイベントを生成
                    doTrackEvent(ex);
                } catch (Exception e) {
                    // エラーがあってもクラッシュを防止
                } finally {
                    // 強制終了
                    mExceptionHandler.uncaughtException(thread, ex);
                }
                return null;
            }
        }.execute();

    }

    /**
     * トラックイベントを実行します。
     * 
     * @param ex
     *            {@link Throwable}
     */
    private void doTrackEvent(final Throwable ex) {

        final StringBuilder actionBuilder = new StringBuilder();
        // バージョン情報
        actionBuilder.append("versionCode:");
        actionBuilder.append(AppUtils.getVersionCode(mApplicationContext));

        final StringBuilder errorMessageBuilder = new StringBuilder();
        // OS
        errorMessageBuilder.append("os:");
        errorMessageBuilder.append(Build.VERSION.SDK_INT);
        errorMessageBuilder.append(BR);
        // モデル
        errorMessageBuilder.append("model:");
        errorMessageBuilder.append(Build.MODEL);
        errorMessageBuilder.append(BR);
        errorMessageBuilder.append(Log.getStackTraceString(ex));

        // アナリティクスに送信
        final GoogleAnalyticsTracker tracker = GoogleAnalyticsTracker.getInstance();
        tracker.startNewSession("UA-XXXXXXXX-X", mApplicationContext);
        tracker.trackEvent("error", actionBuilder.toString(), errorMessageBuilder.toString(), 1);
    }
}

あとは、アプリケーションクラス等で以下の記述をすると準備完了です。
Thread.setDefaultUncaughtExceptionHandler(new AnalyticsRetracableUncaughtExceptionHandler(this));

ちなみにGoogleAnalyticsのV2からは、どういうエラーでアプリが強制終了したかを自動的に送信してくれます。
詳細はV2の記事を参考にしてください。

2012年10月10日水曜日

「Android での Google Play 課金 API 続・ことはじめ (実践編)」をまとめました。

チームEGGの曽川です。
今回は「「Android での Google Play 課金 API 続・ことはじめ (実践編)」をまとめました。
動画はこちらです。


今日はIn-App-Billingの遅延の話し。
In-App-Billinの質問は多く来ている
業績を上げている会社は多い

問題点
反映が遅くてクレームの処理に追われる事もある
日本だけではなく、韓国、アメリカ、ヨーロッパでも起こっている。

今回はホワイトボードで紹介する
推奨している作り方とは?

構成
アプリには一般的に以下のものが含まれる
Activity
Service
BroadcastReceiver


GooglePlay側
アプリのBroadcastReceiverに対してIN_APP_NOTIFYを通知


サンプルプログラムの例
1.GooglePlayがBroadcastReceiverに対してIN_APP_NOTIFYを送信
2.ServiceでIN_APP_NOTIFYを受信
3.GooglePlayに対してgetPurchaseInformationを送信
4.BroadcastReceiverでPURCHASE_STATE_CHANGEDを受信
5.Serviceに処理を渡す
6.Service内で後にゴニョゴニョ処理を行う
7.結果がActivityに反映される

どこで間違いが起こるか?
1.GooglePlayからIN_APP_NOTIFYが飛んで来る時
この処理が遅い?届いてない?と問い合わせが多く寄せられる
しかしGoogleの分析ではほぼ速攻で送っている
2.BroadcastReceiverからServiceに処理を渡すとき
3.ServiceからActivityにデータを渡すとき

メモリ不足によるダメージ
GooglePlayの画面が前面に出ているときはメモリを使っている
そのため以下の事が起こることが多い
1.BroadcastReceiverまでは届いたが、Serviceに渡すときにメモリ不足でServiceが起動しない
2.Serviceまでは届いたが、画面に反映させたりDBに保存する際にアプリが終了した
⇒GooglePlayはCONFIRM_NOTIFICATIONが送られるまで再配信する仕様
ただし、運が良ければ数秒、悪ければ数分後に配信されるため1回でも起こるとダメージが大きい
⇒ユーザはその間待ち続ける
再送は3秒後、30秒後、2分後という風に広がっていく(間隔は保障されていない)

一番最初の配信を逃さない。
BroadcastReceiverで受け取った瞬間にSQLite,txt,SharedPreferenceに保存するなどする
⇒サンプルコードでは記述されていない
Broadcastから書くのか良いのかServiceで書くのが良いのかまだ分析できていない
受け取ってから10ms以内書き込む
これを行う事で安定したという報告がある

データの保存後
まずは速攻でCONFIRM_NOTIFICATIONSで処理を返す
Serviceでいけるならばその場で処理を続ける
次回アプリを起動した際にデータが残っていれば処理の続きを行う

データ保存のありがちな間違い
1.PURCHASE_STATE_CHANGEDで受け取ったデータをメモリ上にのみ保持する
2.図の2や3の処理を行う
3.途中でシャットダウンされる
4.次回のIN_APP_NOTIFYまでまたなければならない

Google側のCONFIRM_NOTIFICATIONSの計測
問題がないアプリは一瞬で返ってくる
問題のあるアプリは1分程度などかかる場合がある

アプリの検証
Activityが終了している状態
Serviceが一回走ったけど終了させられた場合
⇒動くか?
BroadcastReceiver単体で動作するか?

今回と08/28分の質問に答える
Q.GooglePlayカードについて
A.まだアメリカのみ
Q.In-App-BillingとSubscriptionのorderIdが一致しない
A.それぞれでキーとして扱うものが違う
SubscriptionはpurchaseTokenを使う
orderIdは次の月になれば別のものになったり、月の途中で変更されたりする(仕様)
一方In-App-BillingはorderIdを使う
Q.Subscriptionの払い戻しとキャンセル
A.GooglePlayのAPIは一度販売すると有効期限が来るまでは基本キャンセルは無いという仕様
払い戻しをした場合のコンテンツの提供の停止はアプリ側で行う
払い戻しの通知がアプリに届いた場合、サーバ側のAPIで判明した場合というロジックを明示的に作成する
Google側で検討する
Q.長期間支払っているユーザに割引などどう対応するか?
A.Subscriptionの値段の変更はできないが今後できるようになる
現状、GooglePlayのAPIではできない
Q.定期購読モデルを止める時
A.GooglePlayから規約上のペナルティはない。
一般常識に照らし合わせてユーザのクレームが起こらないようにする
ユーザにキャンセルの方法を案内する
・GoogleWalletからキャンセル
・GooglePlayからキャンセル
Q.定期購読の猶予時間はどう計算されるのか
A.最初の購入日時を照らし合わせている
Q.GooglePlayの他のアプリマーケットよりも低い傾向にあるが何か施策は無いか?
A.フリーミアム
無料でダウンロードさせて個別課金が多い
ユーザをどう集めるかが肝になってきている
iOSとAndroidで一人当たりの売り上げが変わらないところもあるしAndroidが低いといわれる事もある
・アプリのクオリティ
・ユーザ層の違い
・課金がうまくいかない
アプリ内課金の方が長期にわたって収益が上がりやすい
正式版をアプリ内課金で行っている方が収益が上がる
⇒端末のメモリを圧迫しなくてよい
AndroidのGooglePlayは全ユーザにクレジットを要求していないから?
⇒課金のハードルは低い

2012年9月26日水曜日

「Android アプリ機種対応ことはじめ」をまとめました

EGGの曽川です。

Android アプリ UI/UX デザイン ことはじめをみたのでまとめてみました。
動画はこちらです。
公式のまとめはこちらです。

全端末に対応しなけりゃいけないのではないか?疑問に答える。

●Android OSの最近の動き

4.0系の上昇が非常に著しい
09/04時点で4.0以上が22.1%

●画面化像度対応

どう解決するか?
Javaに関しては豊富なAPIが提供されている。
values-swXXdp(Smallest Width):画面の狭いほうの長さがxxdpであれば使用する。
トレーニングやAndroid デザインを活用する
GoogleIO 2012のリソースを参考にする
タブレット端末とハンドセットの端末を考慮する
dimenリソース(values>dimens.xml)を使用する
 ⇒レイアウトが同じでもスペースの大きさを変えられる(あまり知られていない)

●その他の話題

JavaのAPIは上方互換
 ⇒2.2等で動くなら4.0等でも動く
Android compatibility Definition Document
 ⇒全OSバージョン用に端末メーカへの最低要件定義を行っている
  迷った時はここを見る
OutOfMemory
 ⇒一部の端末ではRAMが小さい
  トレーニングを参考に
OSにより違う処理をする
 ⇒lazy class loading(リフレクションよりも高速)
  参考

●NDK
MIPSは安価で高速な処理を今後できるのではないか
Androidはarmだけではなくなっている
OpenSLも便利
 ⇒音声再生ライブラリ

CPUアーキテクチャ別のマルチプルapk
 ⇒これまではOSバージョンだけでしか分けられなかった

●OpenGL ES
90.7%がOpenGL ES 2.0
 ⇒2.0前提でOK
GPUファミリーが多い
 ⇒powerVRをターゲットにしている人が多いがAndroidでは特殊(iPhone)
  powerVRはレンダリングシェーダの属性をまとめてソートするのが早い
  GPU別にチューニングする
4つのファミリーでテストする。

●WebView
機種ごとのふるまいが違う(日本人からの質問が多い)
WebViewを透明にして後ろのActivityを見せるとかしているのはある端末ではトラブルが起こるなど。
 ⇒Google自身も全機種について把握していない
  ActivityとWebViewを重ねるのは気をつけてほしい
canvasとcssアニメーションはどっちが早い?
 ⇒cssアニメーション
  4.0ではcanvas中心になってきている。
  4.0でcssアニメーションを使い続けるとメモリリークしているかも?(Google調査中)
  ※ちなみに酷使しているのは日本だけ

ChromeとWebViewがマージされつつある。(完全マージはいつかは未定)
 ⇒今後は標準ブラウザとWebViewを統一化する

拡大縮小
 ⇒書くのが楽なのはviewport
  ただし、スクロールするときに画像が粗くなる
   ⇒WebViewでのスクロールをなるべく避けるのが良いのでは?
   ⇒もしくはjavascriptでガリガリ書く
WebViewは先読みとキャッシュを使う

●WebView(2)

WebViewのレイアウト計算がずれる場合がある
 ⇒ActivityとWebViewの計算をずらす

●SIMカードがないと起動できないアプリについて
物理的なIDの利用はおススメしない
SIMカードは携帯を対象としているので、Wi-fiのみ端末が軒並みダメになる。
個人情報漏えいの懸念
 ⇒root化した端末で偽装できる
  その人のアカウントで利用できてしまう
  原則はスマートフォンで自動ログインはしない
複数台以上の端末に入れてほしくない制御を入れたい場合には? 
 ⇒Build.SERIAL
  ANDROID_ID(端末初期化でリセット可能)
  ※2.3以降のみ
  ※やむを得ない場合に使用する
   Googleは禁止してはいないが十分注意する。ユーザはパーミッションを意識している。
  ※個人的にはこちらも参考にして欲しい
パーミッションについてはアプリ説明文で説明する

●端末のブラックリスト
サムスンの幾つかの機種が出て来ない場合もある
 ⇒Google側で対応中。
テストしてない機種という理由でブラックリスト化するのはオススメしない
 ⇒すぐ反映されないリスト
  入っていないリストはアクセスできる
 ⇒発売直後はアクセスできる
  ユーザの評価が固まってきたり、安定してきたらブラックリストは使わない。
Manifestで縛る方が安全

●Q&A
Q.不具合のポイントは?
A.OpenGL、カメラ
 Activity、Viewを普通に使っていたら機種ごとの差分は出ない
Q.テストのTips
A.10数機種テストする100機種以上テストしているところもある
 配信したい国の人気の端末でテストする
 RAM容量が小さい端末でもテストする
Q.タブレットのような画面を画面の小さな端末で代用する方法があると聞いたが?
A.Screen Compatibility Mode
 targetSDK8にしてると拡大される
Q.内蔵メモリなのに外部SDカードとして使わなければいけない場合はどうすればいいか
A.セカンダリエクスターナルストレージ問題
 標準のAPIではアクセス出来ない領域にストレージが存在している。
 機種別のプログラミングしかない
 Googleと端末メーカの問題

●補足
今後タブレット端末は増えるので対応してほしい

2012年9月12日水曜日

「AndroidアプリUI/UXことはじめ」をまとめました

EGGの曽川です。

Android アプリ UI/UX デザイン ことはじめをみたのでまとめてみました。
動画はこちらです。
公式のまとめはこちらです。

●より使いやすいUI
良くないUI
 やたらと縦スクロールをする
  ⇒Webだと自然
   画面を切り替える操作は何も操作しなくても表示されている状態が理想
   大事なボタンが下の方にスクロールしないと出てこないのはアプリとしてはダメ
   「これならWebで良くないか?」というコメントもGoogle Playで見かける


●Android OSのUI/UXの進化
 OS
 ⇒ハニカムのときは転換期
  HoloTheme、ActionBar
  ※タブレット対象なので広くは使われていない
 ⇒ICSはタブレットとハンドセットの統合
  現状のメインは2.3
 ⇒4.0、4.1は急速に伸びている、2.x系はマイナーになってきている
 これからはUIのデザインは4.xのデザインで作る


●Androidデザインガイドラインを読む
 ⇒Androidの基本的な考え方
  アクションバー、Backボタンのデザイン
  Googleのアプリはこれを基本的に使用している
 ⇒全く外れてしまうと、ユーザが迷ってしまう
  GooglePlayのランキングにも影響する


●様々なUI、UXのデザイン
 Webとアプリは大きな差がある
 ⇒Web:どのプラットフォームでも共通のものが提供される
  アプリ:プラットフォームごとに挙動や操作感が変わる


●AndroidDesignForSuccess(GoogleIO)
 ⇒デザインはブランドよりも基礎に位置する
  ありがちな間違い
 ⇒アプリを発注する側は仕様を詳しくは指定しない(ブランド表現、機能のリストだけを忠実に再現する事が多い)
  プラットフォームの事をよく考える


●Navigation in Android
 Backキーの扱い
  ⇒他プラットフォームの方が見落としがちなのがBackキーが標準搭載されている事
   画面の中の戻るボタンは描画領域として勿体ない
●Ten Things Game Developers Should Know

 ゲームの中のBackボタンの扱いは?
  ⇒シーンによって違う
   シューティングゲームの場合
    1回目ポーズのダイアログ(コンティニューorタイトル)
    2回目は何が起こるか?
    ⇒ゲームに戻るべき
     終了するのはダメ
  ⇒前の画面に戻ることに使ってください



●ActionBar
 絶対理解してください。
 タブと組み合わせることの重要性
 ⇒下にタブを出さない
 ⇒ルール
  ナビゲーション:上or左端
  アクション:右or下
 ⇒下タブのデメリット
  下にタブがあるのはナビゲーションが下にあることになる
  下のボタンの並びは大きい画面に対応しづらい
  もう一個のOS(アップルw)との工数削減を行いたくなるが避ける。
  企画の段階で配慮をしておけば対応できると思う


●BackとUpのちがい
 Back:時系列を戻る
 Up:論理階層を上がる


●Fragment
 Androidアプリは1つのapkで提供を、そのためにFragmentを使ってください。
 縦横、画面サイズの変化に柔軟に対応する
 かっこいいアプリを


●リスト表現
 枠線は極力減らす
 ⇒代わりに空白を使う
  右矢印は冗長なので避ける
  ※ここだけは見てほしいというような場合のみ付ける


●アプリボタン
 左側のアプリアイコン
  ⇒グラデーション、陰影あり
 右側のアクションのアイコン
  ⇒グラデーション、陰影もなし
   Android Designで提供しているので利用してください
 ⇒他プラットフォームからのコピーは×


●ホーム画面アイコン
 壁紙の色に関わらず見やすいように


●テーマ
 Holo.Light
 Holo.Dark
 ⇒必ずしも合わせる必要はないが、色使いなどを参考にしてほしい


●左右方向のスワイプ
 スワイプさせる方が混乱が少ない
 アプリらしいUIになる


●ホーム画面
 少し前までは機能のアイコンが並んでいるだけだった(ダッシュボード)
 ホーム画面はEvernote等で採用されているAppIconをタッチすると少し下がったところにあるUIにしてほしい(参考リンク)

●Notification
 うるさいものはNG


●キーポイント
 戻るボタン
 縦スクロールを控えめに

●ツールアプリとゲームアプリの違い
 ゲーム系
  ⇒独自のUIをもってくるのはOK
   ガイドラインを適用できないシーンもある
   ボタンが小さいとかは避ける
   ゲームであっても画面上の戻るボタンと縦スクロールはつらい
 ツール系
  ⇒アプリを発注、自己開発する場合はデザイナーとうまく仕事をする

Q.ICS、JBに移行するときの注意点
A.ICSとJBはそのまま移行できるはず
 JBはノティフィケーションがリッチになった
 WebView等の非公開APIは動作しなくなる
Q.古いAPIレベルと新しいものは対応が辛い。プリファレンスフラグメントが対応されないのは?
A.Compatibility⇒SupportLibraryに名前が変更
 プライマリリスト順に行っている
 ⇒アクションバーの優先度が高い
  PreferenceFragmentはActionBarよりも低い
  現状はオープンソースを利用するなどして自分で対応する
Q.ActionBarは2.x系でつかえるのか?
A.4.xは標準だが、2.x系はActionBarSherlockは良くできているので使用する
 サポートライブラリは現在準備中
Q.Androidデザインは日本語化あるのか
A.要望はあるが、検討中
 予算的に多く裂いてもらえればあるかもw
Q.WebView/HTMLを使わないアプリの速度向上方法
A.SDKのツールでプロファイルする
Q.多くの端末で対応している場合にUIの実機テストの勘所はあるか?
A.targetSDKによるメニューボタンの違い
 Backボタンがハードウェアかソフトウェアで違う
 画面サイズ
 機種ごとの違いが出る
  ⇒OpenGL、カメラ
 ※リストビューのスクロール速度は機種によって違うので注意