Eclipseの最近のブログ記事

Android アプリから、GAE 開発サーバーのアカウントを利用する

| コメント(0) | トラックバック(0)

と、Android 端末のGoogle アカウントを用いて、GAEアプリケーションを利用する準備を徐々に進めてきていたのだが、GAEの開発環境でのやり方が分からなかったため、とりあえず実際にアプリケーションをリリースして試していた。

が、何とかいける方法が見つかったので、メモ。

通常PCから、GAEのアプリケーションをログインしないと使えないようにすると、以下の様なログイン画面にナビゲートされる。

gae_auth04 

ただ、Android 端末ではGoogle アカウントを管理しているので、アプリケーションがログインを必要とするGAEアプリケーションを利用する場合、以下の様な画面を表示させて、事前に認証を行っておくことが出来る。その手順は、前回実現した。

android_gae03

が、同じ手順で、ホスト名のみ、開発用のGAE の Webサーバーにしてもそのままでは動作しない。

まぁあくまで開発環境なので、認証周りまで完全にエミュレート出来ているわけでは無いのだろう。

開発用のサーバーでは、以下の様なログイン画面が表示され、何でもいいのでメールアドレスを入力してログインすると、目的のアプリケーションに遷移するようになっている。

gae_auth02

ということは、まぁ Cookie になにがしかの値を埋め込んでいるのだろうと言うことで、Firebug でのぞいてみると・・・

gae_auth01

こんなエントリーが。なんか、後半の数値の部分は動的に生成しているのかな?と思って、検索してみると数件そのままの値がヒット。

dev_appserver_login=test@example.com:False:185804764220139124118

なんか、固定値みたいなので、そのまま Cookie にセットして送信するコードを書いて、、、

String uri   = null;
String acsid = null;
HttpGet httpGet = null;
HttpResponse response = null;
if (Debug.isDebuggerConnected()) {
    // デバッグ時のコード
    uri   = "http://192.168.10.119:8080/cardroid";
    acsid = "dev_appserver_login=\"test@example.com:False:185804764220139124118\"";
} else {
    // リリース時のコード以下参照
    // http://typea.info/blg/glob/2010/08/android-google-gae.html 
}
HttpPost httpPost = new HttpPost(uri);
httpPost.setHeader("Cookie", acsid);

response = client.execute(httpPost);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
    String l = null;
    while((l = reader.readLine()) != null) {
    Log.i("MyApp",l);
}

実行してみると、めでたく、ログイン状態となったようだ。

gae_auth03

これで、開発環境で、GAEアプリをテスト出来そうだ。

 

以上。

Android ArrayAdapter に ラジオボタンを置く

| コメント(0) | トラックバック(0)

arrayadapter_with_radio

Android だと、リストビューから一件選択するのに、ラジオボタンが表示されることがよくある。

確かに、上記のようなダイアログを作ってて、対象が1件だけだと、ぱっと見メッセージと区別がつかないのでどうしていいか分からないが、ラジオボタンがあると「選択してね」感がかもしだされる。

ラジオボタンを使ってみる

で、ラジオボタンを配置しようと、単純にラジオボタンを含むレイアウト(R.id.rdo_select_item)を作成して動かしてみると・・・

ArrayAdapter<String> adapter 
    = new ArrayAdapter(context,
                               R.layout.row_items, 
                               R.id.rdo_select_item, 
                               items);

きちんと画面には表示されるが、リストビューの setOnItemClickListener に 登録したリスナーでリストの選択がどうもハンドリング出来ない。

android.R.layout.simple_spinner_dropdown_item を使ってみる

と、まぁ表示はされるのだが、文字色が黒だったり、どうもしっくりこない。

あと、選択したらきちんと動くのだが、ラジオボタンはフェイクのようで、選択状態の見た目にはならない。

ArrayAdapter adapter 
    = new ArrayAdapter(context,
                               android.R.layout.simple_spinner_dropdown_item, 
                               items);

TextView の代わりに CheckTextView を使いつつ、listChoiceIndicatorSingle を指定してみる

ArrayAdapter の使い方は、通常のTextView を単純に使用するのと同じにしておいて、

ArrayAdapter adapter 
    = new ArrayAdapter(context,
                               R.layout.row_items, 
                               R.id.txt_select_item, 
                               items);

レイアウトの方で、TextView の代わりに、CheckedTextView を使う。

