/* */ /* */

2012年11月26日月曜日

【iPhone】課金処理が勝手に走る状態の防止【StoreKit】

※この記事の内容はどう考えても推奨されません。その場しのぎです。
 実装しちゃって課金処理で不利益出ても何も出来ません。

※iOS7、iOS8で再度組み直してみたのですが、今コレ使えないみたいです。
 ダイアログ出る前にSKPaymentTransactionStatePurchasingが
 飛んできませんでした。

何やらiPhoneの課金処理に関する掲示板などを見ていると、
「アプリを起動すると何故か課金処理が走ってしまう」
というのを時々見かけます。

取りあえずその場しのぎの策に行き着いたので書き残します。

iPhoneアプリを起動して、特に操作をしていないのに、
課金のダイアログが出て、IDとパスワードを求められる事があります。
これは、前回何かしらのアプリを起動して、課金処理に入り、
課金処理が正常に終了しないままアプリが落ちるとなります。
次回起動した後、特定のタイミング(StoreKitさんが要らん親切で通知してくれます。)でダイアログが呼び出されます。

本当に厄介!!

この問題に突き当たるという事は、公式のプログラミングガイド通りに、
アプリ起動直後にオブザーバー(何か課金処理を管理してるっぽい奴)の登録をしているのだと思われます。
そすると、最初に登録したオブザーバーさんが律儀に途中で終わっちゃってるトランザクションを拾って来てくれるみたいです。(しかもストアキットさんの任意のタイミングで・・・)

なので。。。

これ破棄すればオーケー

「ちゃん再処理しろよ」という白い林檎からのプレッシャーを感じますが取り合えず無視
 だっていきなりダイアログ出てくるとか不審過ぎるし、タイミング取得できる様な仕組みも見当たらないし・・・


本題です。
ここではNonCondensableな課金を例として挙げております。


AppDelegate.h

@interface AppDelegate : UIResponder 


AppDelegate.m
//未完トランザクション用

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{

    for (SKPaymentTransaction *transaction in transactions) {

        switch (transaction.transactionState) {

    

            case SKPaymentTransactionStatePurchasing: {// 購入中

                [queue finishTransaction:transaction];

                //アイテムID取得したいならこの部分

                break;

            }

            case SKPaymentTransactionStatePurchased: {// 購入成功

                [queue finishTransaction:transaction];

                break;

            }

            case SKPaymentTransactionStateFailed: { //購入失敗/中断

                [queue finishTransaction:transaction];

                break;

            }

            case SKPaymentTransactionStateRestored: {// 購入履歴復元

                [queue finishTransaction:transaction];

                break;

            }

        }

    }

}


//オブザーバーの削除(NSNotificationCenterで呼ばれる)


-(void)removeTransaction {

    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

}

//オブザーバーの登録(NSNotificationCenterで呼ばれる)

-(void)addTransaction {

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

}


