ラベル 備忘録_Android の投稿を表示しています。 すべての投稿を表示
ラベル 備忘録_Android の投稿を表示しています。 すべての投稿を表示

2012年2月20日月曜日

ADK ハッカソンに行ってきた #adk_hack

ADKハッカソンに行って来ました。

昨年の5月くらいでしたっけ?ADKが出てきたの。
当時から触ろう、触ってみよう、触れたらいいなぁ…
とか思って会社のHT-03Aを2.3.4に上げた記憶があります。

まあ今日まで触ることなかったわけですが。


サンプル一通り触ってそれなりに楽しめたのでよかったです。
ある意味その時点で目的は達成しました(ぉぃ
主催者の @itog さんのところに標準のサンプルを見やすくしたものが置いてあります。
http://d.hatena.ne.jp/itog/20110701/1309487325

本日一番頑張った奴
https://plus.google.com/113699421589247328057/posts/9QQuoHQcBcn

え、ハッカソンの結果?
間に合わなかったんだよ、イワセンナ(*^Д^*)ハズカシイ

なので俺個人としてはLED光らせてAnimationSet弄って終わりましたw

未完のそのアプリの姿はこちら。




あれだね、デザイナーさんって素晴らしいね。
画面だけ見れば、いかにも出来ている感満載じゃねーかw

えぇ、まあ本来はジョイスティックでドロイド君がゴールに向かう予定でした。
アプリ名称の通りダルマさんが転んだなので

  • 3つ有るLEDを信号機のように使用する
  • 3つ目(赤)点灯時にドロイド君が動いている(ジョイスティック押下状態でない)場合NG
  • NGにならずにゴールに向かう
  • LED点灯タイミングは指定範囲内でランダム

とまあそんな予定でしたがマージが間に合わずにできませんでした、テヘヘ

とかいう発表だったんですが、今思えば
「上記のどこがゴールなのか」
という疑問があります。

うむ、仕様すら決めきれてなかったかw

ついでにいうと2回くらい「あれ、これって仕様と違くない?」って @tmk_beta さんに突っ込まれてたのはこちらのアカウントになります。







ということで完成させるのに強力な味方が増えました。
仕様追加は @kerukerupappa さんまで!

って、あれ、それこれじゃないよね?
とりあえず今度 @kerukerupappa さんにお会いした時に考えます。

まさかのヨチヨチロボ部?w

もしくはおもかげるラジコン部に食いつかせるというのもあるなぁ…

2012年2月19日日曜日

#StartupGroovy に行ってきた

件名の通り昨日行って行きました



らしいです。つまり俺のStartupGroovyは終わっていない(違

と、言いましても既に多くの方がまとめられていますし、詳細はそちらとかを

http://d.hatena.ne.jp/absj31/20120218/1329636558
http://d.hatena.ne.jp/grimrose/20120219/1329619718
http://tomykaira.hatenablog.com/entry/2012/02/19/155237
http://d.hatena.ne.jp/orangeclover/20120219


私の超素直な一言感想



普段Java(つーかAndroidっつーか)をメインで触っているとScalaにしてもGroovyにしても
確かに超短くなりますし、便利だなーって思います。
→すみません、その程度の認識です。こまけーこたーいいんだよ

そんな俺の結果。いいのか悪いのかは正直よくわかんないけど。
https://bitbucket.org/daneko/startupgroovy


でも楽しかったです。また有るなら参加したいですね〜

生 @kyon_mm さん見れたし。


以下重要


後日模範解答がでるのと

Android + JUnit + Robolectric + Gradle

な環境構築に関してまとめてくれるそうです。


Android + JUnit + Robolectric + Gradle

な環境構築に関してまとめてくれるそうです。

大切なことなので2回書きます。

やっぱり言ってみるものですね。


ということでSpockやらGradleやら触ってみたい項目がTODOに積まれるわけですが、他にも積んでいるものがあるわけで…
いつになったら消化できるんだこれ…




2012年2月5日日曜日

AsyncTaskLoader使ってみた…んだけど

今更ながらAsyncTaskLoaderを使って見ました。

コンパチライブラリを使用してです。(未だに2系だけしか持ってません( ー`дー´)キリッ)
動作させたのは2.2のエミュレータとDesire(2.2)とINFOBAR(2.3)

以下参考サイト
時代はAsyncTaskよりAsyncTaskLoader
ソフトウェア技術ドキュメントを勝手に日本語化

サンプルはこちら
githubじゃなくてスミマセン。
単に指定した秒数待ってランダムに値をぶん投げるだけのものですが…
https://bitbucket.org/daneko/asynctaskloadercheck

まあ今までAsyncTaskは使わずに自前でThread書いたりHandlerに投げたりばっかしてました…
なのでこれからは時代の流れにそってAsyncTaskLoaderを使いたいと思います。



で終わるんだったらこのエントリーはしません。

以下ハマった現象

画面回転を行なうと「FragmentActivityから生成されたLoader側はなぜかおかしい挙動をする」
Fragment#getLoaderManagerで生成したものは期待通りの動きをするんですが…

何を言っているのかわか(ry

とりあえずおもむろにログを晒す

02-05 18:36:30.732: D/jp.omokageru.dnk.loader.HelloLoaderActivity(613): onButtonClick
02-05 18:36:30.732: V/LoaderManager(613): initLoader in LoaderManager{44f79998 in HelloLoaderActivity{44f43928}}: args=Bundle[{sleeptime=2000}]
02-05 18:36:30.741: D/jp.omokageru.dnk.loader.HelloLoaderActivity(613): onCreateLoader
02-05 18:36:30.741: D/jp.omokageru.dnk.loader.CheckLoader(613): CheckLoader
02-05 18:36:30.741: V/LoaderManager(613):   Starting: LoaderInfo{44f7adb0 #0 : CheckLoader{44f7af20}}
02-05 18:36:30.741: D/jp.omokageru.dnk.loader.CheckLoader(613): onStartLoading
02-05 18:36:30.771: V/LoaderManager(613):   Created new loader LoaderInfo{44f7adb0 #0 : CheckLoader{44f7af20}}
02-05 18:36:30.781: D/jp.omokageru.dnk.loader.CheckLoader(613): loadInBackground
02-05 18:36:32.786: V/LoaderManager(613): onLoadComplete: LoaderInfo{44f7adb0 #0 : CheckLoader{44f7af20}}
02-05 18:36:32.786: V/LoaderManager(613):   onLoadFinished in CheckLoader{44f7af20 id=0}: String{44f7e390}
02-05 18:36:32.791: D/jp.omokageru.dnk.loader.HelloLoaderActivity(613): onLoadFinished
02-05 18:36:32.791: D/jp.omokageru.dnk.loader.HelloLoaderActivity(613): loader:CheckLoader{44f7af20 id=0}
02-05 18:36:32.791: D/jp.omokageru.dnk.loader.HelloLoaderActivity(613): data:Finish9
02-05 18:36:42.191: V/LoaderManager(613): Retaining in LoaderManager{44f79998 in HelloLoaderActivity{44f43928}}
02-05 18:36:42.191: V/LoaderManager(613):   Retaining: LoaderInfo{44f7adb0 #0 : CheckLoader{44f7af20}}
02-05 18:36:42.191: V/LoaderManager(613): Destroying Inactive in LoaderManager{44f79998 in HelloLoaderActivity{44f43928}}
02-05 18:36:42.291: V/LoaderManager(613): Finished Retaining in LoaderManager{44f79998 in HelloLoaderActivity{44f813a0}}
02-05 18:36:42.331: V/LoaderManager(613):   Finished Retaining: LoaderInfo{44f7adb0 #0 : CheckLoader{44f7af20}}
02-05 18:36:42.331: V/LoaderManager(613):   Stopping: LoaderInfo{44f7adb0 #0 : CheckLoader{44f7af20}}
02-05 18:36:46.451: D/jp.omokageru.dnk.loader.HelloLoaderActivity(613): onButtonClick
02-05 18:36:46.451: V/LoaderManager(613): restartLoader in LoaderManager{44f79998 in HelloLoaderActivity{44f813a0}}: args=Bundle[{sleeptime=2000}]
02-05 18:36:46.451: V/LoaderManager(613):   Making last loader inactive: LoaderInfo{44f7adb0 #0 : CheckLoader{44f7af20}}
02-05 18:36:46.451: D/jp.omokageru.dnk.loader.HelloLoaderActivity(613): onCreateLoader
02-05 18:36:46.451: D/jp.omokageru.dnk.loader.CheckLoader(613): CheckLoader
02-05 18:36:51.482: V/LoaderManager(613): Retaining in LoaderManager{44f79998 in HelloLoaderActivity{44f813a0}}
02-05 18:36:51.491: W/LoaderManager(613): Called doRetain when not started: LoaderManager{44f79998 in HelloLoaderActivity{44f813a0}}
02-05 18:36:51.491: W/LoaderManager(613): java.lang.RuntimeException: here
02-05 18:36:51.491: W/LoaderManager(613):  at android.support.v4.app.LoaderManagerImpl.doRetain(LoaderManager.java:733)
02-05 18:36:51.491: W/LoaderManager(613):  at android.support.v4.app.FragmentActivity.onReallyStop(FragmentActivity.java:634)
02-05 18:36:51.491: W/LoaderManager(613):  at android.support.v4.app.FragmentActivity.doReallyStop(FragmentActivity.java:616)
02-05 18:36:51.491: W/LoaderManager(613):  at android.support.v4.app.FragmentActivity.onRetainNonConfigurationInstance(FragmentActivity.java:442)
02-05 18:36:51.491: W/LoaderManager(613):  at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3617)
02-05 18:36:51.491: W/LoaderManager(613):  at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3673)
02-05 18:36:51.491: W/LoaderManager(613):  at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3789)
02-05 18:36:51.491: W/LoaderManager(613):  at android.app.ActivityThread.access$2400(ActivityThread.java:125)
02-05 18:36:51.491: W/LoaderManager(613):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2037)
02-05 18:36:51.491: W/LoaderManager(613):  at android.os.Handler.dispatchMessage(Handler.java:99)
02-05 18:36:51.491: W/LoaderManager(613):  at android.os.Looper.loop(Looper.java:123)
02-05 18:36:51.491: W/LoaderManager(613):  at android.app.ActivityThread.main(ActivityThread.java:4627)
02-05 18:36:51.491: W/LoaderManager(613):  at java.lang.reflect.Method.invokeNative(Native Method)
02-05 18:36:51.491: W/LoaderManager(613):  at java.lang.reflect.Method.invoke(Method.java:521)
02-05 18:36:51.491: W/LoaderManager(613):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
02-05 18:36:51.491: W/LoaderManager(613):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
02-05 18:36:51.491: W/LoaderManager(613):  at dalvik.system.NativeStart.main(Native Method)

ログを見たら破棄したローダを再利用して落ちているように見えます。

発生させる挙動としては以下
  • アプリを立ち上げる
  • AsyncTaskLoaderを実行する
  • 結果を見る
  • 画面回転する
  • AsyncTaskLoaderを実行する
  • onCreateLoaderで止まる(なぜ…)
  • 再度画面回転する
  • 上記ログが出る

回避方法はわかった範囲では以下2つ
  • 画面回転時Activityを破棄しないようにする(単に逃げる)
  • onCreate時にFragmentActivity#getSupportLoaderManagerを呼び出す(なぜうまく行くのかわからん…)
2つ目なんてバットノウハウ以外の何者でもないんだけど

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // ここでの呼び出しを無くすとなぜか画面回転後は動作しなくなる
        // しかもonCreate以外での呼び出しは駄目っぽい
        getSupportLoaderManager();
    }

ただ画面回転で起こるということは、状況によってはIntentなんかで画面遷移した際にも
起こりそうで嫌です…
destroyまで走れば一緒かな…

Fragment#getLoaderManagerもFragmentActivity#getSupportLoaderManagerも
結局はFragmentActivity#getLoaderManagerを呼んでいますしなんでなんだろ

どっちもonDestroy時にLoaderManager#doDestroy呼んでいるし



何れにしても、なんかうまくいきませんでした。
「使い方がおかしいだろ」とかあったら教えてください。

2012年1月22日日曜日

今更ながらAntでAndroidのTestを実行してみる

前回初めてAnt触ったわけですが、とりあえず気になったんでやって見ました系

Ant詳しくない俺がやっているので間違っていたらツッコミください。
この辺とか参考


例によってEclipseから適当にプロジェクトを生成。
その時Testプロジェクトも作成

HelloAnt/
├── AndroidManifest.xml
├── assets
├── bin
├── gen
├── proguard.cfg
├── project.properties
├── res
└── src
    └── jp
        └── omokageru
            └── dnk
                └── helloant
                    └── HelloAntActivity.java

HelloAntTest/
├── AndroidManifest.xml
├── assets
├── bin
├── gen
├── proguard.cfg
├── project.properties
├── res
└── src
    └── jp
        └── omokageru
            └── dnk
                └── helloant
                    ├── HelloAntActivityTest.java //これは自分で作る
                    └── test

//HelloAntActivityTest.java
package jp.omokageru.dnk.helloant;

import android.test.ActivityInstrumentationTestCase2;

public class HelloAntActivityTest extends ActivityInstrumentationTestCase2<HelloAntActivity> {

    public HelloAntActivityTest() {
        super("jp.omokageru.dnk.helloant", HelloAntActivity.class);
        // TODO Auto-generated constructor stub
    }

    public void testText() throws Exception {
        assertEquals("Hello World, HelloAntActivity!",
                getActivity().getString(jp.omokageru.dnk.helloant.R.string.hello));
    }
}

HelloAntとTestの親ディレクトリへ居るとしてAntプロジェクト用にupdate

2012/01/25 修正
http://developer.android.com/guide/developing/testing/testing_otheride.html#UpdateTestProject にある通り、「android update test-project」を使うべきでした。
update test-projectで引数に指定したとおりant.propertiesが生成されます。

android update project -p HelloAnt/
# android update project -p HelloAntTest/ 
# test-projectの-mは -pからみた相対パスか絶対パスを指定する
android update test-project -m ../HelloAnt/ -p HelloAntTest/

そしてTest側にant.propertiesファイルを作成します。
中身はこんな感じ
# HelloAntとHelloAntTestが並列なのでこんな感じ
# tested.project.dir=../HelloAnt/
# test-updateにより自動で生成される


あとはエミュレータか、端末がつながっている状態で下記のように実行するとこんな感じです。
# HelloAntTestディレクトリに居るつもり
ant all clean debug install test
# 出力中略
-test-project-check:

test:
     [echo] Running tests ...
     [exec] 
     [exec] jp.omokageru.dnk.helloant.HelloAntActivityTest:
     [exec] .
     [exec] Test results for InstrumentationTestRunner=.
     [exec] Time: 0.315
     [exec] 
     [exec] OK (1 test)
     [exec] 
     [exec] 

BUILD SUCCESSFUL
Total time: 13 seconds

まあ all cleanが居るかどうかはびみょーなところでしょうけど無くて困るものでもないし…



試しに ant emma debug install test
とか叩いて見ましたが怒られました。
WARNING: Code Coverage is currently only supported on the emulator and rooted devices.
だそうです…

エミュレータ上でとりあえず試してみたらこんな感じでした。


root端末上でやるならばこの辺参考にすれば出来るんでしょうけど、
誰かやってないのかなぁ…(日本語でのまとめ的な意味で)



2012年1月14日土曜日

今更ながらAntでAndroidアプリをビルドしてみる

件名の通り、今更ながらAntでビルドして見ました。
AntでAndroidをビルドしたことない人向けです。つまり俺。

AndroidSDKやらADTはver16です。

流れ
・とりあえずビルドする(デバッグビルド)
・リリースビルドで署名まで済ませる
・NDKでのビルドも含める

このあたり参照
・http://developer.android.com/guide/developing/building/building-cmdline.html

流石にAntやJDKはインストール済みでパスが通っていることは前提


とりあえずデバッグビルド


とりあえず既にあるeclipseのプロジェクトと共存することが前提なので、
対象プロジェクトはeclipseで作ります。
ここではHelloAntという名前で作った前提となっています。

そうするとこんなディレクトリ構成かと

HelloAnt
├── AndroidManifest.xml
├── assets
├── bin
├── gen
├── proguard.cfg
├── project.properties
├── res
└── src

とりあえず上記HelloAntディレクトリに移動
で、以下コマンドを実行

android update project -p ./

すると以下の結果となる

Updated local.properties
No project name specified, using Activity name 'HelloAntActivity'.
If you wish to change it, edit the first line of build.xml.
Added file ./build.xml
Updated file ./proguard.cfg

build.xmlとlocal.propertiesという2つのファイルが増えているはず

HelloAnt
├── AndroidManifest.xml
├── assets
├── bin
├── build.xml
├── gen
├── libs
├── local.properties
├── proguard.cfg
├── project.properties
├── res
└── src

これでAntプロジェクトとして作成されました。


おもむろに以下コマンドを叩きます

ant debug

binディレクトリに行くと「HelloAntActivity-debug.apk」として出来ているはず。

以上でデバッグビルドは終わり。


リリースビルドに署名まで済ませる


まずkeystoreを作成する(略)

次に「ant.properties」というファイルを作成し以下の内容を書く
key.store=hoge.keystore //(へのパス)
key.store.password=hogehoge
key.alias=hoge
key.alias.password=hugahuga

key.storeの右辺にはkeystoreファイルのパスを記載すればOK
ここではhoge.keystoreという名前でHelloAntディレクトリ直下に居る前提なので
単に「hoge.keystore」と書かれています。

HelloAnt
├── AndroidManifest.xml
├── ant.properties
├── assets
├── build.xml
├── hoge.keystore
├── libs
├── local.properties
├── proguard.cfg
├── project.properties
├── res
└── src

おもむろに以下コマンドを叩く

ant release

binディレクトリに行くと「HelloAntActivity-release.apk」として出来ているはず。

以上でリリースビルドは終わり。


一旦キレイにする


ant clean

binとかgenディレクトリが消えるよ!


proguardを使う


通常eclipseで使うのと一緒
project.propertiesに以下の一行を追加

proguard.config=proguard.cfg //(へのパス)

昔はantでproguardは大変だったらしい。

追加で記入したら「ant release」でprogurardがapkに使われます。


NDKでのビルドも含める


ndkにもパスが通っていることが前提です。

ついにbuild.xmlを開きます。
そして51行目付近を見ます



上記部分をこんな感じに書き換えます

    
        
            
            
        
    


これで実行!と言いたいのですが、そもそもNDKのソースが無いのでsampleの「hello-jni」から
jniディレクトリごとコピーしてきます。

HelloAnt
├── AndroidManifest.xml
├── ant.properties
├── assets
├── build.xml
├── hoge.keystore
├── jni
│   ├── Android.mk
│   └── hello-jni.c
├── libs
├── local.properties
├── proguard.cfg
├── project.properties
├── res
└── src
hello-jni.cの関数名修正を忘れずに。
あと呼び出し元も無いので適当に作ってください。

めんどければhello-jniごとコピーで、antプロジェクト作成でもOKです。

ant release/debug
と叩けば-pre-buildの項目がこんな感じで出力されます

-pre-build:
     [exec] Compile thumb  : hello-jni <= hello-jni.c
     [exec] SharedLibrary  : libhello-jni.so
     [exec] Install        : libhello-jni.so => libs/armeabi/libhello-jni.so



NDKでのビルドも含める その2


NDKだけ常にリリースビルドじゃ芸がないのでさっき修正した場所を下記のようにして見ました。

    
        
            
        
    

    
        
            
                rel!
                
                    
                    
                
            
            
                dev!
                
                    
                    
                
            
        
    

これでdebug指定時はNDK_DEBUG=1でビルドされます。

なお、ant releaseなのかdebugなのかは
android-sdk/tools/ant/build.xml
を見て、${build.target}に直接文字列入れていたのでそれで判定しましたが…
いいのかな…

あとはコンパイル失敗をsoファイルができたかどうかとかでチェックしてやればより良いかも?

そういやndk-buildをオプション無しで叩いてもリリースビルドでチョットびっくり。
r4ぐらいの時、デフォルトはデバッグビルドじゃなかったでしたっけ?


おまけ


ant clean
ant release -v > tmp.txt
で詳細をよく見てみたら…

-compile:
Property "tested.project.absolute.dir" has not been set
Property "tested.project.absolute.dir" has not been set
    [javac] jp/omokageru/dnk/helloant/HelloAntActivity.java added as jp/omokageru/dnk/helloant/HelloAntActivity.class doesn't exist.
    [javac] /Users/dnk/personal/android/HelloAnt/gen/R.java.d skipped - don't know how to handle it
    [javac] jp/omokageru/dnk/helloant/R.java added as jp/omokageru/dnk/helloant/R.class doesn't exist.
    [javac] Compiling 2 source files to /Users/dnk/personal/android/HelloAnt/bin/classes
    [javac] Using modern compiler
    [javac] Compilation arguments:
    [javac] '-d'
    [javac] '/Users/dnk/personal/android/HelloAnt/bin/classes'
    [javac] '-classpath'
    [javac] '/Users/dnk/personal/android/HelloAnt/bin/classes:/Users/dnk/personal/android/HelloAnt'
    [javac] '-sourcepath'
    [javac] '/Users/dnk/personal/android/HelloAnt/src:/Users/dnk/personal/android/HelloAnt/gen'
    [javac] '-target'
    [javac] '1.5'
    [javac] '-bootclasspath'
    [javac] '/Users/dnk/app/android-sdk/platforms/android-8/android.jar'
    [javac] '-encoding'
    [javac] 'UTF-8'
    [javac] '-g'
    [javac] '-source'
    [javac] '1.5'


ん?1.5?
確か今ってビルドの指定は1.6じゃなかったでしたっけ?

ということで
android-sdk/tools/ant/build.xmlで、1.5指定のところ2ヶ所を1.6に書き換えて動作させて見ましたが、
問題なく動いているきがしました。

ant testとかあるけど、そのへんはきっとtest部の方々が総掛かりでまとめてくれることを勝手に期待しております。


リリースが頻発するとか、NDKを含めたコンパイルが多いとかだとAntの方が楽?
普段のデバッグビルド状態だとEclipseが吐き出したもので困ることはありませんが、
準備しておけば楽っぽいですね。

あと、複数人で開発しててちゃんとビルドサーバっつーかCIサーバみたいなのが有るところだと必須なんでしょう。

そういうところだとどんなAndroid開発なのか凄く気になる…



2012年1月6日金曜日

【未解決】 BOOT_COMPLETEDとamコマンド

起動のイベントを取るには以下をBroadcastReceiverで受ければOK

android.intent.action.BOOT_COMPLETED

詳細は例によってtechboosterさんとかを参照
http://techbooster.org/android/application/1100/

これらの動作を確認するのに一々rebootなんかしてられませんので、
下記コマンドをターミナルから叩いて動作確認!

adb shell am broadcast -a android.intent.action.BOOT_COMPLETED

と思ったらこれが受信しないわけです。

結論から言うとパーミッションをマニフェストに書く位置が問題でした。


以下だとOK





    略
    

    
        
            

            
        
    



以下だと「実際に端末を起動」したときはOKですが、ターミナルからイベント発行時はなぜか認識されず…
認識しなかった端末は以下
  • Desire
  • INFOBAR
  • IS03





    略
    

    
        
            

            
        
    




    略
    

    
        
            

            
        
    


これ何故なんでしょうか…

ちなみにエミュレータだとどれでも問題なく動作します。

なお後者の書き方でイベント発行時はこんな警告が出ます

permission denial言われてもねぇ…
そしてプリインストールと思われるアプリも幾つか引っかかっている。

とにかくBOOT_COMPLETEDをターミナルから叩くときは、端末によってはパーミッション書く位置で
動作しなかったりするっぽいから注意!でいいのだろうか?

もしくは他に要因があるのかな?詳しく知っている人居たら教えて欲しいです。

2011年11月6日日曜日

AndroidMock環境構築メモ 〜java.lang.RuntimeException: Could not find mockに嵌っている人へ〜

最近ようやくテストを書くようになりました、こんにちは。
以下はAndroidMockを使用する上でのメモ。

Android Test部による翻訳ページやtestterTestのソース、
そもそもAndroidMockのページのAndroidMockinEclipse.pdfを見てもうまく行かなかった俺向けmemo。

誤解を恐れずに言うとそもそものpdfが間違っているというか不親切。たぶん、きっと、もしかしたら…
ちなみに「interfaceに対してmockを生成するだけ」なら
  • AndroidMockRuntime.jarをプロジェクトに追加するだけ
でOK


まず陥ったこと
classのモックを生成しようとすると
java.lang.RuntimeException: Could not find mock…
が発生

ここではinterfaceでもclassでもMock作るよ〜的なこと書いてあるし、
こっちではMyClassとかいうclassのMock作っているしなんで??

とまあ結局こいつを解消するのに一晩掛かったというか、朝6時になった…/(^o^)\

以下アホみたいにスクショを混ぜて設定方法
動作確認環境は
  • Mac Book Air OSX 10.7
  • Eclipse 3.7
  • X06HT
  • INFOBAR A01
  • XOOM
AndroidMockのバージョンは1.1.1


まずMockを生成したいテストプロジェクトのプロパティを選択


AndroidMockRuntime.jarをプロジェクトに追加
Generator.jarではない。

後々プロジェクトを他人と共有したりするのなら、
プロジェクト以下の適当なディレクトリ(libとかlibsとか)に
コピって参照する方がいいかも。

繰り返すけど、Mockを生成する対象がInterfaceだけなら、
ここから下は設定してもしなくても一緒。


Java Compiler を選択してJDK1.6が選択されていることを確認

その下のAnnotation Processingを選択し、下図のようにチェックを入れる。
Enale processing in editorにはチェックを入れない



Annotation Processing の Processor optionsを下記のように修正

確かにpdfを見ると思わず
  • bin_dirにはテストプロジェクトのbinディレクトリへのフルパス
  • logfileにはファイル名のみ
とやりたくなるが、そうすると嵌る。

ここでは、
  • bin_dirにはテストプロジェクト/bin/classesへのフルパス
  • logfileにはテストプロジェクト/ AnnotationProcessor.logへのフルパス   
を指定したと仮定
俺が嵌ったところなので大きく書きます。


2011-11-08 追記
logfileを単にファイル名だけ記載すると
…/Eclipse.app/Contents/MacOS/ 以下に出来てました。
ってことは同ディレクトリ以下にワークスペース作れば(まあ作らないけど)
bin_dirも相対パスで書けるんじゃね?とか思います。
本当はもっと環境に依存しない書き方があればいいんですけど
ぱっとは思いつかね。



次にFactoryPathを設定
ここはpdfと一緒
AndroidMockGenerator.jarのパスを追加し忘れないように。

あとANDROID_RUNTIME の値はとりあえず手元の最新のandroid.jarを指定すればいいと思う。
俺の場合はr14だったのでICS用ですが、2.2~3.1端末で動きました。
→多分下方互換があるからなんだと思うけど。

また「自分で作成したクラスのMockが必須」であるのならば、pdfで言うAPP_UNDER_TESTを作らないとダメ。(名前はなんでもいい)
なので設定値は
  • テスト元プロジェクト/bin/classes
とかになるはず。
このAPP_UNDER_TESTに相当するパスを間違えると、たしかAnnotation.logに
「classが見つかんねーよ!」とログが出るはず。

その他外部ライブラリのモックを作るのなら、ここでそのライブラリへのパスを指定する。



と、ここまで設定したら、とりあえずお試しということで以下の様なソースを
適当に書いてみる。(テストとしては全く役に立たないけどw)
/**UsesMocksを忘れないように*/
    @UsesMocks(HogeUtil.class)
    public void testMockTest() {
        HogeUtil mockUtil = AndroidMock.createMock(HogeUtil.class);
    }

HogeUtil.classは元プロジェクトにあるなんかのクラスと仮定してください。
@UsesMocksが無くてもコンパイルは通りますが、下記が生成されません。

上記でビルドすると
bin_dirで指定したディレクトリ以下に「genmocks」ディレクトリが出来ており、
  • HogeUtilDelegateInterface.class
  • HogeUtilDelegateSubclass.class
が生成されているのが確認できるはず。
もし出来ていなければ、設定がどっか間違っていると思われる。
Annotation.logになにかヒントが出ているかもしれないので、見るといいかも?

次にとりあえずJUnitを実行させて、緑になるのを確認したほうがいいと思う。
仮に
java.lang.RuntimeException: Could not find mock…
が発生するようなら、やっぱり設定が間違っている。


ちなみにAnnotationで@UsesMocksを指定しているが、Interfaceは無くても問題なく動く。

createMockのソース追いかければわかるけど、interfaceの場合はすぐEasyMockへ
クラス名渡してその戻り値を使っている。
一方でクラスの場合は、上記の生成したクラスファイルを探しに行っていることがわかる。

なのでinterfaceの場合はAnnotationを書かず、Generatorが動作せずに
classファイルを生成しなかったとしても何も問題ない。
どっちにしてもinterfaceの場合DelegateInterface/SubClassどっちも作成されないけど。

結果最初に書いた、「InterfaceのMockを作るだけならRuntime.jarを追加するだけでOK」につながる。



なぜAndroidMockGenerator.jarでなくて、Rutime.jarをプロジェクトに追加したか?

実はどっちも試しました。
一晩あれこれ嵌ればそりゃあ色々と試しますw
結果どっちも同じように動きました。

ここからは単なる仮定なんですが、ファイルサイズからすると
Runtime.jarのほうが小さい。
でまあ名前とFactory Path の設定から
  • Generator.jarはDelegateInterface.class/DelegateSubclass.classを生成するための実行ファイル
  • Runtime.jarはAndroidMockをAndroid上で動かすために必要な最低限のクラスファイルの塊
なんだと思います。
当然処理が被るから、Generator.jarにはRuntime.jarも包含していると。

まあ細かく見てないから知らないけど多分そんな感じ。


以下NGな場合
bin_dirにテストプロジェクト/binを指定すると確かにgenmocksディレクトリが
bin以下に生成される。

ただし、この生成されたclassがテストプロジェクトによって生成されたapkに
含まれることがないようだ。
この状態だとAnnotationProcessor.logを見ても、きちんと生成されているので
実際に動作させて「createMock」を呼んだ所で
java.lang.RuntimeException: Could not find mock…
が発生する。

InterfaceのMock作成の場合はそもそもMockGeneratorが
事前生成しない(する必要がない)ので発生しない。

ただし、bin/classesを吐き出し先とするとEclipse上からは
フィルタがかかっているのようなので見えない。
→多分解除できると思うけどFilterにそれっぽいのが見つからない…

これが嫌ならテストプロジェクト/hoge ディレクトリとかを吐き出し先として、
そのhogeディレクトリをリンクさせてもOK
ただしクリーンをかけてもhogeディレクトリが対象にならないので、
自前でgenmocksディレクトリを削除する必要がある。

当然bin/genmocksをコンパイル対象にすればいいじゃんと思ったが、
実際に行うとコンソールにこっそりと下記が出力され、やっぱり
java.lang.RuntimeException: Could not find mock…
が発生する
trouble processing:
[2011-11-06 14:25:31 - HelloMockTest] Dx class name (genmocks/jp/omokageru/dnk/hellomock/HogeUtilDelegateInterface) does not match path (jp/omokageru/dnk/hellomock/HogeUtilDelegateInterface.class)
...while parsing jp/omokageru/dnk/hellomock/HogeUtilDelegateInterface.class
...while processing jp/omokageru/dnk/hellomock/HogeUtilDelegateInterface.class

結局genmocksディレクトリを直接指定してはダメで、
genmocksディレクトリの親ディレクトリを指定するのだが、
binディレクトリはすでに直下のclassesディレクトリをパスとしているので
binは指定できない。



以下疑問
残った android_framework_mocks.jar っていつ使うんだろ…
想像するに名前とサイズからAndroid特有のクラスのモックを作る際に必要なのかな?
いやでも、android.jarを指定させているからそこから参照して勝手に作りそうな気もするけど…

まあきっとなんかで詰まったらFactoryPathにでも指定すれば解決する日が来るのだろうw

もしなんか間違っていたりしたらご指摘ください。

2011年7月25日月曜日

あえてProgressDialogを使いまわす

間違っている気がしないでもない、ProgressDialog使い回し方法
たぶんProgressDialog以外でも使えるけど、その場合どうなるかは未検証

というよりも基本は

  • ProgressDialog生成
  • show
  • dismiss
を必要に応じて繰り返すでいいと思うんですが…

なんかこう毎度毎度ProgressDialogを生成するのがねぇ…

そして不思議とググッても(日本語では)この方法しかTOPに出てこないし。


ちなみに以下のようにすると、処理中を示すあのグルグル回るアニメーションが二回目以降動かないです。

/**
 * ところどころ脳内補完必須
 */
public class TestActivity extends Activity {

 private ProgressDialog   mProgressDialog    = null;
 
 public void onCreate( Bundle savedInstanceState) {

  mProgressDialog = new ProgressDialog( this);
  mProgressDialog.setProgressStyle( ProgressDialog.STYLE_SPINNER);
  mProgressDialog.setMessage( "Now processing...");
  mProgressDialog.setCancelable( false);
 }
 
 
 /**
  * ボタン押した時とかの処理
  */
 public void onClickTest( View view){
  
  Thread th = new Thread() {

   @Override
   public void run() {

    // TODO 適当に時間のかかる処理
    
    mProgressDialog.dismiss();
    
    return;
   }

  };

  th.start();
  
  mProgressDialog.show(); //2回目以降はグルグルアニメーションがされない!
  
 }

これを以下のようにすると二回目以降もグルグル回る

 /**
  * ボタン押した時とかの処理
  */
 public void onClickTest( View view){
  
  Thread th = new Thread() {

   @Override
   public void run() {

    // TODO 適当に時間のかかる処理
    
    mProgressDialog.hide();
    
    return;
   }

  };

  th.start();
  
  mProgressDialog.show();
  
 }

ただし developersの dismiss と hide をみると(恐らく)こう書いてあります。

  • dismiss:スクリーンから削除するよ。(たぶん)終了時の処理をなんか足したい場合にオーバーライドするならdismissじゃなくてonStop()側でやってね☆(ゝω・)vキャピ
  • hide:表示は消えるけど、これdismissじゃねーから

とまあ、恐らくProgressDialog側(Progress以外のダイアログもたぶん)はThreadっぽい実装を確実にしているっぽい。
いや、ソース見てない(追いかけるのメンドイ)から知らんけど。
でもOnStartとかOnStopとかそれっぽいし、恐らくdismissは内部のThread終了処理も含んでいるんでしょう。
一方でhideはホント表面上消しているだけっぽい。てか説明がそれだけだし…。

まあ、何が言いたいかというと上のソースもそういう意味で間違い。
きっと正しく修正するとこんな感じ

/**
 * ところどころ脳内補完必須
 */
public class TestActivity extends Activity {

 private ProgressDialog   mProgressDialog    = null;
 
 public void onResume() {

  mProgressDialog = new ProgressDialog( this);
  mProgressDialog.setProgressStyle( ProgressDialog.STYLE_SPINNER);
  mProgressDialog.setMessage( "Now processing...");
  mProgressDialog.setCancelable( false);
  
  super.onResume();
 }
 
 public void onPause() {

  mProgressDialog.dismiss();
  mProgressDialog = null;
  super.onPause();
 }
 
 /**
  * ボタン押した時とかの処理
  */
 public void onClickTest( View view){
  
   Thread th = new Thread() {

   @Override
   public void run() {

    // TODO 適当に時間のかかる処理
    
    mProgressDialog.hide();
    
    return;
   }

  };

  th.start();
  
  mProgressDialog.show();
  
 }
}

何れにしてもdismiss呼んでおけば終了処理は行われるんだろうと脳内解釈するとこれで正しいと思います

まあこういう書き方をするメリットは…?と聞かれると「さぁ…」としか答えられませんが…

でもhideが非推奨メソッドでも無く、呼び出せるようになっているという以上、間違いではないはず。

インスタンスを再生成しないぶん、もしかしたらメモリ的に有利かもしれないけど…
ダイアログ一個でそんなに変わるとは思えないです…(計測すればイイんだけど)
むしろ背面でもグルグル処理やっていたらCPU食っているかもしれない(それもたかが知れているはずですが)

あとは、

  • 時間のかかる処理
  • 一旦ユーザーに何か処理を行わせるためにhide
  • 時間のかかる処理再開
  • 一旦ユーザーに何か処…
とかの場合でしょうか??なんか良い使い方が思いつかん。

で、本当の所どうなんだろう。サンプルでhideを見かけない(ような気がする)理由は。
教えて偉い人♪

2011年7月22日金曜日

久々にセンサーを使ってみた

台風一過って普通すごい夏日になる気がするんですけど… てな今日でしたが、中身は全くもって台風とは関係ありません

先週久しぶりにセンサーを使うことがありましたがちょっと困ったことがあったので備忘録
というか誰か解決法教えてください。

  • 知らない間に傾きセンサーが非推奨になってた
  • 端末によって常に「SENSOR_STATUS_UNRELIABLE」を返すお方が居る…


前者に関しては、非推奨になっている以上仕方ないので修正します。
参考はこちら

SDTECHさん
TechBoosterさん

非推奨になっているので、<user-feature>にもありません。

http://developer.android.com/guide/topics/manifest/uses-feature-element.html

でもまぁなんでセンサーのあるものをワザワザ演算としたのでしょうね…
併用じゃ駄目だったのかしら…
こんな私にも分かるように誰か事細かく説明して欲しいです


次は問題の常にUNRELIABLEなんですが…

上記のサイトのサンプルソースを見ても、UNRELIABLEな値は無視していいと思うというか無視するべきかと思っています。

Developerにもこうかいてあります。

The values returned by this sensor cannot be trusted, calibration is needed or the environment doesn't allow readings

俺の拙い英語力でも「信頼出来ないか、キャリブレーションが必要か、その環境で(値を)読み出すのは許可できねー」
とかそんなんだと思います。

つまり以下のサンプルソース的なので正しいはず。

public void onSensorChanged( SensorEvent event) {
 if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE){
  return;
 }
 
 // 略

まあ、大体の端末は別にこれで問題なく動くと思います。

が、IS06は違った…

でーん



単に onAccuracyChanged にだけ焦点を当てたアプリを作りました。
待てど暮らせど加速度と近接はUNRELIABLEなまま。

磁気と傾き(非推奨)はこの後MEDIUMとLOWを行ったり来たり…

ちなみにたまたま2台IS06があったので、2台試しましたが、結果は同じ。

で、これ結局どうすればいいの?
とりあえずUNRELIABLEのままで値を読むようにしましたが、いいのかそれで…


おまけ。
desireで動作させた。
キャプチャ取ったあと、傾き(非推奨w)もちゃんとHIGHになりました。
センサーによっては準備時間的なものが必要なんだろと勝手に解釈

あと、IS03も大丈夫だったような…。



2011年7月14日木曜日

Go 節電プロジェクトに乗っかってみた

なんかAPIが公開とかなので、これに乗っかってみた。

いきなりですが、節電には興味ありません(キリッ

本当に節電させたければ、強制的にやらないと無理だと思うんですけどねぇ…


というわけでAndroid で取得してみた



 private final static String mInstanceUri = "http://api.gosetsuden.jp/usage/tokyo/instant/latest";
 private static String  elecConsumption = null;

 
 private boolean getInstanceConsumption() {

  String jsonStr = getJsonData( mInstanceUri);
  if( jsonStr == null){
   return false;
  }

  try{
   JSONTokener token = new JSONTokener( jsonStr);
   JSONArray arr = new JSONArray( token);

   if( arr.length() != 1){
    return false;
   }

   // 良くうん千万KWと聞くので、上から4桁でいいやという適当実装
   elecConsumption = arr.getJSONObject( 0).getString( "usage").substring( 0, 4);

  }catch( JSONException e){
   return false;
  }

  return true;

 }


 private String getJsonData( String uri) {

  String ret = null;
  try{
   HttpClient httpClient = new DefaultHttpClient();
   HttpGet httpGet = new HttpGet( uri);

   HttpResponse response = httpClient.execute( httpGet);

   // 失敗時の処理
   if( response.getStatusLine().getStatusCode() != HttpStatus.SC_OK){
    return null;
   }

   // 解析と出力
   // サーバーからのデータを取得
   InputStream is = response.getEntity().getContent();
   InputStreamReader isr = new InputStreamReader( is);
   StringWriter strin = new StringWriter();
   BufferedReader buf = new BufferedReader( isr);
   for ( String line; ( line = buf.readLine()) != null;){
    strin.write( line);
   }

   is.close();
   isr.close();
   buf.close();

   ret = strin.toString();

   strin.close();


  }catch( ClientProtocolException e){
   return null;
  }catch( IOException e){
   return null;
  }

  return ret;

 }

これで直近の消費電力量が取れました。合っていると思いますw
詳細なAPI仕様はこっちを見てください。

なお、恐らくドキュメント側がまだ整理されていないようで、2011/07/14 現在

  • ミリ秒と書いてあるけど秒単位のやつがある
  • 日本基準時間と書いてあるけどGMTで返ってくるやつがいる
とかありましたので、時間データを使う際は微妙に注意してください。
ドキュメントが正しいのか、データが正しいのか早めに決めて欲しいですね~


この手のアプリは既に腐るほど世の中に出回っていますので、それこそ時間を掛けてじっくり作るより、
即座に飛びついて速攻公開のほうが注目されるかと思いますw


とは言うものの、見せ方だよなぁ…
ぱっと思いつくなら、今頃もっといろんなモノ作れているっつーの。



おまけ
会社の人に頼んで、絵を作ってもらってちゃちゃっと作ったwidget
やっぱり絵が作れるっていうのは凄い… その才能が欲しいです…
しかも絵の作成も早いしww


2011年7月5日火曜日

android:onClickに関するmemo

完全に私的メモです。
元ネタはこちら
http://groups.google.com/group/android-group-japan/browse_thread/thread/1aa53a356930e039
読んでて良かった、グループのメール(ダイジェスト)w
まあ、真面目に読みだしたの最近ですけど…
でも結構困ったときに、まずAndroid関連のグループのメールに検索かけると何とかなりますよね。

私も同じことを疑問に持ったことがありましたが、「まあそういうもんだろ」で終わってました。
質問者と回答者に感謝♪


以下memo

 <Button
  android:id="@+id/button1"
  android:onClick="hoge" />
ってリーソスに書くと、ソース側で
 public void hoge(View v) {
  // クリック時の処理
 }
と書けるのは知ってた。

んじゃー、2個ボタンあったらどーするの?
 <Button
  android:id="@+id/button1"
  android:onClick="hoge1" />

 <Button
  android:id="@+id/button2"
  android:onClick="hoge2" />
 public void hoge1(View v) {
  // クリック時の処理
 }
 public void hoge2(View v) {
  // クリック時の処理
 }
もちろんこれでも動く。

でもこう書けるよね。
 <Button
  android:id="@+id/button1"
  android:onClick="hoge" />

 <Button
  android:id="@+id/button2"
  android:onClick="hoge" />
 public void hoge1(View v) {
  // クリック時の処理
  switch( v.getId()){
  case R.id.button1:
   // button1の処理
   break;
  case R.id.button2:
   // button2の処理
   break;
  }
 }

改めてソース読めば、そりゃそうだって内容ですよね。
id割り振ってんだからそれで分けられるだろと。
でも見るまで気がつかなかった…。

ID以外にもあえて分岐条件に使ったら何か面白いものが書けそうな気もしますね。

2011年6月18日土曜日

未解決 AdMobをIMEの下に隠したら消えた?

AdMobをIMEの下に隠したら消えた?

超絶的に適当なレイアウトだと消えるようです。
正直どうでもいいんですけど気になって気になって…

また消えること自体も別にいいんです。

が

消えた事自体を取得できなくて困ってます。

とりあえずこんなレイアウトとソース

main.xml

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:ads="http://schemas.android.com/apk/lib/com.google.ads"
 android:layout_width="fill_parent" android:layout_height="fill_parent"
 android:id="@+id/adLayout"
 android:orientation="vertical">

 <EditText android:id="@+id/editText1" android:text="test"
  android:layout_width="fill_parent" android:layout_height="wrap_content">
  </EditText>

 <Button android:text="Button" android:id="@+id/button1"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"></Button>

</LinearLayout>

ソース
private WrapAdView  mAdView  = null;
private AdRequest  mAdRequest = null;

private LinearLayout mLayout  = null;
private Button   mButton  = null;

public void onCreate( Bundle savedInstanceState) {

 super.onCreate( savedInstanceState);
 setContentView( R.layout.main);

 mLayout = (LinearLayout) findViewById( R.id.adLayout);

 initAdmobMakeView();

 mButton = (Button) findViewById( R.id.button1);

 mButton.setOnClickListener( new OnClickListener() {

  public void onClick( View v) {

   // TODO 自動生成されたメソッド・スタブ
   initAdmobMakeView();
  }
 });
}


private void initAdmobMakeView() {

 if( mAdView == null){
  mAdView = new WrapAdView( this, AdSize.BANNER, "Publisher ID");
  mAdView.setAdListener( this);
  mLayout.addView( mAdView);
 }

 if( mAdRequest == null){
  mAdRequest = new AdRequest();
 }

 mAdRequest.addTestDevice( "hoge");
 mAdRequest.addTestDevice( AdRequest.TEST_EMULATOR);

 if( mAdRequest.isTestDevice( this)){
  Log.d( AdRequest.LOGTAG, "test");
  mAdView.loadAd( mAdRequest);
 }else{
  Log.d( AdRequest.LOGTAG, "not test");
 }
}

うん、こんなレイアウトのアプリはありえないね。

というのは置いといて…

おもむろにEditTextに改行を加えていくとこうなります。



AdViewが消えます。

その時Logcatにはこんな表示が

Not enough space to show ad! Wants: <480, 75>, Has: <480, 69>

ああ、表示領域が足りなくなったんですね。そらしゃーない。

んじゃー、表示できる領域がまた出来れば復活しますよね~

…

戻りません…

まあ再度adLoad呼べばいいんですが、そのタイミングがわかんないですよね。

ってことで AdListener をインプリメントすれば何かわかるんじゃないかな~

と思い、こんな感じに修正

public class Test extends Activity implements AdListener {

 /*中略*/

 public void onDismissScreen( Ad arg0) {

  Log.d( AdRequest.LOGTAG, "onDismissScreen");

 }


 public void onFailedToReceiveAd( Ad arg0, ErrorCode arg1) {

  Log.d( AdRequest.LOGTAG, "onFailedToReceiveAd");

 }


 public void onLeaveApplication( Ad arg0) {

  Log.d( AdRequest.LOGTAG, "onLeaveApplication");

 }


 public void onPresentScreen( Ad arg0) {

  Log.d( AdRequest.LOGTAG, "onPresentScreen");

 }


 public void onReceiveAd( Ad arg0) {

  Log.d( AdRequest.LOGTAG, "onReceiveAd");

 }
}

とりあえず何か返ってくるだろってことで期待しました。

が、消えるタイミングでは何も来ない…

…

えー、じゃあAdView拡張してなんかそれっぽいやつ(名前だけで適当に判断)Overrideしてイベント収集とか??

/**
 * とりあえずイベント収集
 */
class WrapAdView extends AdView {

 public WrapAdView( Activity activity, AdSize adSize, String adUnitId) {

  super( activity, adSize, adUnitId);
 }


 @Override
 public boolean dispatchPopulateAccessibilityEvent( AccessibilityEvent event) {

  Log.d( AdRequest.LOGTAG, "dispatchPopulateAccessibilityEvent:" + event);
  return super.dispatchPopulateAccessibilityEvent( event);
 }


 @Override
 public void dispatchWindowVisibilityChanged( int visibility) {

  Log.d( AdRequest.LOGTAG, "dispatchWindowVisibilityChanged:" + visibility);
  super.dispatchWindowVisibilityChanged( visibility);
 }


 protected void dispatchVisibilityChanged( View changedView, int visibility) {

  super.dispatchVisibilityChanged( changedView, visibility);
  Log.d( AdRequest.LOGTAG, "visibility:" + visibility);
 }


 protected void onWindowVisibilityChanged( int visibility) {

  super.onWindowVisibilityChanged( visibility);

  switch( visibility) {
  case VISIBLE:
   Log.d( AdRequest.LOGTAG, "VISIBLE");
   break;
  case INVISIBLE:
   Log.d( AdRequest.LOGTAG, "INVISIBLE");
   break;
  case GONE:
   Log.d( AdRequest.LOGTAG, "GONE");
   break;
  }

 }


 protected void onVisibilityChanged( View changedView, int visibility) {

  super.onVisibilityChanged( changedView, visibility);

  switch( visibility) {
  case VISIBLE:
   Log.d( AdRequest.LOGTAG, "VISIBLE");
   break;
  case INVISIBLE:
   Log.d( AdRequest.LOGTAG, "INVISIBLE");
   break;
  case GONE:
   Log.d( AdRequest.LOGTAG, "GONE");
   break;
  default:
   Log.d( AdRequest.LOGTAG, "ERR:" + visibility);
  }
 }
}

結果何も取得できず!!

( ´∀`)Σ⊂(゚Д゚ ) なんでやねん!

ちなみにレイアウト側を弄るとそもそもIMEの下に隠れるとかなくなりますが…

でもなんか釈然としない。

あー、やっぱりレイアウトに関してもっと勉強せんと駄目なんだろうなぁ…

ま、別にたまたま気がついただけで逃げ方は幾らでもありそうですが、

必ずディスプレイの下側にAdViewを配置して、IME表示時は隠れるようにしたい!!

とかになったらどーすんだろ。

IMEの表示非表示とかどうにかしてどうにかなるのかな…と思ってググったらぽいのがありました。

http://y-anz-m.blogspot.com/2010/12/androidonscreen-input-methods.html

上記を参考にして上手いことゴニョゴニョすれば何とかなりそうですね。

結論:やっぱりレイアウトに関して勉強すれば幾らでも回避できそう

メデタシメデタシ…

なのかな?

例の警告発生時(消えた事自体)のイベント取得方法知っている方居ましたら教えてください。

2011年6月15日水曜日

AdMobテストモードを使ってみた

前回はとりあえず表示だけしてみました。

だけど表示位置の確認やそもそもの動作確認したいですよね。

というかそれしないと駄目だろ。

という開発者のためのテストモードです。

てかさー、AdMobさんさー、もっとテストモードに関して分かりやすく書いてくれよ…

前回と全く同じ環境ですが、一応動作確認環境を書いておきます

端末:X06HT(Desire)

OS:2.2(公式っつーかrootも取ってない)

AdMobSDK:Ver 4.1.0

特にAdMobのSDKのバージョンが古いと、全くもって導入方法が変わるので注意


使用API

  • AdRequest.addTestDevice or setTestDevices
  • AdView.loadAd

XMLベースの場合

残念ながらXMLだけでレイアウトも書いてテストモードで動作ってのは出来なそうです。
そんなに追っかけてないから嘘かもしれませんがw

http://code.google.com/intl/ja/mobile/ads/docs/android/banner_xml.html#lookupandload

さて、自前でロードする(loadAdを呼んで広告表示させる)場合、上記の Lookup and Load 項目にあるように

・ads:loadAdOnCreate="true"
を削除するか false を指定する必要があります。

それが完了したらとりあえず下記のようなソースを書きます。

下のソース上では、レイアウトが記載されたxmlはmain.xmlという前提です。

当然パブリッシャーIDもxml上に書かれています。

public void onCreate( Bundle savedInstanceState) {

 super.onCreate( savedInstanceState);
 setContentView( R.layout.main);

 initAdmobLoadResView();

}


private void initAdmobLoadResView() {

 AdView adView = (AdView) findViewById( R.id.adView);
 AdRequest adRequest = new AdRequest();

 adRequest.addTestDevice( AdRequest.TEST_EMULATOR); // ※

 if( adRequest.isTestDevice( this)){
  Log.d( AdRequest.LOGTAG, "test");
 }else{
  Log.d( AdRequest.LOGTAG, "not test");
 }
 adView.loadAd( adRequest);

}

上記をとりあえず動作させ、Logcatを見ると…

「エミュレータ」ならとりあえず 「test」という文字列が表示されるでしょう。
(まあエミュレータの重さにうんざりしているので、確認してないんですけど。)

そして広告がテスト広告になっているのが確認できるかと思われます。

じゃあ、実機だとどうなのか…

多分普通に広告が表示されるか、表示に失敗して何も起きないかどっちかです。

Logcat的にはこんな感じ



で、Logcat上のこんな部分に注目しましょう。
To get test ads on this device, call adRequest.addTestDevice("hogehoge");

なにやら変なIDっぽいものが書いてあります。

これをおもむろにコピーして上記ソースの※部分を

・AdRequest.TEST_EMULATOR → "hogehoge"

に置き換え。

またここで得たhogehogeは端末毎で異なるようです。多分。

少なくとも会社の人の端末借りたら「not test」って返ってきましたし。

ついでに

if( adRequest.isTestDevice( this)){
 Log.d( AdRequest.LOGTAG, "test");
 adView.loadAd( adRequest);
}else{
 Log.d( AdRequest.LOGTAG, "not test");
}

としておくと安全面でいいかも

なお、最初に上記のようなソースにしなかったのには理由が。
loadAdを呼ばないと上記のLogcatが表示されないんですね。

最初からこの状態だと「not test」が表示されるだけで後は何も起こりません。

ちなみに…

addTestDeviceのjavadocはこんな感じです。

public void addTestDevice(String testDevice)

Causes test ads to be returned to a device. Look at logcat while requesting an ad to see how to return test ads to a device. There will be a log message with the code needed to add the current device to the list of test devices. For example:

(String testDevice)

この部分「だけ」をみて、TelephonyManagerつかってgetDeviceID呼べばいいのか?

とか思って実際にそんなソース書いてテストモードに移行できずに悩んでいたのは秘密

で、肝心のドキュメントはこちら
http://code.google.com/intl/ja/mobile/ads/docs/android/intermediate.html#addtestdevice

実にソースを含め5行でまとまっています。

5行でね…


レイアウト自前実装の場合はもう書かなくてもいいですよね。

loadAdの前にaddTestDeviceを呼んでやればOKです。

てかさー、testDeviceのIDですか?

取得できるようなAPIとか開けておいて欲しいです…


おまけ debuggable trueの場合のみテストモード
実際やるとしたらこんなソースになるんでしょうか。

public void onCreate( Bundle savedInstanceState) {

 super.onCreate( savedInstanceState);
 setContentView( R.layout.main);

 initAdmobLoadResView();

}


private void initAdmobLoadResView() {

 AdView adView = (AdView) findViewById( R.id.adView);
 AdRequest adRequest = new AdRequest();

 // debuggable が trueの場合
 if( ApplicationInfo.FLAG_DEBUGGABLE == (ApplicationInfo.FLAG_DEBUGGABLE & getApplicationInfo().flags) ){

  adRequest.addTestDevice( "hogehoge");

  if( adRequest.isTestDevice( this)){

   Log.d( AdRequest.LOGTAG, "test");
   adView.loadAd( adRequest);

  }else{

   Log.d( AdRequest.LOGTAG, "not test");
  }

 }else{
  adView.loadAd( adRequest);
 }
}

次回は
・適当に配置したら色々困って解決策募集中
です。

2011/06/15現在答え見つかってないので、誰か教えてくださいな。

AdMobを使ってみた

AdMobといえば、よくあるAndroidアプリの上とかにくっついている広告です。

アプリを提供したいけど…とても有料アプリとしては使ってくれなそう…
でも収入も得たい!

そうなるとこういった選択肢しかないでしょうね。





私の動作確認環境

端末:X06HT(Desire)
OS:2.2(公式っつーかrootも取ってない)
AdMobSDK:Ver 4.1.0

特にAdMobのSDKのバージョンが古いと、全くもって導入方法が変わるので注意





必須準備

SDKの用意…
AdMob行って登録して適当に貰ってきましょう。
ついでにパブリッシャーIDなるものも適当に貰ってきましょう。
http://jp.admob.com/

AdMob SDK download とかでググれば、親切丁寧に教えてくれているサイトも見つかるかと。


続いて…以下の通りに作業を進めるだけw
http://code.google.com/intl/ja/mobile/ads/docs/android/fundamentals.html

ここで忘れてはいけないのは以下二つ
http://code.google.com/intl/ja/mobile/ads/docs/android/fundamentals.html#adactivity
http://code.google.com/intl/ja/mobile/ads/docs/android/fundamentals.html#permissions

Permissionの設定は、広告データ取ってきたり広告に飛んだりするので
当然必要ですが、思わず忘れてしまいそうなのが、
com.google.ads.AdActivity というアクティビティを宣言すること
だと思います。

なお、上記のドキュメントの通りきっちり書きましょう。
AdActivityを宣言し忘れたり、configChangesの内容を端折ったりすると、コンパイル段階でエラーになりましたw





XMLだけで表示する場合

http://code.google.com/intl/ja/mobile/ads/docs/android/banner_xml.html#defineadview

上記の 「Defining a com.google.ads.AdView」の項目にあるXMLをコピって終了。
コピった先がmain.xmlとかなら、あとはsetContextView( R.layout.main)を適当に呼べばよし。
ads:adUnitIdには、AdMobで発行されたパブリッシャーIDを突っ込むのを忘れずに。





自前実装

http://code.google.com/intl/ja/mobile/ads/docs/android/fundamentals.html#addadview

といってもXMLで書くのをソースで書いて、loadAdを呼ぶだけ。
本当に上記の通り。
AdViewを生成するときに、コンストラクタにAdMobで発行されたパブリッシャーIDを突っ込むのを忘れずに。





上記を実行すると見たことのある、あの広告画面が表示されます。
メデタシメデタシ

とは終わらない…

ここまでの内容だと、本当にドキュメントを流し読みすりゃ出来る内容です。
ぶっちゃけAdMob的に問題だろ?と思うのは…

テストモードに関しての記述が少なすぎること。

てっきりAdMobのサイト上で設定するとかMarketに登録されるまでテスト広告がでるんでしょ?
とか思ってましたよ。


というわけで次は肝心のテストモードに関してです。

2011年4月16日土曜日

SDカードにインストール可能なアプリを作る

今月のアクセス履歴見たら、10%近くがGoogle先生のBOTでした。

なぜ?w


2.2からサポートされていているし、どうやって作るんだろうと思っていた
・SDカードにインストール(移動可能)なアプリ


ぐぐったら答えが出てきましたw

techboosterさんの記事



上記記事の通りでした。

internalOnly : 内蔵メモリへのみインストール
auto : 内蔵メモリにインストール。SDカードに移動可能
preferExternal : SDカードにインストール。もちろん内蔵メモリに移動可能

ってのをとりあえず確認。メデタシメデタシ。

sdcard_inst.png


ウィジェットやら無いし、常時起動でもないし~ みたいなものは
とりあえずautoにしておけば良さそうですね。


つっても俺が個人で作って喜ぶアプリなんてサイズ的にもたかがしれていますがww

そういえば preferExternal に設定してあるアプリって見たことないな…

2011年2月6日日曜日

マッシュルームアプリとかキャンディアプリを作ってみた

やっぱりAndroidのIMEはシメジですよね~
いつも大変お世話になっております。

ところで最近の和製Android端末なら赤外線モジュール積んでいるんで、アドレス交換とかも楽ですが、
よく考えなくてもDesireさんにはそんなモノありません(´;ω;`)

で、そうするとどうなるか。

  • 昔ながらのメールで連絡先送るね~
になります。

よね?

そう、自分の電話番号をマッシュルームで呼び出せば便利じゃね?

ってことで今更ながら、マッシュルームアプリを作ってみました。


細かい詳細はadamrockerさんのサイトへ

まずは、上記サイトを読んだ上で、作ってくれている「MushroomTemplate」を参考に…というかほぼコピーというか…

public class MyInfo extends Activity {

 private static final String ACTION_INTERCEPT = "com.adamrocker.android.simeji.ACTION_INTERCEPT";
 private static final String REPLACE_KEY = "replace_key";

 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  Intent it = getIntent();
  String action = it.getAction();
  // Simejiからの呼び出し
  if (action != null && ACTION_INTERCEPT.equals(action)) {
   replace( _getMyTelNum());

   return;
  }
  //setContentView(R.layout.main);
  finish();
 }


 /**
  * 元の文字を置き換える
  * @param result Replacing string
  */
 private void replace(String result) {
  Intent data = new Intent();
  data.putExtra(REPLACE_KEY, result);
  setResult(RESULT_OK, data);
  finish();
 }

 /**
  * 自分の電話番号取得
  * @return 電話番号文字列
  */
 private String _getMyTelNum(){
  TelephonyManager ts = null;
  String returnStr = null;

  // エラーチェックとかはとりあえず無視
  ts = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
  returnStr = ts.getLine1Number();

  return returnStr;
 }

}

もとの文字列を置き換えるもなにも、欲しいのは電話番号だけなので、入力されてきた文字列なんて無視です。(ぉぃ)

ちゃんと作るなら、メソッド内でエラーチェックしたりなんだりしますが、メンドイのでその辺も放置

Androidで自分の電話番号を取得する為にはTelephonyManagerを使うため、マニフェストに
・< uses-permission android:name="android.permission.READ_PHONE_STATE" />
の記述(パーミッションの指定)を忘れずに

とりあえず実際に動かしてみます。

とりあえず何か入力する画面にカーソルを合わせ…



おもむろにシメジボタンを押すと、味気ない標準のアイコンとセンスのないアプリ名Topに出てくるw



とりあえず押して見るとちゃんと電話番号が!



流石にこれだけじゃつまんないので、ついでにキャンディー化してみましょう。

public class MyInfo extends Activity {

 // マッシュルーム
 private static final String ACTION_INTERCEPT = "com.adamrocker.android.simeji.ACTION_INTERCEPT";
 private static final String REPLACE_KEY = "replace_key";

 // キャンディ
 private static final String ACTION_INJECTION = "com.adamrocker.android.simeji.ACTION_INJECTION";
 private static final String CANDITATE_KYE = "candidate_key";

 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  Intent it = getIntent();
  String action = it.getAction();
  // Simejiからの呼び出し
  if (action != null && ACTION_INTERCEPT.equals(action)) {
   replace( _getMyTelNum());
   return;

  }else if(action != null && ACTION_INJECTION.equals(action)){
   _canditate();
   return;
  }
  //setContentView(R.layout.main);
  finish();
 }

 // 中略

 /**
  * 文字配列の返却
  */
 private void _canditate(){
  ArrayList telnums = null;
  JSONArray jsons = null;
  Intent data = new Intent();
  String myInfo = null;
  String telhead = null;
  String telbody1 = null;
  String telbody2 = null;

  myInfo = _getMyTelNum();

  telhead = myInfo.substring(0, 3);
  telbody1 = myInfo.substring(3, 7);
  telbody2 = myInfo.substring(7, 11);

  telnums = new ArrayList();
  telnums.add(myInfo);
  telnums.add(telhead + "-" + telbody1 + telbody2);
  telnums.add(telhead + "-" + telbody1 + "-" + telbody2);

  jsons = new JSONArray(telnums);
  data.putExtra(CANDITATE_KYE, jsons.toString());

  setResult(RESULT_OK, data);
  finish();//キャンディーを終了して複数の文字をSimejiに渡す
 }

}

上の方のソースにちょっと足して変更しただけ。

あとはマニフェスト上にキャンディ用のインテントフィルタを追加するのを忘れずに

何か入力する画面にカーソルを合わせ…



おもむろに「+」ボタン(「ま」の下)を押すと、さっきと同じアプリ名がTopに出てくる



とりあえず押して見ると今度は変換候補に電話番号が!




とまあ、すごく簡単にIME拡張出来ましたw
これもステキなIMEソフトを無料で提供してくれているadamrockerさんのお陰です。

そしてやっぱりAndroidは面白い。

家でプログラムヤル気になるしね。

どう考えても BREWとかBREWとかBREWとか…は…、家でヤル気になんね~し…

→コンパイル環境も家にはないけど…

2011年1月15日土曜日

ロック画面より上にActivityを表示したい!

マーケットにあるステキなメディアプレーヤーさん方
使っていて疑問に思ったのが、どうやってロック画面より上に表示してるんだ?

と思ってググったら見つかったので試してみます。

明日の鍵 「スクリーンロックを解除する方法」
superdry memorandom :-D [memo]Androidアプリ側からスクリーンロックを解除し、画面表示する方法

どうやら、KeyguardManager.KeyguardLockのdisableKeyguard()で、ロック解除。
reenableKeyguard()で再度有効化、のようです。

何も考えず上記ブログで作られていた クラスを丸パクリし、ResumeでgetUnlock/PauseでreleaseUnlockを呼びました。
そして

  • アプリ起動
  • 電源ボタンを押下
  • 結果として、確かにロック画面より上にActivityが表示される
となりました。


が、所詮丸パクリのため、一連の動作一回目の電源ボタン押下後に、 何もしていないのに液晶が明るくなって、Activityが表示されます。

二回目以降は問題ないのですが…

どうやら、起動直後のOnResume時にWakelock.acquire(); を読んでいるのが駄目なようです。
ログを見るかぎりは

  • 起動直後のOnResumeでacquire()(※)
  • 電源ボタン押下時のOnPauseで、Wakelock.release()
と呼ばれているのにも関わらず、その後
  • なぜかOnResumeが走る
なんでかは分からないけど※の処理がスタックされていて、OnPauseの直後に呼ばれているのだろう…

なので借り物のクラスをキーロック時のみacquireを呼ぶように修正
これでとりあえずはOKでした。

 public void getUnlock() {

  // acquire wake lock
  _log( "getUnlock:called");

  PowerManager pm = (PowerManager) mContext.getSystemService( Context.POWER_SERVICE);
  mWakelock = pm.newWakeLock( PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "ScreenLockManager");
  // 今回の呼び出し環境だと、ここにあると困る
  // mWakelock.acquire();

  // unlock screen
  KeyguardManager km = (KeyguardManager) mContext.getSystemService( Context.KEYGUARD_SERVICE);
  mKeyguardLock = km.newKeyguardLock( "ScreenLockManager");
  if( km.inKeyguardRestrictedInputMode()){
   // ここに移動
   mWakelock.acquire();
   mKeyguardLock.disableKeyguard();
   isScreenLock = true;
   _log( "getUnlock: disableKeyguard");
  }else{
   isScreenLock = false;
  }
 }

と、ここまでやってみて一つの疑問が。

PowerManagerでWakeLockを制御する必要は本当にあるのか??

ってことです。

上記以外にも、画面を明るくするために…として呼んでいるソースを見かけるのですが、
そもそも

  • スリープ(というのかな?画面暗転)状態で電源ボタン押下すれば、そもそも明るくなる
わけで…

というわけでPowerManager.WakeLock関連全コメントアウトして試してみました。

結果として、何の問題もありませんでした…
個人的には、何故か暗転したままってのを期待したのですが、普通に動いてしまった…

package com.omokageru.ak.screenunlocktest;

import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;

public class ScreenUnlockTest extends Activity {

 private ScreenLockManager slm = null;


 /** Called when the activity is first created. */
 @Override
 public void onCreate( Bundle savedInstanceState) {

  super.onCreate( savedInstanceState);
  setContentView( R.layout.main);
  _log( "onCreate");
  slm = new ScreenLockManager( this);
 }


 @Override
 protected void onDestroy() {

  // TODO 自動生成されたメソッド・スタブ
  super.onDestroy();
  _log( "onDestroy");
 }


 @Override
 protected void onPause() {

  // TODO 自動生成されたメソッド・スタブ
  super.onPause();
  _log( "onPause");
  slm.releaseUnlock();
 }


 @Override
 protected void onResume() {

  // TODO 自動生成されたメソッド・スタブ
  super.onResume();
  _log( "onResume");
  slm.getUnlock();
 }


 private void _log( String str) {

  Log.d( "ScreenUnlockTest Activity", str);
 }
}


/**
 * 以下パクリもの
 */
class ScreenLockManager {

 private Context       mContext;
 private KeyguardManager.KeyguardLock mKeyguardLock;
 private boolean       isScreenLock;


 public ScreenLockManager( Context context) {

  mContext = context;
 }


 public void getUnlock() {

  _log( "getUnlock:called");

  // unlock screen
  KeyguardManager km = (KeyguardManager) mContext.getSystemService( Context.KEYGUARD_SERVICE);
  mKeyguardLock = km.newKeyguardLock( "ScreenLockManager");
  if( km.inKeyguardRestrictedInputMode()){
   // ここに移動
   mKeyguardLock.disableKeyguard();
   isScreenLock = true;
   _log( "getUnlock: disableKeyguard");
  }else{
   isScreenLock = false;
  }
 }


 public void releaseUnlock() {

  _log( "releaseUnlock:called");
  // release screen
  if( isScreenLock){
   mKeyguardLock.reenableKeyguard();
   isScreenLock = false;
   _log( "releaseUnlock: reenableKeyguard");
  }
 }


 private void _log( String str) {

  Log.d( "ScreenLockManager", str);
 }

}

う~ん、ロック解除に関してPowerManagerの利用はどんな意味があるのだろう?

もうちょっと(覚えていたら)調べてみようかな

2010年7月3日土曜日

Desire入手!


じゃじゃ~ん、というほどのものでもありませんが、Desire入手しました。

会社名義で。





desire





主な比較対象がHT-03Aなので、比較するのも申し訳ない感じですが、非常に良い感じ。

そのほか会社に転がっているXperiaと比較しても断然Desireですね。

ただ、ハードの作り込みは中の人曰くXperiaのほうがずっと良く出来ており、何かのベンチマークではXperia >> Desireらしいです。

もしかすると、いつ来るかわからないアップデートでXperiaの評価が凄く上がるかもしれません。

ただ、普通の使い勝手とか考えると、やっぱりXperiaという選択肢は無いように思えます。

もし現状でAndroid端末が欲しいならDesire一択ですね。

もしくは次の製品を待つ!

スマートフォンが欲しいならiPhoneなのかな…

まあ、私林檎製品買う気ないのであれですけど…





そういえばXperiaのドライバってダウンロードできるようになったんかな?

4月末の時は公式から消えていた気がするけど、現状はどうなんでしょ?
ちなみにDesireの場合は、
HTC SyncってのをPCにインストールし、DesireをPCに繋いで、DesireからHTC sync を選択すればインストールされます。(XP sp3で確認)

ただ、なんかDesire側は接続出来なかったよ!とか言ってきますが、ドライバはインストールされるようです。

インストール後はADBを通じてちゃんと認識されていました(eclipseにて確認)





え?あいえすぜろいち?なにそのメガネケース?




2010年5月22日土曜日

今更ながらDesireを見てきた

最近全く家でプログラム書いてない…
まあ、その分会社で書いているので問題ないですがwww

さて、今日何となく電気屋に行ってフラっと立ち寄ったら
HTC Desire が置いてあり、触れました^^

先日OS2.2もGoogleIOで発表されたので、何処と無く今更感がありますが…


正直な感想

Desire >>>>>>>> Xperia >= HT-03A

です。

XperiaもHT-03Aも(会社名義ですが)保有しています。
もちろんXperiaのハードスペックには全く不満はありません。
CPUだってXperiaとDesireは同じスナップドラゴン。

だけど体感が全然違う、比べるのも失礼なくらい違う。

HT-03Aは既にちょっと前の携帯なので、比較に入れるのはどうかと思いますが、
まあ、メインで使っているので…

個人的に一番使っている(使い慣れている)というのもあり、
Xperia >= HT-03A としました。
正直、普段使うようなアプリでの動作感の差は、XperiaとHT-03Aでそんなに違うとは思えません。

そこを踏まえると、やはりOSのバージョンの差が大きい気がします。
XperiaもわざわざあそこまでOSをカスタマイズするメリットがあるのか気になります。
docomoというキャリアが問題なのか、ソニエリが問題なのか。
PCを買う時だって、(ハードスペックは十分なのに)OSを選択するのに今更98やXPを選ばず、7選ぶでしょ?
iPhone買う時だって今更3G買わずに3GS買うでしょ?(アップル商品買った事ないけど)
1.6を選ぶ理由は無いと思うんですけどねぇ…
なんだか時代遅れですよね。
��もちろんセキュリティ的な問題や、キャリアの基準等あるんでしょうが。)

iPhoneは使ったこと無いので、想像で勝手に比較するとこんな感じでは?

Desire > iPhone 3GS >>>>>>>> Xperia >= HT-03A

Android 2.2 は更に速度向上とのことですので、今後が更に気になります。
HTC EVO 出ないかな~

来月には次世代iPhoneの発表があるはずですし、スマホ市場の行方は気になりますね。



え?IS-01?
なにそのキャベツロイド。

2010年2月14日日曜日

特定のタップイベント時にMapViewから座標を取得する

意外と試行錯誤したので記録にしておきます。

というワケでまずは特定のタップイベントの取得に関して・・・。
は、以下のサイトを参考にしました。

hoge256blogさん
adakodaさん

上記のサイトを見て、なんも考えずに
・Activity → MapActivity
ぐらいの変更でとりあえずなんとかなるだろう・・・。
ってのがまず甘かった。


public class Main extends MapActivity implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {

 private GestureDetector gesture;
 private MapView mapView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  protected void onCreate(Bundle icicle) {
   // TODO 自動生成されたメソッド・スタブ
   super.onCreate(icicle);
   setContentView(R.layout.main);

   mapView = (MapView)findViewById(R.id.map);
   mapView.setEnabled(true);
   mapView.setClickable(true);
   mapView.setBuiltInZoomControls(true);
   mapView.setSatellite(false);

   gesture = new GestureDetector(this);

   mapView.invalidate();

  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
   gesture.onTouchEvent(event);
   return super.onTouchEvent(event);
  }
  // 以下onDoubleTapとか軒並み略

いわずともあれです。
MapView上をタップしても「onTouchEvent」にイベントが来ません。
上記には書いていませんが、ところ狭しとログかけまくってます。
MapViewが全て処理してくれているため、当たり前といえば当たり前です。

じゃあ、上記mapViewになんかタッチを処理しそうなリスナーとか登録すりゃいいんじゃね?
と考え、eclipseの力を発揮(?)させ、setOnTouchListenerなるものを発見。
ってことで以下に修正。

public class Main extends MapActivity implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {

 private GestureDetector gesture;
 private MapView mapView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  protected void onCreate(Bundle icicle) {
   // TODO 自動生成されたメソッド・スタブ
   super.onCreate(icicle);
   setContentView(R.layout.main);

   mapView = (MapView)findViewById(R.id.map);
   mapView.setEnabled(true);
   mapView.setClickable(true);
   mapView.setBuiltInZoomControls(true);
   mapView.setSatellite(false);

   gesture = new GestureDetector(this);

   // 追加
   mapView.setOnTouchListener( new OnTouchListener() {

    public boolean onTouch(View v, MotionEvent event) {
     // TODO 自動生成されたメソッド・スタブ
     gesture.onTouchEvent(event);
     return false;
    }
   });

   mapView.invalidate();

  }

  /* コメントアウト
  @Override
  public boolean onTouchEvent(MotionEvent event) {
   gesture.onTouchEvent(event);
   return super.onTouchEvent(event);
  }
   */
  // 以下onDoubleTapとか軒並み略

結果、一回しかイベント取れないし。
ちょっとやってみたところ、どうやら「onTouch」で一度でもfalseを返すともうダメ。
普通false返したら、superクラスというか、そもそも備えた処理してくれるんじゃないんですか。
自前で処理を全部書いて、全てtrueを返すなら運用出来そうですが、MapViewですよ。
もともとある機能の分まで実装できるかっての。
ていうかできるくらい能力ありゃ苦労しませんよねw

そこで考えたのが、「流石にイベントが来ていない訳ないだろ」ということ。
横取りしちまえwwww ってこの辺の発想はVCのサブクラス化とかから来てるのかな・・・。
ということで次のように修正。

//略

 @Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
  //TODO 自動生成されたメソッド・スタブ
  gesture.onTouchEvent(ev);
  return super.dispatchTouchEvent(ev);
 }

 @Override
 public void onCreate(Bundle savedInstanceState) {

  //略

  /* コメントアウト
  //追加
  mapView.setOnTouchListener( new OnTouchListener() {

   public boolean onTouch(View v, MotionEvent event) {
    //TODO 自動生成されたメソッド・スタブ
    gesture.onTouchEvent(event);
    return false;
   }
  });
   */
  mapView.invalidate();

 }

 //以下onDoubleTapとか軒並み略

あくまでイベントが知りたいだけなので、処理するしないに関わらず、本来の処理に渡しています。
こうすることで

  • イベント取れる
  • MapViewそのものの処理は邪魔しない(はず)
と考えました。

だが幸せは長く続かなかった

とりあえず長押し処理に何かを付け加えましょう。
わかりやすいので長押しした場所を中央に移動するとかにしました。

public void onLongPress(MotionEvent e) {
 // TODO 自動生成されたメソッド・スタブ
 // 以下を追加
 // タップされた画面座標をmapView上の緯度経度に変換
 GeoPoint temp = mapView.getProjection().fromPixels((int)e.getX(), (int)e.getY());
 _log("GeoPoint lat:" + temp.getLatitudeE6() + " long:" + temp.getLongitudeE6());
 mapView.getController().animateTo(temp);
 mapView.postInvalidate();
}

こちらも一見良さそうですが、良く考えるとmapViewに渡している画面座標って、たぶんMapActivityが管理している幅になりますよね。
当然これ動作させると、なんだか長押しした場所がView中央よりやや北側になります。
ちなみに以下のような画面ならなおのこと・・・。

この場合富士山を長押ししたところで、富士山がView中央にくることはない。


じゃあ、あとは何があるかと考えると、
・MapViewを拡張する
くらいかなと思っていたところでこんなのを発見
日本アンドロイドの会
該当ページの一番下の方で「タップされた位置にアイコンを描画する」って項目があるじゃないですか。
しかも後で気がついたんですがAndroidプログラミング入門にもそれっぽいこと書いてあるし・・・。一体どこ読んでんだ?っていわれそうです。
ってことでOverlayを試してみましょう。

public class Main extends MapActivity {

 private MapView mapView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  protected void onCreate(Bundle icicle) {
   // TODO 自動生成されたメソッド・スタブ
   super.onCreate(icicle);
   setContentView(R.layout.main);

   mapView = (MapView)findViewById(R.id.map);
   mapView.setEnabled(true);
   mapView.setClickable(true);
   mapView.setBuiltInZoomControls(true);
   mapView.setSatellite(false);
   mapView.getOverlays().add( new MyOverlay(mapView));
   mapView.invalidate();

  }

  // 略
 }

 /**
  * Overlayクラス追加
  */
 class MyOverlay extends Overlay implements
 GestureDetector.OnDoubleTapListener,
 GestureDetector.OnGestureListener{

  private GestureDetector gesture = new GestureDetector(this);
  private MapView parent;

  MyOverlay(MapView mapView){
   parent = mapView;
  }

  @Override
  public boolean onTouchEvent(MotionEvent e, MapView mapView) {
   // TODO 自動生成されたメソッド・スタブ
   gesture.onTouchEvent(e);
   return super.onTouchEvent(e, mapView);
  }


  // 略

  public void onLongPress(MotionEvent e) {
   // TODO 自動生成されたメソッド・スタブ
   GeoPoint temp = parent.getProjection().fromPixels((int)e.getX(), (int)e.getY());
   parent.getController().animateTo(temp);
   parent.postInvalidate();

  }

  // 略

 }

これで無事MapView上で長押しした場所が、Viewの真ん中に来ることになりました。
まさかOverlayにこんな使い方があるとは・・・。
てっきり描画だけかと思っていましたし。
いや、正しい使い方かどうかは分かりませんがね。
まあ、考え方によってはイベント処理のみを追加したい場合に、こんなOverlayクラスを追加するだけでできるので、既存の処理への影響が少ない!かもしれないというメリットがありますかね?