そして、checkMark 属性に、android.R.attr.listChoiceIndicatorSingle を指定する。

<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android" 
          android:id="@+id/txt_select_item" 
          android:layout_width="fill_parent" 
          android:layout_height="fill_parent" 
          android:textSize="20sp" 
          android:checkMark="?android:attr/listChoiceIndicatorSingle">
</CheckedTextView>

これで、見た目的には、希望通りになった。

ただ、android.R.layout.simple_spinner_dropdown_item 同様、ラジオボタンはフェイクだけど・・・ まぁいいや。

これに限らず、android.R には、こねたがいっぱいつまってそうなので、いずれ詳しくみてみたい。

また一歩野望に近づいた。

Android(X06HT Desire) Activity の状態を一時的に保存する。Activity ライフサイクルの確認。

| コメント(0) | トラックバック(0)

先日の勉強会で発表のために、Android アプリケーションの開発から公開までしたのだけれど、なにぶん知識と時間不足のため、ちょっと完成度低めだったので、少しずつ改良しながら、さらに野望の実現を目指す。

作成したのは、単語帳アプリケーションなのだが、単語帳のカードを途中までめくったところで端末の方向を切り替えると、最初のページに戻ってしまう。これはいただけない。修正する。

プライベートなプリファレンスをつかって、現在のページを保持させれば良いだろう。

getPreferences(MODE_PRIVATE) で、暗黙で Activity の クラス名をキーとしてgetSharedPreferencesを利用できる

プライベートなプリファレンスを使う例

 @Override
 protected void onPause() {
     Editor editor = getPreferences(MODE_PRIVATE).edit();
     // 現在ページを保存
      editor.putInt(PREF_KEY_CURRENT_PRACTICE, vfCard.getDisplayedChild());
     editor.commit();
     super.onPause();
 }
 @Override
 protected void onResume() {
     int idx = getPreferences(MODE_PRIVATE).getInt(PREF_KEY_CURRENT_PRACTICE, 0);
     // 現在ページを復帰
     if (0 <= idx && idx < vfCard.getChildCount()) {
         vfCard.setDisplayedChild(idx);
     }
     super.onResume();
 }

 

・・・ が、これがうまくいかない。

Activity のライフサイクルイベントのメソッドをオーバーライドして、ログを仕込んで、なにが起こっているか確認してみる

まずは、アプリケーションを起動し、対象の Activity を呼び出すと、

android_activity01 

09-02 23:32:43.878: INFO/MyApp(11021): onCreate()
09-02 23:32:44.668: INFO/MyApp(11021): onStart()
09-02 23:32:44.668: INFO/MyApp(11021): onResume()

onCreate から、onStart()、onResume() を経てActivity 実行中に。ふんふん。リファレンス通り。

 activity01

そして、画面を横にしてみる。

android_activity02

09-02 23:33:05.348: INFO/MyApp(11021): onPause()
09-02 23:33:05.348: INFO/MyApp(11021): onStop()
09-02 23:33:05.348: INFO/MyApp(11021): onDestroy()
09-02 23:33:05.428: INFO/MyApp(11021): onCreate()
09-02 23:33:06.358: INFO/MyApp(11021): onStart()
09-02 23:33:06.368: INFO/MyApp(11021): onResume()

activity02

おおっと、画面を横にした程度なら、onPause() から onResume() のコースを取ってくれても良さそうなものだが、一旦 Destroy までされてしまうようだ。

画面の縦と横では内容が全く異なるはずなので、画面自体の再構築の必要がある。当然と言えば当然か。

毎回 onCreate() も呼ばれてしまうとなると、onCreate() で初期化しようにも、Activity を新規に構築するのか、画面の向きが変わったのか判断つかない。 となると、Acitvity を呼び出すタイミングで、値をクリアする操作をしないといけない。 この場合、Activity プライベートなプリファレンスは使えないので、Activity 間で共有できるプリファレンスを使って書き換えてみる。

共有プリファレンスを使う例

 @Override
 protected void onPause() {
     Editor editor = getSharedPreferences(
                     PREF_KEY, 
                     MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE).edit();
     editor.putInt(PREF_KEY_CURRENT_PRACTICE, vfCard.getDisplayedChild());
     editor.commit();
     super.onPause();
 }

 @Override
 protected void onResume() {
     int idx = getSharedPreferences(PREF_KEY, MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE)
                   .getInt(PREF_KEY_CURRENT_PRACTICE, 0);
     if (0 <= idx && idx < vfCard.getChildCount()) {
         vfCard.setDisplayedChild(idx);
     }
     super.onResume();
 }