//なんか、スプラッシュ画面で呼ばれてる部分


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    〜〜〜〜〜〜〜

    //何か色んな処理 

    〜〜〜〜〜〜〜


    //オブザーバーの登録

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    

    // 通知センターに登録

    //オブザーバーの破棄をするメソッド

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeTransaction) name:@"remove" object:nil];

    //オブザーバーの登録をするメソッド

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(addTransaction) name:@"addtran" object:nil];

}
という具合に、アプリ起動時に未完トランザクション用のオブザーバーの登録を済ませてしまいます。
未完トランザクションがあった場合、-(void)paymentQueueホニャララが呼ばれますので取りあえずfinishTransactionを呼んじゃいます。
ダイアログが表示されるのは、1回目に
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
に入った後(1回目は購入中に入ります)なので、そこで終了させてしまえば
ダイアログは出ないみたいです。
「任意のタイミングで処理したい」ならば、「//購入中」の部分で
アイテムIDを適当なArrayにでも突っ込んで、
お好きなタイミングで課金処理を走らせると良いかと思います。
取りあえず破棄完了!!
でもこのままだと、未完じゃない新規のトランザクションまで破棄されちゃいます。
なので、別に実際の課金処理を用意してやります。
MyStoreObserver.h
#import 
@interface MyStoreObserver : NSObject
@end
MyStoreObserver.m
@implementation MyStoreObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    NSLog(@"paymentQueue:updatedTransactions");
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchasing:
            {
                // 購入処理中
                break;
            }
            case SKPaymentTransactionStatePurchased:
            {
                [queue finishTransaction:transaction];
                // 購入処理成功時の処理
                break;
            }
            case SKPaymentTransactionStateFailed:
            {
                [queue finishTransaction:transaction];
                // 購入処理エラー。ユーザが購入処理をキャンセルした場合もここにくる
                // エラーが発生したことをユーザに知らせる
                // [self failedTransaction:transaction];
                if (transaction.error.code != SKErrorPaymentCancelled){
                    //エラーの場合
                } else {
                    //ユーザーがキャンセルした場合
                }
                break;
            }
            case SKPaymentTransactionStateRestored:
            {
                [queue finishTransaction:transaction];
                // リストア処理
                [self restoreTransaction:transaction];
                break;
            }
        }    
    }
}
// 全てのリストア処理が終了(その後、「購入処理の終了」が呼ばれる)
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue 
{
     //何かの処理   
}
// リストアの失敗(cancel時も呼ばれる)(その後、「購入処理の終了」は呼ばれない)
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
    //何かの処理
}
// 購入処理の終了
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions 
{    
    //南下の処理
}
@end

んでもって、後は実際課金走らせる部分で
○○○.m
//上の方で宣言しておく
MyStoreObserver *mySKObserver;
~~~~~
~~~~~
//ここでダミー破棄の通知
NSNotification *n = [NSNotification notificationWithName:@"remove" object:self];
[[NSNotificationCenter defaultCenter] postNotification:n];
//重複すると怖いので一応破棄。多分無くても大丈夫
[[SKPaymentQueue defaultQueue] removeTransactionObserver:mySKObserver];
//実際に走らせるオブザーバーを登録
[[SKPaymentQueue defaultQueue] addTransactionObserver:mySKObserver];
~~~~~~~~~~
~~~~~~~~~~
-(void)viewWillDisappear:(BOOL)animated{
    //使い終わったオブザーバーをここで削除
    [[SKPaymentQueue defaultQueue]removeTransactionObserver:mySKObserver];
    //ノーティフィケーションも削除
    [[NSNotificationCenter defaultCenter]removeObserver:self];
    
    //一応もう一回ダミーを立てるべきだろうか・・・
    //NSNotification *n = [NSNotification notificationWithName:@"remove" object:self];
}

という感じで、ダミーのオブザーバーと実際に課金走らせるオブザーバーを交換して上げる。
取りあえずはこれで動いてる感じ!!
参考サイト様一覧
【なんてこったいブログ】
http://nantekottai.com/2011/10/28/storekit/
【アルデンテ】
http://d.hatena.ne.jp/kozy_twt/20110401/1301680959
【In-App Purchaseプログラミングガイド】
https://developer.apple.com/jp/devcenter/ios/library/documentation/StoreKitGuide.pdf



2012年11月14日水曜日

【iPhone】iPhoneでデバッグ時にだけ走るコード【デバッグ】

今回はiPhoneアプリのお話。(Javaが打ちたい。。。ブログ書く時間ない。。。)

取りあえず私のやらかしたお話・・・
iPhoneアプリのデバッグコード有効のままリリースしちゃった☆

こんなミスを二度と繰り返さない様に、デバッグの時だけ有効になる
コードの書き方を残しておきます。

#define DEBUG 1
とか書いて
if(DEBUG){〜〜}
とか書いていたのですが、この"DEBUG"を書き間違えるというミスを今回侵しました。
なので、ビルドコンフィギュレーション(BuildConfiguration)によって自動で
読み込む部分を変える様にしてみます。

Xcode左側のファイル一覧からProjectを選択
Project->BuildSettings->Apple LLVM compiler 4.1 - Preprocessing