で、呼び出し元の Activity では、上記 onPause と同様にして、putInt() を利用して、 0 (初期値) をセットすれば、期待したように動くようになった。

よしよし。

 

・・・ が、ちょっとまった。プリファレンスは永続化される。 しかも自分が想定した利用法では、永続化する必要は全くない。たかだか Activity の一時的な状態を格納するだけに使うのはもったいない(?)のではないか。

 

Application の サブクラスを作成してそこに保持させる

ようなことを、ちらっとどこかで見たな~2chだったかな~

Activity は 破棄されても、Application は、基本生きているはず。ただ、システムによって、メモリが足りないときとかはKill されるということだったかと。リファレンス見てもそういう感じだ。

なので、Application が 不用意に Kill されても 保持していたいような設定などは、上記プリファレンスを使うことによって保持(永続化)し、まぁ使っている間だけ覚えていればいいや的な状態は、Application のメンバーとして持たせることにする。

Application のサブクラスを作成するのは、単純に、android.app.Application を継承したクラスを作成する。

ただし、それだけでは、そのアプリケーションクラスがメインのアプリケーションだと認識されていないので、AndroidManifest.xml に記述を追記してあげる必要がある。application の android:name に自作クラスを設定する。

<application android:name="CardroidApplication" 
                android:icon="@drawable/icon" 
                android:label="@string/app_name" 
            : 

もしくは、AndroidManifest.xml の設定画面、Application タブから、Name を設定する。

android_application

また一歩野望に近づいた。

Android デバッグ中か否かを判定する

| コメント(0) | トラックバック(0)

Android アプリから、GAE の認証を行うことは出来たのだが、GAE の SDKに含まれる開発環境にうまく接続することが出来ない。

仕方がないので、デバッグ中か否かを判定して、デバッグ中の場合は、接続先のURLをローカルアドレスに変更し、認証プロセスをスキップさせようと思いそれらしいAPIを探してみる。

android.os.Debug.isDebuggerConnected()

あった。

・・・ が、試せど試せど 上記の結果が true にならない。いらいらいらいら。

しらべてたら、SDKにこんな記述が。

Declare your application as debuggable in your manifest

マニフェストファイルに書けばいいのね。ふーん。

で、見てみると、ありました、Debuggable = true に設定。

android_debugable01

LogCat に出力するとキャプチャがとれないし、false の場合確認が出来ないので、Toast に表示させるべく、メイン Activity の onCreate に以下を記述。

(Toast.makeText(this, 
    String.valueOf("Debug.isDebuggerConnected() = " + Debug.isDebuggerConnected()), 
    Toast.LENGTH_LONG)).show();

(が、Toast にしてもキャプチャはとれないことに後で気づく)

起動してみると、以下の様なダイアログが。ちゃんと機能してるっぽい。あたりまえか。

 android_debugable02

そして、きちんと Toast に true が表示されました。

もちろん USB デバッグを停止して実行すると false が返りました。これでデバッグ時と通常時で挙動が切り替えられます。一歩野望に近づきました。

android_debugable03

Eclipse のプラグインからもいろいろ操作ができるようになりました。

android_debugable04   

リリースするときには false に戻した方が良さそうです。

勉強会が成功裏に終了

| コメント(0) | トラックバック(0)

内輪(といっても、知人の知人ぐらいまでの広がりで勤務先や職業は基本ばらばらの10人弱程度)での勉強会3回目が昨日無事終了しました。

年末、GW、夏期 の長期休暇を基本開催時期に設定しているので、これで1セット終了したことになります。

関係者の皆様お疲れ様でした。

昨日の内容は、財務会計から、Python、アイディア出しのワーク、そして古事記と非常にバラエティーに富んでいるとともに、個人的にもとても興味があるテーマ、内容で非常に楽しめたし勉強になりました。ありがとうございました。

プロの講師の方や、まるで漫談師の様なしゃべくりのレベルの方にまじって、2回目の勉強会で、スマートフォン開発について につづいて拙いながらもこの2ヶ月くらいAndroidについて調べたこと、開発から公開の流れまでを発表させていただきました。

こういうプレゼン的なのは基本的にてんぱり気味になってしまい、苦手なのですが、エクササイズだと思ってやらせていただいています。優しい目で見守ってください。

 