夫々で対応したコンフィグに対する定数を入れて行きます。
自分はデバッグ環境の時のみ1になるようにしております。
これで、#define定義した時と同じ動きをする模様。
しかも、リリースとかデバッグとかアドホックとかのビルド環境によって値を変えられます!

後はコード内で
if(DEBUG){
    //デバッグ時にのみ走らせたいコード
}else{
    //本番時にのみ走らせたいコード
}
という書き方をすればオーケー。

なんか、Xcodeのバージョンによっては最初からDEBUG=1が
設定されている事もあるようです。

序でに、こちらのサイトさんでは、デバッグ構成時にだけ走るLogの書き方が載っておりました。
http://goo.gl/OFDqY


最近Rubyのお勉強始めました。

2012年5月20日日曜日

【Android】Android標準レイアウトを変更してみる【レイアウト】

【ここの部分は読み飛ばしても大丈夫です】

お仕事でTabView使っていたのです。

TabViewって標準でテキストにアニメーションが入ってて、長すぎるテキスト
入れると、文字が小さくならずに、選択時にアニメーションで隠れた文字が
出てくるようになるんですよね。
その為、標準のプロパティの中にはテキストサイズ(Textsize)の変更が存在
していません。

そんな中で言われたのが・・・

お客様「アニメーション要らないからテキストサイズ小さくして」
私「(; ゚д゚)・・・はい。。。」

まぁ、Viewのxml手打ちすれば良いかとか思っていたのですが、現在のレイ
アウトに特に不満を持っているわけでも無さそうだったので、できる限りレイ
アウト弄りたくない!(メンドウだし)。。。
そう思って調べたらありました!



【やっと本題】
さて、今回はTabViewを例として挙げていますが、結構色んなレイアウト変更
することが出来るみたいです。

※それと、これ書きながらテストコード打っていたのですが、自宅のSDKのバー
ジョンが古いようで、会社の時とは挙動が違いました。今回の画像ではタブの
イメージとタブの文字が被ってしまっていますが、最新のSDKでは自動でイメー
ジのサイズが調整されたはずです。余裕あったら後で直します。。。

まずは普通に実装します。

TabTestActivity.java
import android.app.Activity;
import android.app.TabActivity;
import android.content.res.Resources;
import android.os.Bundle;
import android.widget.TabHost;

//継承はTabActivityなのをお忘れなく
public class TabTestActivity extends TabActivity{
 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  //リソースフォルダ
  Resources res = getResources();

  //タブの本体作成
  TabHost tabHost = getTabHost();

  //多分タブ夫々の本体?
  TabHost.TabSpec spec;

  //Tab夫々を作っていきます。今回は3つ////////////////////

  //1個目
  spec = tabHost.newTabSpec("tab1")
    .setIndicator("Tab1test",
      res.getDrawable(R.drawable.ic_launcher))
      .setContent(R.id.Tab1);
  tabHost.addTab(spec);

  //2個目
  spec = tabHost.newTabSpec("tab2")
    .setIndicator("tab2test",
      res.getDrawable(R.drawable.ic_launcher))
      .setContent(R.id.Tab2);
  tabHost.addTab(spec);

  //3個目
  spec = tabHost.newTabSpec("tab3")
    .setIndicator("tab3testてすとてすと",
      res.getDrawable(R.drawable.ic_launcher))
      .setContent(R.id.Tab3);
  tabHost.addTab(spec);

  //Tabが呼び出された時、どこを起動画面にするか選択(0からだよ)
  tabHost.setCurrentTab(0);

  //////////////////////////////////////////////////////


 }
}
main.xml

<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >

        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" >

            <LinearLayout
                android:id="@+id/Tab1"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent" >

                <TextView
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    android:text="Tab1の表示" />
            </LinearLayout>

            <LinearLayout
                android:id="@+id/Tab2"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent" >

                <TextView
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    android:text="Tab2の表示" />
            </LinearLayout>

            <LinearLayout
                android:id="@+id/Tab3"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent" >

                <TextView
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    android:text="Tab3の表示" />
            </LinearLayout>
        </FrameLayout>
    </LinearLayout>