で、今回の発表用に足早に開発~公開をしたアプリ。自分の中ではリファレンス実装(?)的な感じになってます。息子は喜んで使ってくれてます。

単語ロイド

Android アプリから Google アカウントを利用して GAE アプリケーションを利用する

| コメント(0) | トラックバック(0)

Android と GAE を同時に Ecliipse からデバッグする環境は出来たので、Google アカウントを Android アプリケーションから利用してみる。

GAE アプリケーション側でログイン状態を判定

android_gae_07

typea_android_apps.py

/cardroid というリクエストが来たら、cardroid パッケージ cardroid_request_handler モジュールの InitialCardroidPage クラスを呼び出すように設定

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

from cardroid import cardroid_request_handler as cardroid_handler

class MainPage(webapp.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('Hello, webapp World!')

application = webapp.WSGIApplication([
                                      ('/', MainPage), 
                                      ('/cardroid', cardroid_handler.InitialCardroidPage),
                                     ], debug=True)
def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

cardroid_request_handler.py

ユーザーがログイン状態であれば、ユーザーのニックネームとログアウトのリンク を返し、そうでなければ、ログインページへのリンクを返すようにする。

from google.appengine.ext import webapp
from google.appengine.api import users

class InitialCardroidPage(webapp.RequestHandler):
    def get(self):
        self.post()
        
    def post(self):
        user = users.get_current_user()
        if user:
            user_logout_url = users.create_logout_url("/")
            return self.response.out.write("<html>%s <a href="%s">logout</a>" % (user.nickname(), user_logout_url));
        else:
            user_login_url  = users.create_login_url('/cardroid')
            return self.response.out.write("<html><a href="%s">login</a>" % (user_login_url));

PCのブラウザから確認

未ログイン状態の場合、ログインのリンクを表示

android_gae_08

ログインする

android_gae_09

ユーザーのニックネームとログアウトのリンクを表示

 android_gae_10

Android アプリケーションから Google アカウントを利用して GAE アプリケーションを呼び出す。

このあたりを参考に。


GaeUtil クラス

とりあえず、GAE 関係で利用するクラスをまとめるために作成。Static クラスの便利さに遅ればせながら気がついたので、使いまくり。

特に意味は無い。

PracticeUploader

名前は、自分が作っているアプリにちなんでいるだけでこちらも意味は無い。upload() メソッドの中からが本筋。

AccountManager

getAccounts() メソッドを利用すると、Android 端末で管理しているアカウント情報が取得できるようだ。

android_gae06

これらね。

で、それぞれのアカウントは type 情報を持っていて、getAccountsByType("com.google")  で、Google アカウントの情報がとれてくる。

とりあえず、添え字=0 の値を利用しているが、マルチアカウントだと複数件とれてくるのかな?

その場合、選択させる仕組みも必要かも。

で、getAuthToken() メソッドを利用して、認証用のトークンを取得。

取得したアカウントオブジェクトと、"ah" と コールバック用のインスタンスを渡す。

Google アカウントは "ah"  のようだ。認証用のトークンが取得できたら、渡したインスタンスがコールバックされる。

UploadPracticeCallback

このクラスも名前は意味がない(というか自分向け)、上記で参照したコード等は、非同期で処理を行ったりしているが、まずは挙動確認のため、べた書きする。

どうやら、認証が初回等必要な場合は、Intent intent = (Intent)bundle.get(AccountManager.KEY_INTENT);  で Intent が取得できるようなので、startActivity してあげると、認証を確認する画面が表示される。

android_gae03

認証されていれば、String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN); で、認証トークンが取得できる。

Cookie の保持

  認証トークンを含めて、GAE アプリケーションへ 以下の感じでGET リクエストを投げる。

https://{ GAEアプリケーションURL }/_ah/login?continue={ 認証後に遷移するURL }&auth={ 認証トークン }

http か、https かによって、Cookie に "ACSID" または、"SACSID" のキーが設定されてくるので、保持しておく。

 

実際のリクエスト

これで、実際にやりたいリクエストを投げる準備が整った。

上記で保持した Cookie を付加して、POST メソッドを投げる。

ログイン状態になっていれば、ニックネームが正しく LogCat にはかれるはず。

 

問題点

以下、GAE アプリケーションを、リリースして確認した。開発環境でデバッグしたい のだが、現時点ではどうしていいのかわからん。

 ==> 2010/09/08 解決(?) しました!