</TabHost>




実装画面
※イメージとテキストの重なりは最新のSDKなら自動で修正してくれるはずです。

 さて、この時のTab3の文字が微妙に枠からはみ出しているのが確認頂けますでしょうか?
 これの修正やっていきます。
 残念ながらTabWidgetのプロパティにはテキストサイズの項目が無いようで、
android:textsize ="10sp"なんてのができません。 そこで、Android標準のレイアウトを変更
する事にします。




まず、style.xml作ります。


中身はこんな感じ

style.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <style name="MyTheme" parent="android:Theme">
        <item name="android:textViewStyle">@style/MyTextView</item>
    </style>

    <style name="MyTextView" parent="android:Widget.TextView">
        <item name="android:textSize">10sp</item>
    </style>

</resources>



この"Theme"ってのを使うと、アプリ内の特定の標準レイアウトだけを変更
できるらしいです。
今回はTabのtextSizeを"10sp"に変更してみます。


styleが書けたので、これをAndroidManifest.xmlに読み込ませます。


AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.kumagai.shift"

    android:versionCode="1"

    android:versionName="1.0" >



    <uses-sdk android:minSdkVersion="7" />



    <application

        android:icon="@drawable/ic_launcher"

        android:label="@string/app_name" >

        <activity

            android:name=".TabTestActivity"

            android:label="@string/app_name"

            android:theme="@style/MyTheme">

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />



                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>

    </application>



</manifest>
<activity>の部分にandroid:theme="@style/MyTheme"を追加しました。



これで起動すると・・・

この様になります。
Tabの文字が小さくなっているのが確認できますでしょうか?
Tab3の文字が見きれていたのが改善されました。

Activity全体に適応されるので下のLinerLayoutのTextViewでも文字が小さく
なってしまっていますが、こちらは個別に変更可能なため、適当に

<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Tab2の表示"
android:textSize="100sp" />



とでもやれば・・・



