android_gae_11

お、来ました。とりあえず、成功!!

開発環境でどうやるかは、今後の課題。

デバッグモードとリリースモードでコードの切り替えが必要になったりするのかなぁ。

package info.typea.cardroid;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

public class GaeUtil {
    /**
     * 
     */
    public static class PracticeUploader {
        private Context context;
        public PracticeUploader(Context context) {
            this.context = context;
        }
        public void upload() {
            AccountManager accountManager = AccountManager.get(context);
            Account[] accounts = accountManager.getAccountsByType("com.google");
            
            if (accounts.length >= 0) {
                Account account = accounts[0];
                accountManager.getAuthToken(account, 
                                            "ah", 
                                            false, 
                                            new UploadPracticeCallback(this.context), 
                                            null);
            }
        }
    }
    
    /**
     *
     */
    public static class UploadPracticeCallback implements AccountManagerCallback {
        private final String GAE_APP_URI = "http://typea-android-apps.appspot.com";
        
        private Context context;
        public UploadPracticeCallback(Context context) {
            this.context = context;
        }
        @Override
        public void run(AccountManagerFuture result) {
            Bundle bundle;
            try {
                bundle = result.getResult();
                Intent intent = (Intent)bundle.get(AccountManager.KEY_INTENT);
                if (intent != null) {
                    this.context.startActivity(intent);
                } else {
                    String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);

                    DefaultHttpClient client = new DefaultHttpClient();
                    client.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);

                    String uri = GAE_APP_URI + "/_ah/login?continue=/cardroid&auth=" + authToken;
                    
                    HttpGet httpGet = new HttpGet(uri);
                    HttpResponse response = client.execute(httpGet);
                    String acsid = null;
                    for (Cookie cookie : client.getCookieStore().getCookies()) {
                        if ("SACSID".equals(cookie.getName()) ||
                                "ACSID".equals(cookie.getName())) {
                            acsid = cookie.getName() + "=" + cookie.getValue();
                        }
                    }
                    
                    uri = GAE_APP_URI + "/cardroid";
                    
                    HttpPost httpPost = new HttpPost(uri);
                    httpPost.setHeader("Cookie", acsid);
                    
                    response = client.execute(httpPost);
                    InputStream in = response.getEntity().getContent();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                        String l = null;
                        while((l = reader.readLine()) != null) {
                            Log.i("MyApp",l);
                    }
                }
            } catch (OperationCanceledException e) {
                showMessage(R.string.lbl_operation_canceled);
            } catch (AuthenticatorException e) {
                showMessage(R.string.lbl_authenticator_failed);
            } catch (IOException e) {
                showMessage(R.string.lbl_error);
            }
        }
        /**
         * @param resId
         */
        private void showMessage(int resId) {
            (Toast.makeText(this.context,resId, Toast.LENGTH_LONG)).show();
        }
    }
}
  

以上。

Android (実機) と GAE を連携させるためのデバッグ環境を Windows7 に構築する

| コメント(0) | トラックバック(0)

Android アプリの開発の概要はつかめてきたので、Google App Engine と連携させるべく、Eclipse の開発環境を整える。

といっても、Eclipse から PyDev で GAE をデバッグしながら、USB接続した Android 実機(X06HT) からGAE アプリにリクエストを投げられる環境を作るだけだが。

Windows ファイアーウォールの設定

まず、別のホストから、GAE の開発サーバーにアクセスする必要があるので、8080ポートを開放する必要がある。

ポートは8080以外にも設定可能だが、GAEの開発サーバーのデフォルトに合わせる。

コントロールパネルからシステムとセキュリティ、Windows ファイアーウォールの状態の確認を開く

android_gae01

android_gae02

左側のペインから詳細設定を開く

android_gae03

Windows ファイアーウォールの設定画面が立ち上がるので、受信の規則 を左側ペインで選択し、右側の操作から、新しい規則を選択する。

 android_gae04

ポートを指定

android_gae05

特定のローカルポート 8080 を指定

android_gae06

接続を許可する

android_gae07

任意の名前をつける

android_gae08

作成された

android_gae09

Eclipse 側、PyDev アプリの作成

まず、PyDev の新規プロジェクトを作成する。

PyDev については、こここのあたりを 参照。

プロジェクトを起動して、アプリケーションの出力が表示されることを確認

android_gae10

GAE 開発用サーバーの設定