でかくなります(笑


Themeで変更可能なものが結構御座います。
どこかに一覧みたいなのあったはずだけど、どこで見つけたか忘れてしまった。。。


参考サイト
  Y.A.M の 雑記帳 詰まった所があり探すと、必ずといって良い程解決策が載ってるサイト様
  Tech Booster ここもよく行き着きますねw





Androidのデザイン関連ですと、この辺りの書籍は目を通しておくと良いかも。。。

2012年3月3日土曜日

【Android】App Engine Connected Android Project を取り敢えず動かす【GAE】

Ecripseを見ていたらApp Engine Connected Android Projectというものを発見(綴り合ってる?)

何かカッコイイ!!

ということで取り敢えず動かしてみる。

GAEの登録終わってない方は終わらせてからこのページをご覧下さい。
まぁ、誰も見てないだろうけど。。。

WinVista
Ecripse3.7 日本語プラグイン
後色々ゴチャゴチャとプラグイン入れてる環境です。



取り敢えず、プロジェクトを作成していきます。

ファイル->新規->その他->Android->App Engine Connected Android Projectを選択。



次に、プロジェクト名とパッケージ名を入れていきます。
自分はプロジェクト名を「GdgdTest」パッケージ名を「com.gdgdtest」と適当に設定







最後にC2DMの設定を行います。
パッケージ名とC2DMに登録した(又はこのあと登録する)メールアドレスとパスワードを入れましょう。Gmailでしか作ってないので他のアドレスで行けるか分からないです。



最後に「完了」を押しましょう。
ちょっと重くなった後にプロジェクトが作成されてます。


AndroidやGAEのプロジェクトは作った初期状態でそのまま実行すれば動くのですが、
これはそのままでは動いてくれない模様。。。
ということで、設定や登録やっていきます。

まず、C2DMに登録されていない方はこちらで登録をしてください。
http://code.google.com/intl/ja/android/c2dm/signup.html

大事なのはパッケージネームとemailのアドレス。
先程プロジェクト作成時に入力したものを使ってください。(この作業先にやるべきだった?)
ぶっちゃけそれ以外は適当でも・・・


さて、またEcripseに戻りまして、
先程プロジェクトを作成したので、AndroidとGAEの2つのプロジェクトができていると思います。
自分の場合は「GdgdTest-Android」、「GdgdTest-AppEngine」の2つが作成されました。





この内のAndroidの方にあるsrc->com.gdgdtest->「Setup.java」を見てみましょう。
確認すべき点は2行です。



ここではAPP_NAMEにGAEのIDを入力しましょう

    private static final String APP_NAME = "【GAEで登録したID(デプロイするURLの一部)】";

    public static final String PROD_URL = "https://" + APP_NAME + ".appspot.com";

こっちでは、C2DMで登録したメールアドレスが入っていることを確認しましょう。
    public static final String SENDER_ID = "○○○○@gmail.com";




次に、GAEのアプリケーションIDの設定を行います。
GAEのプロジェクトを右クリックしてプロパティを選択します。
Google->Appエンジン の「デプロイメント」にある「アプリケーションID」に
GAEで設定してあるアプリケーションIDを割り当てます。(上のAPP_NAMEと同じ)



後はAndroidプロジェクト選択した状態で実行。
GAEの方はデプロイしてやる。


Androidで何か許可求められるので許可してやる。

こんな感じで「Registerd」が通知されれば成功


次にSayHello?押して


こんなんが返ってきたら成功!



序に、ブラウザからGAEのページに行ってみる。
http://【自分のID】.appspot.com/



SayHelloを押すと

こんな感じ



対象のandroidのGmailアドレスとメッセージを入力すると


Androidの方にこんな感じでgdgdTestの通知が送られました。


これで終了。


ブラウザから特定の端末に向けてメッセージ送れるって凄いね。
リモート操作とかも頑張ればできちゃう?

2012年3月2日金曜日

【GAE】頑張って乗せてみたい【Gosen】

GAEでGosen使ってみたいなと適当に模索。

何とかなりそうなのでこれも忘却録として参照サイトのみ書いておく。



GAE用にGosenのプロジェクトを書き換えてくれているサイト
http://www.altus5.co.jp/wordpress/jikken/gaej-gosen-n3/

GosenのdictionaryのXMLの指定URLが繋がらないようなのでその書き換えについて書いてくれてるサイト
http://shinodogg.com/?p=3964




面倒なのでリンクのみ載せておく。
でもリンク切れが怖いからそのうち自分でもまとめるべき?


追記:
lucene-gosenたるものを発見。
jar一つ追加で中に辞書も内包してくれてるお得ライブラリ。
コレで良かったよ・・・


2012年2月22日水曜日

【忘却録】Androidで陥ったエラー文

java.net.UnknownHostException: api.twitter.com

ソース見なおしてもコレに陥って困ってたら、
AndroidMainifestにパーミッション追加してないだけだった。

<uses-permission android:name="android.permission.INTERNET" />
<application  
        うんたらかんたら
/>

これでOK
なんか悔しいから書いておく。

2012年2月19日日曜日

【Android】Eclipseの初期設定

Android開発の前にEclipseの初期設定

Eclipseは初期状態だと文字のエンコードが「MS932」(Shift-JIS)です。

この状態だとコード中に日本語が混じると正しく表示されなかったり、

ビルドできなくなるなどの問題が生じる可能性があります。

なので、取り敢えず「UTF-8」に変更しておきましょう。

Eclipseの「Window」→「Preference」、左側の「General」→「workspace」そこの下にある

「Text file encoding」を「Default」から「Other : UTF-8」に変更しましょう。


序にJavaコンパイラの文法チェックのレベルの変更も書いとく

AndoroidではJava1.5レベル以上の環境でのチェックが必要

初期設定のままだとJava1.4のハズ。なのでそれの変更

左側の「Java」→「Complier」その欄の「Compiler Compilance level」を1.6とか高い奴に設定

最後に「Applay」→「OK」。

やんなくても動いてたけど忘却録として一応書いとく。