GAE 開発用サーバーと アドレスをバインドする

デフォルトでは、GAE の開発用サーバーは localhost しかリッスンしていない。

これを、Windows 7 マシンの IPアドレスにバインドする。

プロジェクトの src フォルダのコンテキストメニューから、 Debug As - Debug Configurations を選択

android_gae11

GAE の開発用サーバーの引数に -address [IPアドレス] を指定してやる

Python 開発用サーバー のヘルプhttp://code.google.com/intl/ja/appengine/docs/python/tools/devserver.html

Argument タブに、-address IPアドレス の記述を追加する

android_gae12

Debug ボタンを押下すると開発用サーバーが起動する

通常は、http://127.0.0.1:8080 とログがはかれるが、指定したIPアドレスとバインドされている。

android_gae13

WiFi で自宅のネットワークから、Windows PCのIPアドレスの8080ポートに接続すると、GAE アプリケーションが表示される

 android_gae14

成功!

以上。

Android アプリに広告を表示させる AdMob アカウント登録

| コメント(0) | トラックバック(0)

最近、無料アプリケーションのアップデートを Android マーケットで行うと、もれなく広告がついてくるようになりました。

画面が狭くなって使う側としてはあまりうれしくはありませんが、おそらく今後(今も?)アプリケーションを販売することは困難になると思われるので、無料版に広告がつくのが自然の流れになった方が、開発していこうとする側としては望ましいかなと思います。

で、広告をアプリケーションに表示すべく、AdMob のアカウントを登録してみようと思う。

AdMob サイトにアクセス

http://jp.admob.com/

右上の 登録 リンクをクリック

admob01 

アカウント情報を登録

admob02

登録を行うと、確認メールが送信される。

送信されためーるのURLをクリック。

admob03

アカウントが有効化された!

右の「最初のサイトを追加する」をクリック

admob04

アプリケーションタイプの選択

Androidアプリケーションを選択する。

admob05

アプリケーション情報の登録

まだ、公開していないアプリケーションでも、下例のとおり、パッケージ名を指定すれば登録出来る。

market://details?id=<packagename>

admob12

SDKのダウンロード

AdMob Android SDK をダウンロードして、アプリケーションに組み込む必要がある。

手順は、すぐしたにリンクされている PDF にまとめられているが、若干手数がかかる。

こちらにSDKの利用法を簡単にメモ

admob06

広告の収益情報を確認できる様になった

admob07

支払情報の設定

支払を受けるためには、連絡先情報を登録する必要がある

admob08 

admob09 

パブリッシャーID の取得

パブリッシャーID を アプリケーションに組み込む必要があるが、上記で追加したアプリケーションの「設定を管理」をクリックすると、アプリケーション画面にパブリッシャーIDが表示されている。

admob10 

admob11

以上で、AdMob への登録は完了。

アプリケーションへの組込については、こちら にまとめ。

Android マーケット に開発者登録を行う手順

| コメント(0) | トラックバック(0)

X06HT は、期待通りの結末を迎えて一安心。

最初の Android アプリも完成したので、公開するため、Android Market に登録する。

まず、Android マーケット のページ にアクセスする。ページの下の方に、以下の文言があるので、here リンクにアクセス。

If you are a developer, learn about publishing your application here.

ユーザー登録

ユーザー登録画面で必要事項を入力し、次へ。

android_developer01

Google Checkout アカウントの作成

登録料、$25 を支払う必要があるが、支払には Google Checkout のアカウントを作成する必要があるようだ。

ここの画面から登録し、完了後この画面の続きへ戻ってくることができる。

android_developer02

Google Checkout へ、クレジットカード情報を登録。

android_developer03

登録料 $25 を支払う

現在のレートで 2,000円 ちょっと。ご存じのとおり、この登録料は格安。

iPhone は、年間 $99 だし、Windows Phone なら 年間 $99 + アプリ一本あたり $99 だったかと。

android_developer04 

android_developer05

Android マーケット への登録承認

ここまでで、Android マーケットへの登録が完了。

android_developer06

下の方にある、商用利用登録を行うと、販売が行える様になるようだ。

無料で公開を想定しているが、商用利用登録は難なく行うことが出来た。

android_developer11

銀行口座情報の登録が必要のようだ。今度登録しよう。

android_developer12 

Android アプリをアップロードできる様に

android_developer07

開発者登録を行うと、Android Dev Phone 2が購入可能

android_developer08

android_developer09

裏面がちょっとかわいい。

android_developer10

以上。

Android (X06HT Desire) 入力ダイアログ、確認ダイアログを共通化。

| コメント(0) | トラックバック(0)

今日は、X06HTが販売終了するって話が飛び交っている。

有機ELの供給不足でX06HT自体を安定供給できないための一時販売休止だと思いたい。公式発表を待つ。

販売終了でも、Softbank から、Android 携帯が発売されれば、まぁ良とするが、されなかったら至極残念だ。

 

まぁいいや。

Android では、ダイアログボックスを使うのに、JavaScript の alert や confirm みたいに気軽に使えるといいのだけど、どうも用意されていないみたい。使い回したいので共通化しときたいが、どうしたらいいかな~と。

手元の本 にも、そこまでのことは書いてない。

 

一晩考えて、使えそうな形になったので、メモ

共通で作成するダイアログの種類

  • alert ・・・ OK ボタンのみ。警告
  • confirm ・・・ OK 、Cancel 確認
  • inputBox ・・・ 入力エリア、OK、Cancel

の3つをアプリケーションのどこからでも使えるように、android.app.Application クラスを継承して、static メソッドとしてそれぞれ作成する。

問題点

alert は、単に OK ボタンがおされたら閉じるだけなので、特に問題ない。

問題は、残りの2つダイアログでのユーザー入力をどうやって呼び出し元に返すか。

 

JavaScript 等のように結果を Boolean や String で単純に受け取るわけには、いかなそうだ。

EventLister( OnClickListener ) を利用方法に合わせて個別に作成する方法をとってみる。もっと良い方法があれば知りたいが。

対応

confirm については、AlertDialog.Builder を利用するため、ClickListener にダイアログの参照が渡ってくるので、これをつかってダイアログを消すことができる。

inputBox については、UI を XML で定義することにしたので、View.OnClickListener でイベントを取る必要があるが、これだと、ボタン押下時にダイアログを消すことができない。

苦肉の策として、View.OnClickListener を 実装した static 抽象クラス InputBoxOnClickListener  を アプリケーションクラスにもたせ、inputBox メソッドの中でダイアログを生成した後、参照をセットするようにしてみた。

 

 package info.typea.myapp;
 
 import android.app.AlertDialog;
 import android.app.Application;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.view.View;
 import android.widget.Button;
 import android.widget.EditText;
 
 public class MyApplication extends Application {
 
     /**
      * Application 共通警告ダイアログ
      * @param titleId
      * @param msg
      */
     public static void alert(Context context, int titleId, String msg) {
         AlertDialog.Builder b = new AlertDialog.Builder(context);
         b.setTitle(titleId);
         b.setIcon(android.R.drawable.ic_dialog_alert);
         b.setMessage(msg);
         b.setPositiveButton("OK", new DialogInterface.OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
                 dialog.dismiss();
             }
         });
         b.show();
     }
 
     /**
      * Application 共通 確認ダイアログ
      * @param titleId
      * @param msgId
      */
     public static void confirm(Context context, int titleId, int msgId, 
                                 DialogInterface.OnClickListener lisner) {
         AlertDialog.Builder b = new AlertDialog.Builder(context);
         b.setTitle(titleId);
         b.setIcon(android.R.drawable.ic_dialog_info);
         b.setMessage(msgId);
         b.setPositiveButton("OK", lisner);
         b.setNegativeButton("Cancel", lisner);
         b.show();
     }
     
     /**
      * Application 共通 入力ダイアログ
      * @param context
      * @param msg
      * @param lsnr
      */
     public static void inputBox(Context context, String title, 
                                 String contents, InputBoxOnClickListener lsnr ) {
         
         Dialog dialog = new Dialog(context);
         dialog.setContentView(R.layout.input_dialog);
         dialog.setTitle(title);
         
         EditText txtContent = (EditText) dialog.findViewById(R.id.txt_contents);
         txtContent.setText(contents);
         
         Button btnOk     = (Button) dialog.findViewById(R.id.btn_ok);
         Button btnCancel = (Button) dialog.findViewById(R.id.btn_cancel);
         
         lsnr.setDialog(dialog);
         
         btnOk.setOnClickListener(lsnr);
         btnCancel.setOnClickListener(lsnr);
         
         dialog.show();        
     }
     
     /**
      * Application 共通入力ダイアログ 用 Click イベントリスナー
      */
     public static abstract class InputBoxOnClickListener implements View.OnClickListener {
         // ダイアログを消すためにダイアログの参照を保持できるように
         protected Dialog dialog;
         public void setDialog(Dialog dialog) {
             this.dialog = dialog;
         }
         public void dismiss() {
             if (this.dialog != null) {
                 this.dialog.dismiss();
             }
         }
     }
 }

inputBox の 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"
     android:gravity="center">
    <EditText android:text="" android:id="@+id/txt_contents" 
                              android:layout_width="wrap_content" 
                              android:layout_height="wrap_content" 
                              android:minWidth="200sp" 
                              android:scrollbars="vertical" 
                              android:maxLines="3">
    </EditText>
    <LinearLayout 
        android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <ImageView android:id="@+id/img_icon" 
                   android:layout_width="wrap_content" 
                   android:layout_height="wrap_content">
        </ImageView>
        <TextView android:text="" android:id="@+id/txt_msg" 
                 android:layout_width="wrap_content" 
                 android:layout_height="wrap_content">
        </TextView>
    </LinearLayout>
    <LinearLayout 
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" android:gravity="center">
        <Button android:text="@string/lbl_btn_ok" 
                android:id="@+id/btn_ok" 
                android:layout_height="wrap_content" 
                android:minWidth="80sp" 
                android:layout_width="wrap_content">
        </Button>   
        <Button android:text="@string/lbl_btn_cancel" 
                android:id="@+id/btn_cancel" 
                android:layout_height="wrap_content" 
                android:minWidth="80sp" 
                android:layout_width="wrap_content">
        </Button>
    </LinearLayout>
 </LinearLayout>

呼び出し方

ここまで、準備すれば、あとは好きなように Listener を 実装して、Click イベントをハンドリングすればいい。

例えば、confirm を利用して、ファイルの削除確認をしたいなら以下の様なファイル削除用のリスナーを作成して、

 class FileDeleteConfirmDialogOnClickListener implements DialogInterface.OnClickListener {
    private HogeInfo hogeInfo;  // 何かしらダイアログで操作したい情報をリスナーに詰めておく
    public FileDeleteConfirmDialogOnClickListener(HogeInfo hogeInfo) {
        super();
        this.hogeInfo = hogeInfo; 
    }
    @Override
    public void onClick(DialogInterface dialog, int which) {
        if (DialogInterface.BUTTON1 == which) {
            // OK が押されたらファイルを削除する処理をさせる
            File f = new File(hogeInfo.getPath());
            if (f.isFile()) {
                f.delete();
            }
        } else if 
            (DialogInterface.BUTTON2 == which) {
            // NOP
        }
        dialog.dismiss();
    }
 }

こんな、感じで呼び出せばいい。

 MyApplication.confirm(this, R.string.lbl_confirm_dialog_title, R.string.confirm_delete_file, 
     new FileDeleteConfirmDialogOnClickListener(hogeInfo));

inputBox を利用して、新規で作るファイル名を指定させたいなら、

 class FileNameDialogOnClickListener extends MyApplication.InputBoxOnClickListener {
     @Override
     public void onClick(View v) {
         if (v.getId() == R.id.btn_ok) {
         
             EditText txtContent = (EditText) dialog.findViewById(R.id.txt_contents);
             // 入力された値はファイル名
             String filename = txtContent.getText().toString();
             try {
                 File f = new File(filename);
                 f.createNewFile();
             } catch(Exception e) {
                 ((ImageView) dialog.findViewById(R.id.img_icon))
                     .setImageResource(android.R.drawable.ic_dialog_alert);
                 ((TextView) dialog.findViewById(R.id.txt_msg))
                     .setText(e.toString());
                 return;
             }
         }
         this.dialog.dismiss();
     }
 }

こんな感じの専用リスナーを作成して、

 Date d = new Date();
 String msg = String.format("card_%tY%tm%td%tH%tM",d,d,d,d,d);
 String title = "filenam?";
 MyApplication.inputBox(this, title, msg, new FileNameDialogOnClickListener());

こんな感じで呼び出せばいい。

device01 device02

いじょ。



このアーカイブについて

このページには、過去に書かれたブログ記事のうちEclipseカテゴリに属しているものが含まれています。

前のカテゴリはDjangoです。

次のカテゴリはGoogleAppEngineです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

OpenID対応しています OpenIDについて
Powered by Movable Type 5.02