2011年1月27日木曜日



Android2.3で搭載されたNFCの、P2Pモードについて調べました。


P2Pモードとは、NFCデバイス同士がP2P通信を行うためのモードです。このモードで動作するNFCデバイス同士は、Bluetoothのペアリングのような煩雑な手順を経ずにデータの送受信が可能になります。


Android2.3のNFCはデフォルトで、ICカードリーターとして振舞うリーダーモードとして動作しています。そのため、P2Pモードとして動作するようにNFCのサービスに指示する必要があります。


しかし、結論から言うとタイトルの通り、Android2.3はP2Pモードになることができません。次のAOSPのコミットメッセージのとおり、リリース段階でP2Pモードは無効になってしまったようです。









ざんねん。。








文責:技術部 ますい





Android 2.3 のNFCはP2Pモードが使えない



Android2.3で搭載されたNFCの、P2Pモードについて調べました。


P2Pモードとは、NFCデバイス同士がP2P通信を行うためのモードです。このモードで動作するNFCデバイス同士は、Bluetoothのペアリングのような煩雑な手順を経ずにデータの送受信が可能になります。


Android2.3のNFCはデフォルトで、ICカードリーターとして振舞うリーダーモードとして動作しています。そのため、P2Pモードとして動作するようにNFCのサービスに指示する必要があります。


しかし、結論から言うとタイトルの通り、Android2.3はP2Pモードになることができません。次のAOSPのコミットメッセージのとおり、リリース段階でP2Pモードは無効になってしまったようです。









ざんねん。。








文責:技術部 ますい





2011年1月11日火曜日



f:id:bs-android:20110107175909j:image,w400,h300
[Nexus SからHTC Desireに電話してる様子]


Android2.3(gingerbread)で追加されたAPIの一つであるSIP APIを試してみました。
Android SDK 2.3に同梱されているSipDemoというプロジェクトを使ってどの様な手順でSIP通話を実現しているか解説します。


実施環境


発信側:
  端末:Nexus S
  アプリ:SipDemo


着信側:
  端末:HTC Desire (android2.2)
  アプリ:Sipdroid version 2.0.1 beta





準備


まずSIPで通話する為にSIPアカウントを取得しました。Android2.3ではSIP通話の為の各種APIを提供してくれますが、SIPアカウントやSIPプロバイダまでは提供してくれません。いっそGoogleがSIPプロバイダ買収して統合しちゃってくれるとうれしいんですが・・・。
今回はiptel.orgという無料のSIPプロバイダを利用しました。
ここで発信、着信用の二つのアカウントを作成しました。iptel.orgのページの下部の「Subscribe!」から新規アカウントの作成ができます。





通話してみる


とりあえず通話をしてみます。
SipDemoをビルドしてNexus Sにインストールし、起動すると以下の様な画面が現れます。
f:id:bs-android:20110110171031p:image,w320,h480


自分のSIPアカウントを設定していない場合は以下のダイアログが表示されます。
OKを選択するとアカウント設定画面が開きます。
f:id:bs-android:20110110163805p:image,w320,h480


アカウント設定画面で、SIPアカウント、パスワード、SIPプロバイダのドメインを設定します。
f:id:bs-android:20110110163804p:image,w320,h480


設定が完了すると、SIPプロバイダにREGISTERを試みます。成功すると、「Ready」の文字列が表示されます。
f:id:bs-android:20110110163803p:image,w320,h480


「Ready」の状態でメニューを表示し、「Call someone」を選択します。
f:id:bs-android:20110110163802p:image,w320,h480


発信する相手先を入力するダイアログが表示されます。
ここに相手のSIPURI(例:moge@sip.hoge.jp)を入力し、「OK」ボタンを押下すると相手にINVAITEします。
f:id:bs-android:20110110163801p:image,w320,h480


通話に成功した場合、マイクをタッチしている間、自分の声を相手に送信します。


f:id:bs-android:20110110164442p:image,w320,h480








SipDemoおよびAPIの説明


■SipDemoの場所
 「Android SDK Samples for Android API 9, revision 1」がインストールされていれば下記の場所にプロジェクトがあるはずです。


    <android-sdk>/samples/android-9/SipDemo





■SipDemoの構成
 SipDemoのファイル構成です。XMLファイル類は特に気にするようなものは無かったので割愛します。


  IncomingCallReceiver.java //着信を受けるBroadcastReceiver
  SipSettings.java //SIPプロファイルの設定を行うPreferenceActivity
  WalkieTalkieActivity.java //SIPプロファイルの初期化、REGISTER、発信等を行うActivity





■AndroidManifest.xml
 AndroidでSIPを利用する場合、AndroidManifest.xmlに以下のuses-permissionを追加してあげる必要があります。


  android.permission.USE_SIP
  android.permission.INTERNET
  android.permission.ACCESS_WIFI_STATE
  android.permission.WAKE_LOCK
  android.permission.RECORD_AUDIO
  android.permission.MODIFY_AUDIO_SETTINGS


 SipDemoではこのうち「android.permission.MODIFY_AUDIO_SETTINGS」の設定が抜けていますので追記してやる必要があります。「android.permission.MODIFY_AUDIO_SETTINGS」が無くても動作しますが、音声設定周りが無効になってしまいます。





■利用するクラス
 SIP通話を行うために利用するクラスです。以下のクラスを使うだけでSIP通話を実現する事ができます。


  android.net.sip.SipManager       //SIPプロバイダへのREGISTERや発信、着信を管理するクラス
  android.net.sip.SipProfile        //自分のSIPプロファイル
  android.net.sip.SipRegistrationListener //SIPプロバイダにREGISTERする際のリスナ
  android.net.sip.SipAudioCall      //SIP通話の音声を管理する
  android.net.sip.SipAudioCall.Listener  //SIP通話の確立や切断、エラー等のリスナ
  android.net.sip.SipException      //SIP周りの例外








発信までの手順


 発信の処理はWalkieTalkieActivity.javaで行っています。




  1. SipManagerを作成する

  2. SipProfileを作成する

  3. SipManager#open(String, SipRegistrationListener)でSIPプロバイダにREGISTERする

  4. SipManager#makeAudioCall(String ,String, SipAudioCall.Listener, int)で相手先に電話をかける

  5. SipAudioCall.Listener#onCallEstablished(SipAudioCall)で音声通話を開始する





1.SipManagerを作成する

 SipManagerはWalkieTalkieActivity.javaの106行目、initializeManager()で作成しています。
 単純にメンバ変数managerがnullの場合SipManager#newInstance(Context)を呼び出しているだけです。



public void initializeManager() {
if(manager == null) {
manager = SipManager.newInstance(this);
}
initializeLocalProfile();
}








2.SipProfileを作成する

 WalkieTalkieActivity.javaの138行目、initializeLocalProfile()内でSipProfileを作成しています。
 SipProfileの作成はSipProfile.Builderクラスを使って行います。コンストラクタにSIPプロバイダで取得したアカウント名、SIPプロバイダのドメイン名を設定します。(例:hoge@sip.moge.jpの場合、「hoge」がアカウント名で「sip.moge.jp」がドメイン名となります。)
 BuilderをnewしたあとsetPassword(String)でアカウントのパスワードをセットします。最後にSipProfile.Builder#build()を呼び出しSipProfileを作成します。



SipProfile.Builder builder = new SipProfile.Builder(username, domain);
builder.setPassword(password);
me = builder.build(); //meはメンバ変数。SipProfile型








3.SipManager#open(SipProfile, String, SipRegistrationListener)でSIPプロバイダにREGISTERする

 WalkieTalkieActivity.javaの142行目、initializeLocalProfile()内でSipProfileを作成した後、SipManager#openでSIPプロバイダに対してREGISTERを行います。
 openする際にSipRegistrationListenerでREGISTERの結果を受け取ります。何故かリスナのセットはSipManager#openを呼び出した後にしろ、との事。
 SipRegistrationListenerは以下の3つのコールバックを持ちます。




  • public void onRegistering(String localProfileUri)                       //REGISTER中

  • public void onRegistrationDone(String localProfileUri, long expiryTime)            //REGISTER成功

  • public void onRegistrationFailed(String localProfileUri, int errorCode, String errorMessage)  //REGISTER失敗


 SipDemoではこれらのコールバック時に、画面に各種状態の文字列を表示させています。
 SipManager#openをする際にPendingIntentをセットしていますが、これは着信の為に利用します。説明は後ほどします。



Intent i = new Intent();i.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, Intent.FILL_IN_DATA);
manager.open(me, pi, null);


// This listener must be added AFTER manager.open is called,
// Otherwise the methods aren't guaranteed to fire.

manager.setRegistrationListener(me.getUriString(), new SipRegistrationListener() {
public void onRegistering(String localProfileUri) {
updateStatus("Registering with SIP Server...");
}

public void onRegistrationDone(String localProfileUri, long expiryTime) {
updateStatus("Ready");
}

public void onRegistrationFailed(String localProfileUri, int errorCode,
String errorMessage) {
updateStatus("Registration failed. Please check settings.");
}
});









4.SipManager#makeAudioCall(String ,String, SipAudioCall.Listener, int)で相手先に電話をかける

 WalkieTalkieActivity.javaの197行目、initiateCall()内でSipManager#makeAudioCall(String, String, SipAudioCall.Listener, int)を呼び出します。引数は自分のSIPURI, 相手のSIPURI,SipAudioCall.Listener,タイムアウト秒です。
 SipAudioCall.Listenerはリスナという名前のくせにクラスです。コールバックの種類が結構多いのでinterfaceだと煩雑だと考えたのでしょうか?必要なコールバックだけオーバーライドして利用します。SipDemoでは相手が着信後200 OKを返し、通信が確立された際に呼び出されるonCallEstablished(SipAudioCall)と、通話を切断した場合に呼び出されるonCallEnded(SipAudioCall)をオーバーライドしています。



SipAudioCall.Listener listener = new SipAudioCall.Listener() {
// Much of the client's interaction with the SIP Stack will
// happen via listeners. Even making an outgoing call, don't
// forget to set up a listener to set things up once the call is established.
@Override
public void onCallEstablished(SipAudioCall call) {
call.startAudio();
call.setSpeakerMode(true);
call.toggleMute();
updateStatus(call);
}

@Override
public void onCallEnded(SipAudioCall call) {
updateStatus("Ready.");
}
};

call = manager.makeAudioCall(me.getUriString(), sipAddress, listener, 30);









5.SipAudioCall.Listener#onCallEstablished(SipAudioCall)で音声通話を開始する

 WalkieTalkieActivity.javaの202行目、SipAudioCall.Listener#onCallEstablished(SipAudioCall)でSipAudioCall#startAudio()を呼び出し音声通話を開始します。
 SipAudioCall#setSpeakerMode(boolean)にtrueをセットしています。これでハンズオンの状態で通話が開始されます。また、SipAudipCall#toggleMute()を呼び出しています。これで自分の音声はミュートされます。このミュートはタッチ処理の中で「タッチされている間だけオン」といった感じで実装されています。普通の電話の様な動作にしたい場合はcall.toggleMute()の行とタッチイベントの処理を削除するだけで実現できます。



@Override
public void onCallEstablished(SipAudioCall call) {
call.startAudio();
call.setSpeakerMode(true);
call.toggleMute();
updateStatus(call);
}











着信-通話までの手順


 着信はIncomingCallReceiver.javaで行います。IncomingCallReceiverはBroadcastReceiverを継承しており、WalkieTalkieActivityが実行されている間だけ着信を受けます。




  1. IncomingCallReceiverをシステムに登録する

  2. SipManagerに着信時に実行されるPendingIntentを登録する

  3. SipManagerを作成する

  4. SipProfileを作成する

  5. SipManager#open(String, SipRegistrationListener)でSIPプロバイダにREGISTERする

  6. IncomingCallReceiver#onReceive(Context, Intent)で通話を開始する





1.IncomingCallReceiverをシステムに登録する

 WalkieTalkieActivity.javaの69行目、onCreate(Bundle)内でIncomingCallReceiverをシステムに登録します。
 IntentFilterには"android.SipDemo.INCOMING_CALL"を指定しています。"android.SipDemo.INCOMING_CALL"のアクションを持つIntentがBroadcastされた場合に受け取る、という事です。



// Set up the intent filter. This will be used to fire an
// IncomingCallReceiver when someone calls the SIP address used by this
// application.
IntentFilter filter = new IntentFilter();
filter.addAction("android.SipDemo.INCOMING_CALL");
callReceiver = new IncomingCallReceiver();
this.registerReceiver(callReceiver, filter);











2.SipManagerに着信時に実行されるPendingIntentを登録する

 WalkieTalkieActivity.javaの142行目、initializeLocalProfile()内でPendingIntentを作成し、SipManager#openで登録しています。このPendingIntentは着信があった場合に実行されます。
 SipDemoでは"android.SipDemo.INCOMING_CALL"のアクションをBroadcastするPendingIntentを登録しています。このアクションはIncomingCallReceiverをシステムに登録する際に指定したIntentFilterと一致します。



Intent i = new Intent();i.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, Intent.FILL_IN_DATA);
manager.open(me, pi, null);





3.SipManagerを作成する

発信と同じなので省略


4.SipProfileを作成する

発信と同じなので省略


5.SipManager#open(String, SipRegistrationListener)でSIPプロバイダにREGISTERする

発信と同じなので省略





6.IncomingCallReceiver#onReceive(Context, Intent)で通話を開始する

 IncomingCallReceiver.javaの37行目,onReceive(Context, Intent)で着信の処理を行っています。
ContextをWalkieTalkieActivityにキャストし、SipManager#takeAudioCall(Intent, SipAudioCall.Listener)を呼び出しています。SipManager#takeAudioCallを実行したら、次にSipAudioCall.Listener#onRinging(SipAudioCall, SipProfile)が呼び出されます。おそらく内部で100 TRYING、180 RINGINGを発信側に返してonRinging(SipAudioCall, SipProfile)を呼び出しているのだと思います。ここでSipAudioCall#answerCall(int)を呼び出します。javadocを読んでもいまいち動作が不明ですが、200 OKを返しているのでは無いかと思います。onRingingの後にincamingCall.answerCall(30)を実行しています。未確認ですが、この二回目のanswerCallは不要なんじゃないかなーと思います。
 その後SipAudioCall#startAudio()で通話を開始しています。
 wtActivity.call = incomingCall;でWalkieTalkieActivityのメンバ変数にSipAudioCallを設定していますが、WalkieTalkieActivityを終了したり、メニューの「End Current Call」を選択した時にSipAudioCall#endCall()を実行させる為です。



@Override
public void onReceive(Context context, Intent intent) {
SipAudioCall incomingCall = null;
try {

SipAudioCall.Listener listener = new SipAudioCall.Listener() {
@Override
public void onRinging(SipAudioCall call, SipProfile caller) {
try {
call.answerCall(30);
} catch (Exception e) {
e.printStackTrace();
}
}
};

WalkieTalkieActivity wtActivity = (WalkieTalkieActivity) context;

incomingCall = wtActivity.manager.takeAudioCall(intent, listener);
incomingCall.answerCall(30);
incomingCall.startAudio();
incomingCall.setSpeakerMode(true);
if(incomingCall.isMuted()) {
incomingCall.toggleMute();
}

wtActivity.call = incomingCall;

wtActivity.updateStatus(incomingCall);

} catch (Exception e) {
if (incomingCall != null) {
incomingCall.close();
}
}
}












最後に


SIPに関する面倒なほとんどの処理をSIP APIが抽象化してくれました。
上記で説明した手順だけでSIP通話が行えます。SIP APIを使って、例えばAccountManagerに登録されているGMailのアドレスから自動的にSIPアカウント払い出しを行ってすぐ通話できるアプリ、とか割と簡単に作成できそうですね。



文責:技術部 八木 俊広 (新人/Androidアプリケーション技術者認定試験ベーシック合格(笑)/品川)





概説SipDemo(Android2.3 SIP APIを試す)



f:id:bs-android:20110107175909j:image,w400,h300
[Nexus SからHTC Desireに電話してる様子]


Android2.3(gingerbread)で追加されたAPIの一つであるSIP APIを試してみました。
Android SDK 2.3に同梱されているSipDemoというプロジェクトを使ってどの様な手順でSIP通話を実現しているか解説します。


実施環境


発信側:
  端末:Nexus S
  アプリ:SipDemo


着信側:
  端末:HTC Desire (android2.2)
  アプリ:Sipdroid version 2.0.1 beta





準備


まずSIPで通話する為にSIPアカウントを取得しました。Android2.3ではSIP通話の為の各種APIを提供してくれますが、SIPアカウントやSIPプロバイダまでは提供してくれません。いっそGoogleがSIPプロバイダ買収して統合しちゃってくれるとうれしいんですが・・・。
今回はiptel.orgという無料のSIPプロバイダを利用しました。
ここで発信、着信用の二つのアカウントを作成しました。iptel.orgのページの下部の「Subscribe!」から新規アカウントの作成ができます。





通話してみる


とりあえず通話をしてみます。
SipDemoをビルドしてNexus Sにインストールし、起動すると以下の様な画面が現れます。
f:id:bs-android:20110110171031p:image,w320,h480


自分のSIPアカウントを設定していない場合は以下のダイアログが表示されます。
OKを選択するとアカウント設定画面が開きます。
f:id:bs-android:20110110163805p:image,w320,h480


アカウント設定画面で、SIPアカウント、パスワード、SIPプロバイダのドメインを設定します。
f:id:bs-android:20110110163804p:image,w320,h480


設定が完了すると、SIPプロバイダにREGISTERを試みます。成功すると、「Ready」の文字列が表示されます。
f:id:bs-android:20110110163803p:image,w320,h480


「Ready」の状態でメニューを表示し、「Call someone」を選択します。
f:id:bs-android:20110110163802p:image,w320,h480


発信する相手先を入力するダイアログが表示されます。
ここに相手のSIPURI(例:moge@sip.hoge.jp)を入力し、「OK」ボタンを押下すると相手にINVAITEします。
f:id:bs-android:20110110163801p:image,w320,h480


通話に成功した場合、マイクをタッチしている間、自分の声を相手に送信します。


f:id:bs-android:20110110164442p:image,w320,h480








SipDemoおよびAPIの説明


■SipDemoの場所
 「Android SDK Samples for Android API 9, revision 1」がインストールされていれば下記の場所にプロジェクトがあるはずです。


    <android-sdk>/samples/android-9/SipDemo





■SipDemoの構成
 SipDemoのファイル構成です。XMLファイル類は特に気にするようなものは無かったので割愛します。


  IncomingCallReceiver.java //着信を受けるBroadcastReceiver
  SipSettings.java //SIPプロファイルの設定を行うPreferenceActivity
  WalkieTalkieActivity.java //SIPプロファイルの初期化、REGISTER、発信等を行うActivity





■AndroidManifest.xml
 AndroidでSIPを利用する場合、AndroidManifest.xmlに以下のuses-permissionを追加してあげる必要があります。


  android.permission.USE_SIP
  android.permission.INTERNET
  android.permission.ACCESS_WIFI_STATE
  android.permission.WAKE_LOCK
  android.permission.RECORD_AUDIO
  android.permission.MODIFY_AUDIO_SETTINGS


 SipDemoではこのうち「android.permission.MODIFY_AUDIO_SETTINGS」の設定が抜けていますので追記してやる必要があります。「android.permission.MODIFY_AUDIO_SETTINGS」が無くても動作しますが、音声設定周りが無効になってしまいます。





■利用するクラス
 SIP通話を行うために利用するクラスです。以下のクラスを使うだけでSIP通話を実現する事ができます。


  android.net.sip.SipManager       //SIPプロバイダへのREGISTERや発信、着信を管理するクラス
  android.net.sip.SipProfile        //自分のSIPプロファイル
  android.net.sip.SipRegistrationListener //SIPプロバイダにREGISTERする際のリスナ
  android.net.sip.SipAudioCall      //SIP通話の音声を管理する
  android.net.sip.SipAudioCall.Listener  //SIP通話の確立や切断、エラー等のリスナ
  android.net.sip.SipException      //SIP周りの例外








発信までの手順


 発信の処理はWalkieTalkieActivity.javaで行っています。




  1. SipManagerを作成する

  2. SipProfileを作成する

  3. SipManager#open(String, SipRegistrationListener)でSIPプロバイダにREGISTERする

  4. SipManager#makeAudioCall(String ,String, SipAudioCall.Listener, int)で相手先に電話をかける

  5. SipAudioCall.Listener#onCallEstablished(SipAudioCall)で音声通話を開始する





1.SipManagerを作成する

 SipManagerはWalkieTalkieActivity.javaの106行目、initializeManager()で作成しています。
 単純にメンバ変数managerがnullの場合SipManager#newInstance(Context)を呼び出しているだけです。



public void initializeManager() {
if(manager == null) {
manager = SipManager.newInstance(this);
}
initializeLocalProfile();
}








2.SipProfileを作成する

 WalkieTalkieActivity.javaの138行目、initializeLocalProfile()内でSipProfileを作成しています。
 SipProfileの作成はSipProfile.Builderクラスを使って行います。コンストラクタにSIPプロバイダで取得したアカウント名、SIPプロバイダのドメイン名を設定します。(例:hoge@sip.moge.jpの場合、「hoge」がアカウント名で「sip.moge.jp」がドメイン名となります。)
 BuilderをnewしたあとsetPassword(String)でアカウントのパスワードをセットします。最後にSipProfile.Builder#build()を呼び出しSipProfileを作成します。



SipProfile.Builder builder = new SipProfile.Builder(username, domain);
builder.setPassword(password);
me = builder.build(); //meはメンバ変数。SipProfile型








3.SipManager#open(SipProfile, String, SipRegistrationListener)でSIPプロバイダにREGISTERする

 WalkieTalkieActivity.javaの142行目、initializeLocalProfile()内でSipProfileを作成した後、SipManager#openでSIPプロバイダに対してREGISTERを行います。
 openする際にSipRegistrationListenerでREGISTERの結果を受け取ります。何故かリスナのセットはSipManager#openを呼び出した後にしろ、との事。
 SipRegistrationListenerは以下の3つのコールバックを持ちます。




  • public void onRegistering(String localProfileUri)                       //REGISTER中

  • public void onRegistrationDone(String localProfileUri, long expiryTime)            //REGISTER成功

  • public void onRegistrationFailed(String localProfileUri, int errorCode, String errorMessage)  //REGISTER失敗


 SipDemoではこれらのコールバック時に、画面に各種状態の文字列を表示させています。
 SipManager#openをする際にPendingIntentをセットしていますが、これは着信の為に利用します。説明は後ほどします。



Intent i = new Intent();i.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, Intent.FILL_IN_DATA);
manager.open(me, pi, null);


// This listener must be added AFTER manager.open is called,
// Otherwise the methods aren't guaranteed to fire.

manager.setRegistrationListener(me.getUriString(), new SipRegistrationListener() {
public void onRegistering(String localProfileUri) {
updateStatus("Registering with SIP Server...");
}

public void onRegistrationDone(String localProfileUri, long expiryTime) {
updateStatus("Ready");
}

public void onRegistrationFailed(String localProfileUri, int errorCode,
String errorMessage) {
updateStatus("Registration failed. Please check settings.");
}
});









4.SipManager#makeAudioCall(String ,String, SipAudioCall.Listener, int)で相手先に電話をかける

 WalkieTalkieActivity.javaの197行目、initiateCall()内でSipManager#makeAudioCall(String, String, SipAudioCall.Listener, int)を呼び出します。引数は自分のSIPURI, 相手のSIPURI,SipAudioCall.Listener,タイムアウト秒です。
 SipAudioCall.Listenerはリスナという名前のくせにクラスです。コールバックの種類が結構多いのでinterfaceだと煩雑だと考えたのでしょうか?必要なコールバックだけオーバーライドして利用します。SipDemoでは相手が着信後200 OKを返し、通信が確立された際に呼び出されるonCallEstablished(SipAudioCall)と、通話を切断した場合に呼び出されるonCallEnded(SipAudioCall)をオーバーライドしています。



SipAudioCall.Listener listener = new SipAudioCall.Listener() {
// Much of the client's interaction with the SIP Stack will
// happen via listeners. Even making an outgoing call, don't
// forget to set up a listener to set things up once the call is established.
@Override
public void onCallEstablished(SipAudioCall call) {
call.startAudio();
call.setSpeakerMode(true);
call.toggleMute();
updateStatus(call);
}

@Override
public void onCallEnded(SipAudioCall call) {
updateStatus("Ready.");
}
};

call = manager.makeAudioCall(me.getUriString(), sipAddress, listener, 30);









5.SipAudioCall.Listener#onCallEstablished(SipAudioCall)で音声通話を開始する

 WalkieTalkieActivity.javaの202行目、SipAudioCall.Listener#onCallEstablished(SipAudioCall)でSipAudioCall#startAudio()を呼び出し音声通話を開始します。
 SipAudioCall#setSpeakerMode(boolean)にtrueをセットしています。これでハンズオンの状態で通話が開始されます。また、SipAudipCall#toggleMute()を呼び出しています。これで自分の音声はミュートされます。このミュートはタッチ処理の中で「タッチされている間だけオン」といった感じで実装されています。普通の電話の様な動作にしたい場合はcall.toggleMute()の行とタッチイベントの処理を削除するだけで実現できます。



@Override
public void onCallEstablished(SipAudioCall call) {
call.startAudio();
call.setSpeakerMode(true);
call.toggleMute();
updateStatus(call);
}











着信-通話までの手順


 着信はIncomingCallReceiver.javaで行います。IncomingCallReceiverはBroadcastReceiverを継承しており、WalkieTalkieActivityが実行されている間だけ着信を受けます。




  1. IncomingCallReceiverをシステムに登録する

  2. SipManagerに着信時に実行されるPendingIntentを登録する

  3. SipManagerを作成する

  4. SipProfileを作成する

  5. SipManager#open(String, SipRegistrationListener)でSIPプロバイダにREGISTERする

  6. IncomingCallReceiver#onReceive(Context, Intent)で通話を開始する





1.IncomingCallReceiverをシステムに登録する

 WalkieTalkieActivity.javaの69行目、onCreate(Bundle)内でIncomingCallReceiverをシステムに登録します。
 IntentFilterには"android.SipDemo.INCOMING_CALL"を指定しています。"android.SipDemo.INCOMING_CALL"のアクションを持つIntentがBroadcastされた場合に受け取る、という事です。



// Set up the intent filter. This will be used to fire an
// IncomingCallReceiver when someone calls the SIP address used by this
// application.
IntentFilter filter = new IntentFilter();
filter.addAction("android.SipDemo.INCOMING_CALL");
callReceiver = new IncomingCallReceiver();
this.registerReceiver(callReceiver, filter);











2.SipManagerに着信時に実行されるPendingIntentを登録する

 WalkieTalkieActivity.javaの142行目、initializeLocalProfile()内でPendingIntentを作成し、SipManager#openで登録しています。このPendingIntentは着信があった場合に実行されます。
 SipDemoでは"android.SipDemo.INCOMING_CALL"のアクションをBroadcastするPendingIntentを登録しています。このアクションはIncomingCallReceiverをシステムに登録する際に指定したIntentFilterと一致します。



Intent i = new Intent();i.setAction("android.SipDemo.INCOMING_CALL");
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, Intent.FILL_IN_DATA);
manager.open(me, pi, null);





3.SipManagerを作成する

発信と同じなので省略


4.SipProfileを作成する

発信と同じなので省略


5.SipManager#open(String, SipRegistrationListener)でSIPプロバイダにREGISTERする

発信と同じなので省略





6.IncomingCallReceiver#onReceive(Context, Intent)で通話を開始する

 IncomingCallReceiver.javaの37行目,onReceive(Context, Intent)で着信の処理を行っています。
ContextをWalkieTalkieActivityにキャストし、SipManager#takeAudioCall(Intent, SipAudioCall.Listener)を呼び出しています。SipManager#takeAudioCallを実行したら、次にSipAudioCall.Listener#onRinging(SipAudioCall, SipProfile)が呼び出されます。おそらく内部で100 TRYING、180 RINGINGを発信側に返してonRinging(SipAudioCall, SipProfile)を呼び出しているのだと思います。ここでSipAudioCall#answerCall(int)を呼び出します。javadocを読んでもいまいち動作が不明ですが、200 OKを返しているのでは無いかと思います。onRingingの後にincamingCall.answerCall(30)を実行しています。未確認ですが、この二回目のanswerCallは不要なんじゃないかなーと思います。
 その後SipAudioCall#startAudio()で通話を開始しています。
 wtActivity.call = incomingCall;でWalkieTalkieActivityのメンバ変数にSipAudioCallを設定していますが、WalkieTalkieActivityを終了したり、メニューの「End Current Call」を選択した時にSipAudioCall#endCall()を実行させる為です。



@Override
public void onReceive(Context context, Intent intent) {
SipAudioCall incomingCall = null;
try {

SipAudioCall.Listener listener = new SipAudioCall.Listener() {
@Override
public void onRinging(SipAudioCall call, SipProfile caller) {
try {
call.answerCall(30);
} catch (Exception e) {
e.printStackTrace();
}
}
};

WalkieTalkieActivity wtActivity = (WalkieTalkieActivity) context;

incomingCall = wtActivity.manager.takeAudioCall(intent, listener);
incomingCall.answerCall(30);
incomingCall.startAudio();
incomingCall.setSpeakerMode(true);
if(incomingCall.isMuted()) {
incomingCall.toggleMute();
}

wtActivity.call = incomingCall;

wtActivity.updateStatus(incomingCall);

} catch (Exception e) {
if (incomingCall != null) {
incomingCall.close();
}
}
}












最後に


SIPに関する面倒なほとんどの処理をSIP APIが抽象化してくれました。
上記で説明した手順だけでSIP通話が行えます。SIP APIを使って、例えばAccountManagerに登録されているGMailのアドレスから自動的にSIPアカウント払い出しを行ってすぐ通話できるアプリ、とか割と簡単に作成できそうですね。



文責:技術部 八木 俊広 (新人/Androidアプリケーション技術者認定試験ベーシック合格(笑)/品川)





2011年1月6日木曜日



某所からCES2011でAndroid3.0が爆発しているという話があったので、
関連すると思われるCES2011の動画を一通り見てみました。
結論から言うと、「最終的には3.0をのせるよ」というメーカーがほとんどで、
Android 3.0がどういったものかといった情報は今のところ特に得られませんでした。
CES2011と関連しているかは不明ですがGoogleが公式でアップしたとおぼしき動画があり、
そちらではかなり詳しくAndroid3.0に触れています(最下部にあります)。


Android3.0にするよ、と言っているメーカーの動画を紹介します。


Sony Ericsson Arc



D


Xperia arcの外観くるくる。
下記公式のブログでは2.3が乗ってるけどアップデートで3.0にする、と言っている模様
http://www.blogsonyericsson.com/2011/01/05/ces-2011-conoce-al-nuevo-sony-ericsson-xperia-arc/





Toshiba Honey Comb Tablet



D


東芝のタブレット端末の説明
動画ではAndroid2.2で動作している。
最終的には3.0をのせると言っている模様。





ASUS eeePad



D


10.1インチ版はAndroid3.0らしいが、
デモの人が持って写真とらせてるだけ。
12インチ版はWindows7。
モニタ部分を外したり付けたり





[Android 3.0]


Google公式の?Android OS3.0 Honey Comb紹介動画



D


元はGoogle公式の動画らしいですが、公開後すぐに削除されたようです。
有志らが閲覧した際に保存していた動画を再アップしている模様です。
CES2011と関連があるかは不明
ここではAndroid3.0について詳しく紹介がされています。
内容の説明についてはコチラを参照してください。
革新的なOSへ。Android 3.0(Honeycomb)の紹介動画登場。UIの刷新、WEBブラウザに上部タブ登場、Gmailの2画面表示、ビデオチャット対応など


この動画に登場する端末、よく見るとMotorola XOOMですね。





CES2011でAndroid3.0が話題らしいですが・・・



某所からCES2011でAndroid3.0が爆発しているという話があったので、
関連すると思われるCES2011の動画を一通り見てみました。
結論から言うと、「最終的には3.0をのせるよ」というメーカーがほとんどで、
Android 3.0がどういったものかといった情報は今のところ特に得られませんでした。
CES2011と関連しているかは不明ですがGoogleが公式でアップしたとおぼしき動画があり、
そちらではかなり詳しくAndroid3.0に触れています(最下部にあります)。


Android3.0にするよ、と言っているメーカーの動画を紹介します。


Sony Ericsson Arc



D


Xperia arcの外観くるくる。
下記公式のブログでは2.3が乗ってるけどアップデートで3.0にする、と言っている模様
http://www.blogsonyericsson.com/2011/01/05/ces-2011-conoce-al-nuevo-sony-ericsson-xperia-arc/





Toshiba Honey Comb Tablet



D


東芝のタブレット端末の説明
動画ではAndroid2.2で動作している。
最終的には3.0をのせると言っている模様。





ASUS eeePad



D


10.1インチ版はAndroid3.0らしいが、
デモの人が持って写真とらせてるだけ。
12インチ版はWindows7。
モニタ部分を外したり付けたり





[Android 3.0]


Google公式の?Android OS3.0 Honey Comb紹介動画



D


元はGoogle公式の動画らしいですが、公開後すぐに削除されたようです。
有志らが閲覧した際に保存していた動画を再アップしている模様です。
CES2011と関連があるかは不明
ここではAndroid3.0について詳しく紹介がされています。
内容の説明についてはコチラを参照してください。
革新的なOSへ。Android 3.0(Honeycomb)の紹介動画登場。UIの刷新、WEBブラウザに上部タブ登場、Gmailの2画面表示、ビデオチャット対応など


この動画に登場する端末、よく見るとMotorola XOOMですね。







f:id:bs-android:20110105214115p:image


A5協会(Ahoな Android Application Award Association)が主催しているA4(Ahoな Android Application Award)ですが、実は昨年末からヒッソリと投票受付を開始しています。



A4とは


Ahoなアプリをみんなで勝手に選んで表彰するイベントです。


A3(Android Application Award) 2010 Springの前日の2010/6/23に第一回の表彰が行われています。


日経BPさん公認黙認のイベントとなっています。


A4公式サイト



前回から機能が追加されており、投票者が好きにタグ付けできるようになっています。(ニコニコ動画のようなイメージ)


起動画面。


f:id:bs-android:20110105220641p:image:w240


"とうひょうする"のボタンを押すと端末にインストールされているアプリのリストが出ます。


※初回起動時はTwitterアカウントを設定するかどうか聞いてきます。


f:id:bs-android:20110105220739p:image:w240


リストのアプリをタップしたところ。


f:id:bs-android:20110105220433p:image:w240


"新規タグ"のボタンを押すと、オリジナルのタグを作成できます。


f:id:bs-android:20110105220445p:image:w240


ジャンルとタグを選択して、投票ボタンで投票してください。


トップ画面で"らんきんぐみる"のボタンを押すと、現在の投票ランキングを見ることができます。


f:id:bs-android:20110105221503p:image:w240


リストからアプリを選択すると詳細が見れます。


f:id:bs-android:20110105221550p:image:w240


Android Marketに公開されているアプリであれば、"AndroidMarketを表示する"のボタンで、Android Marketの詳細画面に飛びます。


f:id:bs-android:20110105221819p:image:w240





みなさんも、どしどし投票したり、面白いアプリを作って投票したりしてくださいまし!





A4投票アプリのダウンロード


投票アプリはAndroid Marketで"A4"と検索するか、以下のQRコードをAndroid端末で読み取ってください。


http://ahoapp.appspot.com/images/a4qr.png





Android端末でこのページを見ている場合は以下のリンクからでもAndroid Marketを起動できます。


Android Marketへのリンク



Special Thanks!


以下の開発者の方々の協力でA4は成り立っています。ありがとうございます!



Androidもくもく勉強会 Expert Developers



Name: Akihiro Kobayashi


twitter: @hidecheck


hp: http://d.hatena.ne.jp/hidecheck/


mail: hidecheck[at]gmail.com




Name: Kosei Kitahara


twitter: @Surgo


hp: http://surgo.jp/


mail: surgo.jp[at]gmail.com




Name: a_yama (tab02733)


twitter: @tab02733


hp: http://homepage2.nifty.com/a-yama/


mail: tab02733[at]gmail.com




Name: げんすけ


twitter: @gen_suke








A4 2011 Winter の投票、はじまってます。



f:id:bs-android:20110105214115p:image


A5協会(Ahoな Android Application Award Association)が主催しているA4(Ahoな Android Application Award)ですが、実は昨年末からヒッソリと投票受付を開始しています。



A4とは


Ahoなアプリをみんなで勝手に選んで表彰するイベントです。


A3(Android Application Award) 2010 Springの前日の2010/6/23に第一回の表彰が行われています。


日経BPさん公認黙認のイベントとなっています。


A4公式サイト



前回から機能が追加されており、投票者が好きにタグ付けできるようになっています。(ニコニコ動画のようなイメージ)


起動画面。


f:id:bs-android:20110105220641p:image:w240


"とうひょうする"のボタンを押すと端末にインストールされているアプリのリストが出ます。


※初回起動時はTwitterアカウントを設定するかどうか聞いてきます。


f:id:bs-android:20110105220739p:image:w240


リストのアプリをタップしたところ。


f:id:bs-android:20110105220433p:image:w240


"新規タグ"のボタンを押すと、オリジナルのタグを作成できます。


f:id:bs-android:20110105220445p:image:w240


ジャンルとタグを選択して、投票ボタンで投票してください。


トップ画面で"らんきんぐみる"のボタンを押すと、現在の投票ランキングを見ることができます。


f:id:bs-android:20110105221503p:image:w240


リストからアプリを選択すると詳細が見れます。


f:id:bs-android:20110105221550p:image:w240


Android Marketに公開されているアプリであれば、"AndroidMarketを表示する"のボタンで、Android Marketの詳細画面に飛びます。


f:id:bs-android:20110105221819p:image:w240





みなさんも、どしどし投票したり、面白いアプリを作って投票したりしてくださいまし!





A4投票アプリのダウンロード


投票アプリはAndroid Marketで"A4"と検索するか、以下のQRコードをAndroid端末で読み取ってください。


http://ahoapp.appspot.com/images/a4qr.png





Android端末でこのページを見ている場合は以下のリンクからでもAndroid Marketを起動できます。


Android Marketへのリンク



Special Thanks!


以下の開発者の方々の協力でA4は成り立っています。ありがとうございます!



Androidもくもく勉強会 Expert Developers



Name: Akihiro Kobayashi


twitter: @hidecheck


hp: http://d.hatena.ne.jp/hidecheck/


mail: hidecheck[at]gmail.com




Name: Kosei Kitahara


twitter: @Surgo


hp: http://surgo.jp/


mail: surgo.jp[at]gmail.com




Name: a_yama (tab02733)


twitter: @tab02733


hp: http://homepage2.nifty.com/a-yama/


mail: tab02733[at]gmail.com




Name: げんすけ


twitter: @gen_suke








2010年12月29日水曜日



Gingerbreadの新機能の一つとして「StrictMode」が導入されましたので、実際に使用して検証してみました。


StrictModeとは?




  • アプリケーションの動作をもっさりさせる原因となる、 ディスクやネットワークへのアクセスを検知するための仕組み。

  • 「スレッドや仮想マシンのポリシー」という形で、何を検知するかを決めることができます。

  • デフォルトでは下記のようなものが検知できます。


    • ディスクの読み込み、書き込み

    • ネットワークの使用

    • 違反:ログ、クラッシュ、dropbox、邪魔なダイアログ



  • 検知は、ディスク(java.io.*, android.database.sqlite.*, etc)やネットワーク(java.net.*)をフックすることで行われます。


StrictModeの利点




  • パフォーマンスの劣化につながる要素(ディスクアクセス、ネットワークアクセス、データベースカーソルのリークなど)を特定することができます。

  • 特定した結果をlogcatで表示することができ、特殊なログ解析ツール等は不要。

  • 次期バージョン「Honeycomb」ではメイン(UI)スレッドでネットワークのリクエストを行うとfatalエラー(例えターゲットがHoneycomb以前だとしても)となります。したがって、StrictModeは次期バージョンへアプリを対応させる上で、重要なツールとなります。


StrictModeの欠点




  • パフォーマンスを劣化させる全てを検知できる訳ではない。

  • 正常なアクセスもポリシー違反として検知されることがある。

  • 将来において、より多くの種類の検知を行うようになるため、リリース時には手動で無効化しておく手間がかかる。


StrictModeの使いどころ




  • ユーザエクスペリエンスに問題がある(もっさりしすぎている)と感じた場合にのみ、使うと良いです。

  • 将来的にはコンパイルエラーを特定するためのツールの一つになる可能性があります(UIスレッドでアクセスしている部分の特定など)。




StrictMode関連クラス・メソッド一覧


StrictModeクラス:




  • enableDefaults() - おすすめのStrictModeのデフォルト設定、ポリシー違反はログに出力される。

  • allowThreadDiskReads():getThreadPolicy() -> ディスク読み込みの許可 -> setThreadPolicy() までやってくれるラッパー。

  • allowThreadDiskWrites():getThreadPolicy() -> ディスク読み込み&書き込みの許可 -> setThreadPolicy() までやってくれるラッパー。

  • getThreadPolicy():現在のスレッドのポリシーを取得する。

  • getVmPolicy():現在のVMのポリシーを取得する。

  • setThreadPolicy(StrictMode.ThreadPolicy policy):現在のスレッドに指定のポリシーを適用する。

  • setVmPolicy(StrictMode.VmPolicy policy):現在のVMに指定のポリシーを適用する。


StrictMode.ThreadPolicy.Builderクラス - スレッドに適用されるポリシー




  • build() : ThreadPolicyのインスタンスを生成する。

  • detectAll():潜在的に疑いのある全ての事象を検知する。

  • detectDiskReads():ディスクの読み込みを検知する

  • detectNetwork():ネットワーク操作を検知する

  • penaltyDeath():違反したプロセス全体をクラッシュする。

  • penaltyDialog():違反として検知した煩わしいダイアログを表示する。

  • penaltyDropBox():ポリシーに違反したDropboxのスタックトレースとタイミングデータのログを有効にする。

  • penaltyLog():システムログに違反検知ログを出力する。

  • permitAll():全ての検知を無効にする。

  • permitDiskReads():ディスクの読み込み検知を無効にする。

  • permitDiskWrites():ディスクの書き込み検知を無効にする。

  • permitNetwork():ネットワークの操作検知を無効にする。


StrictMode.VmPolicy.Builderクラス - VMプロセス中の全てのスレッドに適用されるポリシー




  • build() :VMのインスタンスを生成する。

  • detectAll():潜在的に疑いのある全ての事象を検知する。

  • detectLeakedSqlLiteObjects():SQLiteCursor または 他のSQLiteのオブジェクトがクローズされずにファイナライズされた場合に検知する。

  • penaltyDeath():違反したプロセス全体をクラッシュする。

  • penaltyDropBox():ポリシーに違反したDropboxのスタックトレースとタイミングデータのログを有効にする。

  • penaltyLog():システムログに違反検知ログを出力する。


StrictModeで実行してみる


下記のようにonCreateで設定するだけでOK。結果はLogcatで確認。



// StrictModeはデバッグ時のみ使用すること
private final boolean DEVELOPER_MODE = true;

@Override
public void onCreate(Bundle savedInstanceState) {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
  :
��以下略)


または



// StrictModeはデバッグ時のみ使用すること
private final boolean DEVELOPER_MODE = true;

@Override
public void onCreate(Bundle savedInstanceState) {
if (DEVELOPER_MODE) {
StrictMode.enableDefaults();
}
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
  :
��以下略)


実行結果

※個人的に随分昔に作成した非常に残念で恥ずかしいコードを利用



12-18 23:30:34.588: DEBUG/StrictMode(337): StrictMode policy violation; ~duration=6604 ms: android.os.StrictMode$StrictModeNetworkViolation: policy=23 violation=4
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:758)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.net.InetAddress.lookupHostByName(InetAddress.java:488)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.net.InetAddress.getAllByNameImpl(InetAddress.java:294)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.net.InetAddress.getAllByName(InetAddress.java:256)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.<init>(HttpConnection.java:68)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.<init>(HttpConnection.java:48)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection$Address.connect(HttpConnection.java:298)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnectionPool.get(HttpConnectionPool.java:89)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getHttpConnection(HttpURLConnectionImpl.java:285)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.makeConnection(HttpURLConnectionImpl.java:267)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.retrieveResponse(HttpURLConnectionImpl.java:1018)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:510)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.net.URL.openStream(URL.java:645)
12-18 23:30:34.588: DEBUG/StrictMode(337): at jp.naoki.seto.ResultList$ViewItemAdapter.getView(ResultList.java:141)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.AbsListView.obtainView(AbsListView.java:1418)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.ListView.makeAndAddView(ListView.java:1745)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.ListView.fillDown(ListView.java:670)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.ListView.fillFromTop(ListView.java:727)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.ListView.layoutChildren(ListView.java:1598)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.AbsListView.onLayout(AbsListView.java:1248)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.View.layout(View.java:7175)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.FrameLayout.onLayout(FrameLayout.java:338)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.View.layout(View.java:7175)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1254)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1130)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.LinearLayout.onLayout(LinearLayout.java:1047)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.View.layout(View.java:7175)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.FrameLayout.onLayout(FrameLayout.java:338)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.View.layout(View.java:7175)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.ViewRoot.performTraversals(ViewRoot.java:1140)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.ViewRoot.handleMessage(ViewRoot.java:1859)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.os.Handler.dispatchMessage(Handler.java:99)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.os.Looper.loop(Looper.java:123)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.app.ActivityThread.main(ActivityThread.java:3647)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.lang.reflect.Method.invokeNative(Native Method)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.lang.reflect.Method.invoke(Method.java:507)
12-18 23:30:34.588: DEBUG/StrictMode(337): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
12-18 23:30:34.588: DEBUG/StrictMode(337): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
12-18 23:30:34.588: DEBUG/StrictMode(337): at dalvik.system.NativeStart.main(Native Method)


パフォーマンスのチューニング方法


ログを解析する


StrictMode policy violation; ~duration=6604 ms: android.os.StrictMode$StrictModeNetworkViolation: policy=23 violation=4
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:758)


ネットワーク関連の処理で、処理時間が(duration)が6604ms = 6.6sec !? もかかっている・・・



DEBUG/StrictMode(337): at java.net.URL.openStream(URL.java:645)
DEBUG/StrictMode(337): at jp.naoki.seto.ResultList$ViewItemAdapter.getView(ResultList.java:141)


問題の場所。どうやら、ResultList.java 141行目のViewItemAdapterクラス内のgetViewでURL.openStreamを実行している。つまり、UIスレッド上に展開されたリストビューで、ネットワークアクセスをしているようだ。


コードを見直す

ResultList.java



89: // リストビューアイテム表示アダプタ
90: private static class ViewItemAdapter extends BaseAdapter {
91:
��前略)
119:
120: public View getView(int position, View convertView, ViewGroup parent) {
121:
��中略)
139: try {
140: url = new URL(result.get(position).get("smallImageUrl").toString());
141: is = url.openStream();
142: } catch (MalformedURLException e) {


確かにソースを確認すると、メインスレッド上のgetViewで(つまり、リストビューの1行表示毎に)実行しており、パフォーマンスを著しく落としている原因になっている。


このように、ログからパフォーマンスを著しく落とす原因(上記の場合、メインスレッド上のネットワークアクセスを行っている箇所)を特定し、これらをバックグラウンドプロセス等に置き換えることで、アプリのパフォーマンスを向上させることができると思われる。


※ちなみに、このコードはHoneycombではfatal errorとなって、コンパイルすら出来なくなるでしょう。


Android端末はフラッシュメモリを内蔵しているので、ディスクアクセスは遅くないのではないか?という疑問


大抵の場合(容量が非常に限られた)内蔵フラッシュメモリを使用している間は、ディスクアクセスは非常に早いです。但し、他のプロセスからバックグラウンドでI/O処理が発生した場合など、劇的に遅くなるケースも多々存在します。したがって、「ディスクアクセスは非常に遅い」と仮定した設計が望ましいと思われます。


StrictModeはセキュリティのメカニズムではない


StrictModeはセキュリティのメカニズムではないので、全てのディスクやネットワークへのアクセスを検知する訳ではなく、保証もされません。Binderの呼び出し時にプロセス境界の向こう側に状態を伝播しますが、それでもベストエフォート型メカニズムに過ぎません。特にJNIからのディスクやネットワークアクセスは必ずしも検知されません。


パフォーマンス改善のためにStrictMode以外で考慮すべきこと


使用するAPIを変えることでパフォーマンスを改善出来ることがあります。例えば、新しいメソッドである SharedPreferences.Editor.apply() は 戻り値を必要としない場合は、同クラスの commit() よりもパフォーマンスが良くなります。



文責:技術部 瀬戸 直喜(前・日本Androidの会四国支部長/情報セキュリティスペシャリスト)





StrictModeでパフォーマンスをチューニングする



Gingerbreadの新機能の一つとして「StrictMode」が導入されましたので、実際に使用して検証してみました。


StrictModeとは?




  • アプリケーションの動作をもっさりさせる原因となる、 ディスクやネットワークへのアクセスを検知するための仕組み。

  • 「スレッドや仮想マシンのポリシー」という形で、何を検知するかを決めることができます。

  • デフォルトでは下記のようなものが検知できます。


    • ディスクの読み込み、書き込み

    • ネットワークの使用

    • 違反:ログ、クラッシュ、dropbox、邪魔なダイアログ



  • 検知は、ディスク(java.io.*, android.database.sqlite.*, etc)やネットワーク(java.net.*)をフックすることで行われます。


StrictModeの利点




  • パフォーマンスの劣化につながる要素(ディスクアクセス、ネットワークアクセス、データベースカーソルのリークなど)を特定することができます。

  • 特定した結果をlogcatで表示することができ、特殊なログ解析ツール等は不要。

  • 次期バージョン「Honeycomb」ではメイン(UI)スレッドでネットワークのリクエストを行うとfatalエラー(例えターゲットがHoneycomb以前だとしても)となります。したがって、StrictModeは次期バージョンへアプリを対応させる上で、重要なツールとなります。


StrictModeの欠点




  • パフォーマンスを劣化させる全てを検知できる訳ではない。

  • 正常なアクセスもポリシー違反として検知されることがある。

  • 将来において、より多くの種類の検知を行うようになるため、リリース時には手動で無効化しておく手間がかかる。


StrictModeの使いどころ




  • ユーザエクスペリエンスに問題がある(もっさりしすぎている)と感じた場合にのみ、使うと良いです。

  • 将来的にはコンパイルエラーを特定するためのツールの一つになる可能性があります(UIスレッドでアクセスしている部分の特定など)。




StrictMode関連クラス・メソッド一覧


StrictModeクラス:




  • enableDefaults() - おすすめのStrictModeのデフォルト設定、ポリシー違反はログに出力される。

  • allowThreadDiskReads():getThreadPolicy() -> ディスク読み込みの許可 -> setThreadPolicy() までやってくれるラッパー。

  • allowThreadDiskWrites():getThreadPolicy() -> ディスク読み込み&書き込みの許可 -> setThreadPolicy() までやってくれるラッパー。

  • getThreadPolicy():現在のスレッドのポリシーを取得する。

  • getVmPolicy():現在のVMのポリシーを取得する。

  • setThreadPolicy(StrictMode.ThreadPolicy policy):現在のスレッドに指定のポリシーを適用する。

  • setVmPolicy(StrictMode.VmPolicy policy):現在のVMに指定のポリシーを適用する。


StrictMode.ThreadPolicy.Builderクラス - スレッドに適用されるポリシー




  • build() : ThreadPolicyのインスタンスを生成する。

  • detectAll():潜在的に疑いのある全ての事象を検知する。

  • detectDiskReads():ディスクの読み込みを検知する

  • detectNetwork():ネットワーク操作を検知する

  • penaltyDeath():違反したプロセス全体をクラッシュする。

  • penaltyDialog():違反として検知した煩わしいダイアログを表示する。

  • penaltyDropBox():ポリシーに違反したDropboxのスタックトレースとタイミングデータのログを有効にする。

  • penaltyLog():システムログに違反検知ログを出力する。

  • permitAll():全ての検知を無効にする。

  • permitDiskReads():ディスクの読み込み検知を無効にする。

  • permitDiskWrites():ディスクの書き込み検知を無効にする。

  • permitNetwork():ネットワークの操作検知を無効にする。


StrictMode.VmPolicy.Builderクラス - VMプロセス中の全てのスレッドに適用されるポリシー




  • build() :VMのインスタンスを生成する。

  • detectAll():潜在的に疑いのある全ての事象を検知する。

  • detectLeakedSqlLiteObjects():SQLiteCursor または 他のSQLiteのオブジェクトがクローズされずにファイナライズされた場合に検知する。

  • penaltyDeath():違反したプロセス全体をクラッシュする。

  • penaltyDropBox():ポリシーに違反したDropboxのスタックトレースとタイミングデータのログを有効にする。

  • penaltyLog():システムログに違反検知ログを出力する。


StrictModeで実行してみる


下記のようにonCreateで設定するだけでOK。結果はLogcatで確認。



// StrictModeはデバッグ時のみ使用すること
private final boolean DEVELOPER_MODE = true;

@Override
public void onCreate(Bundle savedInstanceState) {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
  :
��以下略)


または



// StrictModeはデバッグ時のみ使用すること
private final boolean DEVELOPER_MODE = true;

@Override
public void onCreate(Bundle savedInstanceState) {
if (DEVELOPER_MODE) {
StrictMode.enableDefaults();
}
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
  :
��以下略)


実行結果

※個人的に随分昔に作成した非常に残念で恥ずかしいコードを利用



12-18 23:30:34.588: DEBUG/StrictMode(337): StrictMode policy violation; ~duration=6604 ms: android.os.StrictMode$StrictModeNetworkViolation: policy=23 violation=4
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:758)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.net.InetAddress.lookupHostByName(InetAddress.java:488)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.net.InetAddress.getAllByNameImpl(InetAddress.java:294)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.net.InetAddress.getAllByName(InetAddress.java:256)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.<init>(HttpConnection.java:68)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.<init>(HttpConnection.java:48)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection$Address.connect(HttpConnection.java:298)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnectionPool.get(HttpConnectionPool.java:89)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getHttpConnection(HttpURLConnectionImpl.java:285)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.makeConnection(HttpURLConnectionImpl.java:267)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.retrieveResponse(HttpURLConnectionImpl.java:1018)
12-18 23:30:34.588: DEBUG/StrictMode(337): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:510)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.net.URL.openStream(URL.java:645)
12-18 23:30:34.588: DEBUG/StrictMode(337): at jp.naoki.seto.ResultList$ViewItemAdapter.getView(ResultList.java:141)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.AbsListView.obtainView(AbsListView.java:1418)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.ListView.makeAndAddView(ListView.java:1745)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.ListView.fillDown(ListView.java:670)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.ListView.fillFromTop(ListView.java:727)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.ListView.layoutChildren(ListView.java:1598)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.AbsListView.onLayout(AbsListView.java:1248)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.View.layout(View.java:7175)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.FrameLayout.onLayout(FrameLayout.java:338)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.View.layout(View.java:7175)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1254)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1130)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.LinearLayout.onLayout(LinearLayout.java:1047)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.View.layout(View.java:7175)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.widget.FrameLayout.onLayout(FrameLayout.java:338)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.View.layout(View.java:7175)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.ViewRoot.performTraversals(ViewRoot.java:1140)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.view.ViewRoot.handleMessage(ViewRoot.java:1859)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.os.Handler.dispatchMessage(Handler.java:99)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.os.Looper.loop(Looper.java:123)
12-18 23:30:34.588: DEBUG/StrictMode(337): at android.app.ActivityThread.main(ActivityThread.java:3647)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.lang.reflect.Method.invokeNative(Native Method)
12-18 23:30:34.588: DEBUG/StrictMode(337): at java.lang.reflect.Method.invoke(Method.java:507)
12-18 23:30:34.588: DEBUG/StrictMode(337): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
12-18 23:30:34.588: DEBUG/StrictMode(337): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
12-18 23:30:34.588: DEBUG/StrictMode(337): at dalvik.system.NativeStart.main(Native Method)


パフォーマンスのチューニング方法


ログを解析する


StrictMode policy violation; ~duration=6604 ms: android.os.StrictMode$StrictModeNetworkViolation: policy=23 violation=4
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:758)


ネットワーク関連の処理で、処理時間が(duration)が6604ms = 6.6sec !? もかかっている・・・



DEBUG/StrictMode(337): at java.net.URL.openStream(URL.java:645)
DEBUG/StrictMode(337): at jp.naoki.seto.ResultList$ViewItemAdapter.getView(ResultList.java:141)


問題の場所。どうやら、ResultList.java 141行目のViewItemAdapterクラス内のgetViewでURL.openStreamを実行している。つまり、UIスレッド上に展開されたリストビューで、ネットワークアクセスをしているようだ。


コードを見直す

ResultList.java



89: // リストビューアイテム表示アダプタ
90: private static class ViewItemAdapter extends BaseAdapter {
91:
��前略)
119:
120: public View getView(int position, View convertView, ViewGroup parent) {
121:
��中略)
139: try {
140: url = new URL(result.get(position).get("smallImageUrl").toString());
141: is = url.openStream();
142: } catch (MalformedURLException e) {


確かにソースを確認すると、メインスレッド上のgetViewで(つまり、リストビューの1行表示毎に)実行しており、パフォーマンスを著しく落としている原因になっている。


このように、ログからパフォーマンスを著しく落とす原因(上記の場合、メインスレッド上のネットワークアクセスを行っている箇所)を特定し、これらをバックグラウンドプロセス等に置き換えることで、アプリのパフォーマンスを向上させることができると思われる。


※ちなみに、このコードはHoneycombではfatal errorとなって、コンパイルすら出来なくなるでしょう。


Android端末はフラッシュメモリを内蔵しているので、ディスクアクセスは遅くないのではないか?という疑問


大抵の場合(容量が非常に限られた)内蔵フラッシュメモリを使用している間は、ディスクアクセスは非常に早いです。但し、他のプロセスからバックグラウンドでI/O処理が発生した場合など、劇的に遅くなるケースも多々存在します。したがって、「ディスクアクセスは非常に遅い」と仮定した設計が望ましいと思われます。


StrictModeはセキュリティのメカニズムではない


StrictModeはセキュリティのメカニズムではないので、全てのディスクやネットワークへのアクセスを検知する訳ではなく、保証もされません。Binderの呼び出し時にプロセス境界の向こう側に状態を伝播しますが、それでもベストエフォート型メカニズムに過ぎません。特にJNIからのディスクやネットワークアクセスは必ずしも検知されません。


パフォーマンス改善のためにStrictMode以外で考慮すべきこと


使用するAPIを変えることでパフォーマンスを改善出来ることがあります。例えば、新しいメソッドである SharedPreferences.Editor.apply() は 戻り値を必要としない場合は、同クラスの commit() よりもパフォーマンスが良くなります。



文責:技術部 瀬戸 直喜(前・日本Androidの会四国支部長/情報セキュリティスペシャリスト)







12/26(日)、先月開催されたFeliCaハッカソン(日経BPさん主催)が、大阪でも開催されました。


f:id:bs-android:20101227232826j:image


内容は前回と同じく、AndroidでFeliCaチップが初搭載されたIS03をターゲットに、FeliCa APIを使ったアプリを一日かけて開発します。


資料や成果物(コード)はGoogle Codeにアップされ、開催模様はUSTREAMにて録画されています。


参加メンバー


クリスマス明けで急に冷え込んだ寒い朝にも関わらず、16名もの開発者の方々がご参加くださいました。


その他、スタッフとしてはKDDIさん、SHARPさん、フェリカネットワークスさん、日経BPさん、ブリリアントサービスの5社から各3~4名を加えて総勢30数名のハッカソンとなりました。


f:id:bs-android:20101227232829j:image


f:id:bs-android:20101227232830j:image


はじめのセッション


今回はアイディアソン、ハッカソンの時間を確保することを目的として、日経BPさん、KDDIさん、シャープさんの挨拶もそこそこに、フェリカネットワークスさんからFeliCa APIの解説だけ行われました。


f:id:bs-android:20101228000864j:image


午前のアイディアソン


チーム分けで時間が取られると思いきや、すぐに3~4名のグループが出来上がりあれよと言う間にホワイトボードが埋められていきました。


f:id:bs-android:20101227232827j:image


今回は、ターゲットの端末が既発売であったり前回の成果物などあったり、事前に必要な情報がある程度揃っていたからでしょうか。


アイディアプレゼン


昼食込みのアイディアソンの後、各チームで何を作るかの簡単なプレゼンが行われました。


f:id:bs-android:20101228000865j:image


f:id:bs-android:20101228000866j:image


その時のメモはこちらです。


午後のハッカソン


アイディアソンから引き続き、大きく4グループに分かれ、グループによっては複数のアプリの開発が行われました。


こちらも早い段階でアイディアが固まっていたからか、皆さん早々にもくもくと開発を進めておられました。


f:id:bs-android:20101227232837j:image


一時的に一部ネットワークが不通になる事はあったものの、その他はこれといったトラブルもなく、かなりしっかりした環境をご用意して頂けたのだと思います。


夕方の成果物発表


チームはやべん(3名)『FeliCaでWi-Fi』

FeliCaが接触させるほどの近距離で通信を行う点をセキュリティに有利と考案したアプリ。


アクセスポイントとなる端末から、WEPキーを送信して対向の端末で受け取ります。


一度に送信可能なデータが180Byteに満たない事もあり、最初のキーとなる情報のやり取りをFeliCaで行うというファーストコネクションの提案でした。


f:id:bs-android:20101227232836j:image


チーム神戸(3名)『FeliCaで短歌』

一方で上の句を作成して相手方へ送信、受け取った側では下の句を作成して返す。


そして最後に両者で詠み上げる(実際には表示のみです)というアプリを作成されていました。


送る対象が日本語のため、そのままURLエンコードすると1文字9バイト必要となり、


上の句の17文字を送ることに苦心されたそうです。


f:id:bs-android:20101227232835j:image


チーム神戸 分離して チームぼっち(1人)『MusicFeliCaIn』

Webベースのオーディオファイル管理システムAmpacheとクライアントの端末があったとして、


その端末から別の端末じぇFeliCaによる認証キーの転送を行い、オーディオのストリーミング再生を


行おうとされていました。時間と諸々の関係で残念ながら完成には至らずでした。


f:id:bs-android:20101227232834j:image


チームオールコム(4名)『AllCom(出席&名刺交換)』

勉強会などで照会が大変な出欠確認を、FeliCaを行って補助する仕組み。


出席者が会場に用意された端末にタッチすることで出席をとることができます。


また、その場でタッチしあった人同士で名刺交換や挨拶をし、どの人に会ったかの確認もできます。


この時間でサーバーの環境まで構築されたというのは驚きの一言。


f:id:bs-android:20101227232833j:image


チームばらばら(5名)『ア◯ック25のようなスタンプラリーのようなアプリ』

複数に分割されたパネルと同数の端末を用意し、パネルと端末に同じ番号を割り振ります。


2台をタッチさせることでお互いの番号に合わせたパネルが消えていき、例えば下にある画像がアンロックされていく仕組みです。


応用すればスタンプラリーや宝探しのようなゲームに使えるのでは?というアプリでした。


f:id:bs-android:20101227232832j:image


結果発表


1位はチーム神戸の「FeliCaで短歌」、2位は惜しくも1票差でチームオールコムの「AllCom」でした。


f:id:bs-android:20101227232831j:image


また、景品としてソニー(&フェリカネットワークス)さんからFeliCaリーダーPaSoRiと、日経BPさんからはIS03用特製ケースが提供、授与されました。


最後に


主催された日経BPさんを初め、各社スタッフとしてご参加いただいた方々、クリスマスの寒い中お集まり頂いた開発者の皆さんありがとうございました。そしておつかれさまでした。
今回は前回の実績があるため、ある意味やり辛いかな?と思っていましたが全くの杞憂でした。開発者の皆さんの発想力にはいつも驚かされます。
今後もこのようなイベントに関わり続けていきたいと思います。(文責:中山雅也)





Android FeliCa-thon 2010 in Osaka が開催されました



12/26(日)、先月開催されたFeliCaハッカソン(日経BPさん主催)が、大阪でも開催されました。


f:id:bs-android:20101227232826j:image


内容は前回と同じく、AndroidでFeliCaチップが初搭載されたIS03をターゲットに、FeliCa APIを使ったアプリを一日かけて開発します。


資料や成果物(コード)はGoogle Codeにアップされ、開催模様はUSTREAMにて録画されています。


参加メンバー


クリスマス明けで急に冷え込んだ寒い朝にも関わらず、16名もの開発者の方々がご参加くださいました。


その他、スタッフとしてはKDDIさん、SHARPさん、フェリカネットワークスさん、日経BPさん、ブリリアントサービスの5社から各3~4名を加えて総勢30数名のハッカソンとなりました。


f:id:bs-android:20101227232829j:image


f:id:bs-android:20101227232830j:image


はじめのセッション


今回はアイディアソン、ハッカソンの時間を確保することを目的として、日経BPさん、KDDIさん、シャープさんの挨拶もそこそこに、フェリカネットワークスさんからFeliCa APIの解説だけ行われました。


f:id:bs-android:20101228000864j:image


午前のアイディアソン


チーム分けで時間が取られると思いきや、すぐに3~4名のグループが出来上がりあれよと言う間にホワイトボードが埋められていきました。


f:id:bs-android:20101227232827j:image


今回は、ターゲットの端末が既発売であったり前回の成果物などあったり、事前に必要な情報がある程度揃っていたからでしょうか。


アイディアプレゼン


昼食込みのアイディアソンの後、各チームで何を作るかの簡単なプレゼンが行われました。


f:id:bs-android:20101228000865j:image


f:id:bs-android:20101228000866j:image


その時のメモはこちらです。


午後のハッカソン


アイディアソンから引き続き、大きく4グループに分かれ、グループによっては複数のアプリの開発が行われました。


こちらも早い段階でアイディアが固まっていたからか、皆さん早々にもくもくと開発を進めておられました。


f:id:bs-android:20101227232837j:image


一時的に一部ネットワークが不通になる事はあったものの、その他はこれといったトラブルもなく、かなりしっかりした環境をご用意して頂けたのだと思います。


夕方の成果物発表


チームはやべん(3名)『FeliCaでWi-Fi』

FeliCaが接触させるほどの近距離で通信を行う点をセキュリティに有利と考案したアプリ。


アクセスポイントとなる端末から、WEPキーを送信して対向の端末で受け取ります。


一度に送信可能なデータが180Byteに満たない事もあり、最初のキーとなる情報のやり取りをFeliCaで行うというファーストコネクションの提案でした。


f:id:bs-android:20101227232836j:image


チーム神戸(3名)『FeliCaで短歌』

一方で上の句を作成して相手方へ送信、受け取った側では下の句を作成して返す。


そして最後に両者で詠み上げる(実際には表示のみです)というアプリを作成されていました。


送る対象が日本語のため、そのままURLエンコードすると1文字9バイト必要となり、


上の句の17文字を送ることに苦心されたそうです。


f:id:bs-android:20101227232835j:image


チーム神戸 分離して チームぼっち(1人)『MusicFeliCaIn』

Webベースのオーディオファイル管理システムAmpacheとクライアントの端末があったとして、


その端末から別の端末じぇFeliCaによる認証キーの転送を行い、オーディオのストリーミング再生を


行おうとされていました。時間と諸々の関係で残念ながら完成には至らずでした。


f:id:bs-android:20101227232834j:image


チームオールコム(4名)『AllCom(出席&名刺交換)』

勉強会などで照会が大変な出欠確認を、FeliCaを行って補助する仕組み。


出席者が会場に用意された端末にタッチすることで出席をとることができます。


また、その場でタッチしあった人同士で名刺交換や挨拶をし、どの人に会ったかの確認もできます。


この時間でサーバーの環境まで構築されたというのは驚きの一言。


f:id:bs-android:20101227232833j:image


チームばらばら(5名)『ア◯ック25のようなスタンプラリーのようなアプリ』

複数に分割されたパネルと同数の端末を用意し、パネルと端末に同じ番号を割り振ります。


2台をタッチさせることでお互いの番号に合わせたパネルが消えていき、例えば下にある画像がアンロックされていく仕組みです。


応用すればスタンプラリーや宝探しのようなゲームに使えるのでは?というアプリでした。


f:id:bs-android:20101227232832j:image


結果発表


1位はチーム神戸の「FeliCaで短歌」、2位は惜しくも1票差でチームオールコムの「AllCom」でした。


f:id:bs-android:20101227232831j:image


また、景品としてソニー(&フェリカネットワークス)さんからFeliCaリーダーPaSoRiと、日経BPさんからはIS03用特製ケースが提供、授与されました。


最後に


主催された日経BPさんを初め、各社スタッフとしてご参加いただいた方々、クリスマスの寒い中お集まり頂いた開発者の皆さんありがとうございました。そしておつかれさまでした。
今回は前回の実績があるため、ある意味やり辛いかな?と思っていましたが全くの杞憂でした。開発者の皆さんの発想力にはいつも驚かされます。
今後もこのようなイベントに関わり続けていきたいと思います。(文責:中山雅也)





2010年12月22日水曜日



f:id:bs-android:20101221182358p:image


このトピックは「Google Analytics SDK for Android」と「Android Developer’sのブログ記事」を参考にしています。


SDKの概要


Google Analytics SDK for Androidは、Google Analytics for Mobile Apps SDKの一つで、モバイルアプリの操作を追跡し、それをGoogle Analyticsへレポートするインタフェースを提供します。SDKを利用すると主に下記のものが測定可能です。




  • 訪問者数

  • セッションの滞在時間

  • バウンスレート(直帰率:ウェブサイトを訪問し、サイト内をじっくり閲覧せずに離れた訪問者の割合のこと)

  • ユニーク訪問者数


モバイルアプリケーションの追跡は、Webサイトのページの追跡モデルに対して、若干の構造的な違いがあります。SDKはウェブサイトの訪問者を追跡し、Webページのウィジェットとやりとりを行うモデルを使用しています。したがって、以下で使用される用語は従来のウェブサイトの追跡モデルを、モバイルアプリケーションの追跡として置き換えたものとなります。


このSDKがどのように機能するかを理解するため、Analyticsで追跡する方法に詳しくなるべきです。Analyticsとやりとりするアプリを作るには、以下のモバイル追跡SDKを使用してください:


ページビュー追跡

ページビューは、伝統的なWebサイトのトラフィック量を図る標準的な指標です。モバイルアプリはHTMLページを含んでいないので、ページビューのリクエストを「いつ」「どれくらい」発生させるかを決定する必要があります。また、ページビューのリクエストはディレクトリの構造に基づいたレポートとして設計されています。そのため、Analyticsのコンテンツレポート中の、ページのパス名を使用するため、明示的な要求用の名前を用意する必要があります。選択された名前は実際のHTMLのページがなくとも、ページパスとしてAnalyticsのレポート中に設定されます。


イベント追跡

Analyticsでは、イベントはページビューのリクエストから、ユーザーのウェブページの要素とのやりとりを明確に追跡するように設計されています。Google Analyticsのイベント追跡機能を利用して、Analyticsのレポートインタフェースのイベント追跡セクション中にレポートされる呼び出しを、追加で利用できます。イベントはカテゴリを利用してグループ化され、また、イベント毎のラベルも利用でき柔軟性の高いレポートを提供します。例えば、マルチメディアのアプリはビデオのカテゴリとビデオ名毎のラベルに対して、再生/停止/一時停止を行う事ができます。Google Analyticsレポートはビデオのカテゴリにタグ付けされた全てのイベントを収集します。イベント追跡の情報については、イベントトラッキングガイドを参照して下さい。


カスタム変数

カスタム変数は名前と値のペアのタグで、Google Analyticsの追跡を強化するために追跡コード中に挿入することができます。使用方法に関する情報は、カスタム変数ガイドを参照して下さい。




はじめよう


システム要件

Google Analyticsの追跡システムにAndroidアプリを統合するには、以下のものが必要です:




セットアップ



  • libGoogleAnalytics.jar をプロジェクトの /libs ディレクトリに追加します。

  • プロジェクトのAndroidManifest.xmlに以下のパーミッションを追加します:


    • <uses-permission android:name="android.permission.INTERNET" />

    • <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />




SDKに含まれているサンプルのアプリケーションは、セットアップが成功したらプロジェクトがどのような構成となっているべきかを示すものとなっています。Analyticsが統合された自身のアプリのテンプレートとして、自由に利用しても構いません。


SDKを利用する


SDKを利用する前に、www.google.com/analyticsでフリーのアカウントを最初に取得し、適当な(偽の)WebサイトのURL(例:http://mymobileapp.mywebsite.com)を使ったアカウントに、Webサイトプロフィールを作成しなければなりません。プロフィールを生成したら、WebプロパティIDを書き写すかコピーを保管してください。



WebプロパティIDはまた、追跡コードである「UAナンバー」として知られており「UA-xxxxx-yy」のような形式となっています。xとyは各プロフィールのユニークな番号を示します。追跡オブジェクトをインスタンス化する際はWebプロパティIDを明示する必要があります。詳しくはWebプロパティを御覧ください。



SDKの使用の際には、ユーザに対して、アプリケーション自身や利用規約で、匿名でアプリケーション中のユーザの活動を追跡し、レポートする権利を有していることを示す必要があります。さらに、Google Analytics SDKの使用に関して、アカウントのサインアップ時にGoogle Analytics利用規約に同意する必要があります。


追跡を開始する

GoogleAnalyticsTracker.getInstance()をコールして、シングルトンのトラッカーを取得します。startメソッドをコールすると、WebプロパティIDが渡されて、アクティビティの追跡が開始されます。アクティビティのonCreateメソッド中で直接このメソッドをコールすると良いでしょう。例:



package com.google.android.apps.analytics.sample;

import com.google.android.apps.analytics.GoogleAnalyticsTracker;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class TestActivity extends Activity {

GoogleAnalyticsTracker tracker;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

tracker = GoogleAnalyticsTracker.getInstance();

// 手動のディスパッチモードで追跡を開始する...
tracker.start("UA-YOUR-ACCOUNT-HERE", this);

// あるいは、ディスパッチ間隔(秒)で指定して追跡を開始することも可能
//tracker.start("UA-YOUR-ACCOUNT-HERE", 20, this);

setContentView(R.layout.main);
Button createEventButton = (Button)findViewById(R.id.NewEventButton);
createEventButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
tracker.trackEvent(
"Clicks", // カテゴリ
"Button", // アクション
"clicked", // ラベル
77); // 値
}
});

Button createPageButton = (Button)findViewById(R.id.NewPageButton);
createPageButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// "Medium"名、"MobileApp"の値、セッションレベルのスコープで、このページビューにカスタム変数を追加する
// パラメータは順に、「Index (1~5)」「名前」「値」「スコープ」
// スコープの定義:訪問者=1, セッション=2, ページ=3(デフォルト)
tracker.setCustomVar(1, "Navigation Type", "Button click", 2);

// ページビューを追跡します。これはおそらく、アプリケーションを追跡する方法として良い方法でよく使われるでしょう。
// 例えば、
// tracker.trackPageView("/help");      > ヘルプ画面を誰が見たか追跡する
// tracker.trackPageView("/level2");     > 誰がゲームのレベル2に達したかを追跡する
// tracker.trackPageView("/uploadScreen"); > 誰がアップロード画面を利用したかを追跡する
tracker.trackPageView("/testApplicationHomeScreen");
}
});

Button quitButton = (Button)findViewById(R.id.QuitButton);
quitButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});

Button dispatchButton = (Button)findViewById(R.id.DispatchButton);
dispatchButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 手動でディスパッチを開始する。一定間隔でディスパッチする方法で開始した場合は不要。
tracker.dispatch();
}
});
}

@Override
protected void onDestroy() {
super.onDestroy();
// これ以上必要のない追跡を停止する
tracker.stop();
}
}


カスタム変数のスコープの定義

カスタム変数に定義できるスコープは次の3種類が存在します。




  • 訪問者スコープ(=1):端末でアプリケーションが最初に動作する時のみ呼び出します。同じインデックスでカスタム変数を生成、または、最初の変数を上書きしてはいけません。どのバージョンのアプリが使用されたか、どの種類の電話か、ライトとフルバージョンのアプリとの比較、または、アプリケーションのインストールから全く変化しない何らかのデータを送信するのに便利です。

  • セッションスコープ(=2):アクティビティが開始する毎に1度だけ呼び出します。同じインデックスで異なるカスタム変数を生成しない場合、アクティビティのライフサイクル中の全てのページビューやイベントに対して適用されます。

  • ページスコープ(=3):trackEventやtrackPageViewの直前にカスタム変数を適用する必要がある場合に呼び出します。メソッドが呼び出される度に動作します。スコープが指定されていない場合、これがデフォルトになります。


ページビューとイベントの追跡

ページビューの追跡は簡単です:ページビューを起動したい時に都度トラッカーオブジェクトの trackPageView をコールするだけです。イベントを記録するためには trackEvent をコールします。ページビューとイベントの詳細は、上記のSDKの概要を参照して下さい。


カスタム変数を利用する

カスタム変数の追加もまた簡単です:モバイルSDKによって提供される setCustomVar メソッドを利用するだけです。それぞれのカスタム変数のインデックスがどのようにマッピングされるかを前もって計画するだけで、既に存在する変数の上書きはしません。


カスタム変数の詳細な情報は、カスタム変数ガイドを参照して下さい。setCustomVar メソッドは直接、自身のデータをAnalyticsに送らないことに注意して下さい。次のページビューまたはイベント発生時に併せて送信されます(ピギーバックと呼ばれる方式です)。次の送信までの間はローカルのSQLITEデータベースに追跡情報が保存されます。ページビューやイベントの追跡前に setCustomVar をコールする必要があります。


バッチ処理で統計情報をアップロードする

接続が維持され続ける事やバッテリーの過負荷を防ぐため、追跡のリクエストをバッチ処理することをお勧めします。バッチリクエストを行いたければいつでも、追跡オブジェクトの dispatch メソッドをコールすることができ、手動でも一定間隔毎でもどちらでも実行することができます。



Tip: アプリケーション内で発生する他のHTTPリクエストにトラッカーをバンドルし、ディスパッチすることで、オーバーヘッドを低減することができます。



既知の不具合

不正確なタイムスタンプの可能性:タイムスタンプは Google Analyticsへアプリケーションからディスパッチされた時点で記録されますので、ユーザが長期間に渡りオフラインであった場合、タイムスタンプは100%正確ではないかもしれません。


リファラーの追跡


Android 1.6のOSリリースで、Androidマーケットへのダウンロードリンク中の、URLパラメータのリファラーがサポートされました。アプリケーション向けGoogle Analytics中の参照/キャンペーン情報を自動的に保持するため、Google Analytics SDK for Androidはこのパラメータを使用します。これは、(例えば、アプリの特定広告の有効性を測るといった場合に役立つような)ページビューやイベントを記録し関連付けるために、アプリケーションのソースをインストールすることを可能にします。



警告:アプリケーション中に生存する間はリファラーが残り続けるため、このリファラー操作は従来の訪問レベルのリファラーとは異なります。Androidでのリファラーのセットアップについては、原文のAndroidマーケットのリファラー追跡を御覧ください。



追跡を動作させるため、プロジェクトのAndroidManifest.xmlに、以下のブロードキャストレシーバを追加する必要があります。




<receiver android:name="com.google.android.apps.analytics.AnalyticsReceiver" android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>
</receiver>


Androidマーケットのリファラー追跡

Androidマーケットを通じてGoogle Analyticsのリファラー追跡をセットアップするには、リファラーリンクを生成するURLビルダーを利用します。Analytics SDKは自動的にリファラー情報をパースして記録し、Analyticsのレポートとして公開します。リファラーリンクを生成するには、下記のフィールドを埋めてください。*1


パッケージ名、キャンペーンソース、キャンペーンミディアムとキャンペーン名は必須です。それぞれのパラメータの詳細な説明は、下記のテーブルを参照して下さい。


リファラーリンクパラメータ







































パラメータ必須説明
utm_sourceキャンペーンソース:サーチエンジン、ニュースレター、その他のソースの特定に使用します。utm_source=google
utm_mediumキャンペーン媒体:eメールやコストパークリック(cpc)のような媒体の特定に使用します。utm_medium=cpc
utm_termキャンペーン用語:広告のために提供するキーワードを有料の検索で使用します。utm_term=running+shoes
utm_contentキャンペーンのコンテンツ:A/Bテストや、同じURLが指定された異なる広告やリンクのための、コンテンツをターゲットとした広告に使用します。 utm_content=logolink
utm_content=textlink
utm_campaignキャンペーン名:特別な製品プロモーションや戦略的なキャンペーンを特定するために、キーワードの解析に使用されます。utm_campaign=spring_sale


アプリケーションのリファラー情報を渡すためのマーケットへのリンクは、下記の通りです:


http://market.android.com/search?q=pname:<package>&referrer=<referral>


<package>はアプリケーションのパッケージ名、<referral>はAnalyticsのキャンペーン情報のリストをエンコードしたURLです。URLを生成するツールが下記にあります。


http://code.google.com/mobile/analytics/docs/android/#android-market-tracking


利用シーンの例


Androidに特徴的な例をあげると、ユーザーがフルバージョンのアプリを持っているかどうかに依存するステータス、"Full"または"Lite"の状態を有する"アプリケーションタイプ(AppType)"が挙げられるでしょう。AnalyticsのWebインタフェースを使って、"Lite"ユーザだけを確認したり、特定のバージョンがどの位使用されているかを見たり、"Lite"と"Full"セグメントとの違いを見ることができるでしょう。


利用規約を守る――個人情報は追跡してはならない!


Google Analyticsはそれ自身の利用規約に基づいているため、それらを読み、守ることは非常に重要です。特にAndroidアプリケーションの中にこれらの情報は存在するため、Analyticsサーバに個人を特定できる情報を送信してはいけません。つまり、訪問者レベルのカスタム変数に「電話番号」「名前」「eメールアドレス」を含む事はできないということです。直感的ではないですが、しかしながらこれは重要で、アプリケーションがWebアプリケーションのクライアントであれば(例えば、CRMやショッピングサイト)、Analyticsに店舗の情報(ユーザIDやトランザクションIDといった、Webバックエンドに格納される個人が特定できるものと結合可能な情報)を格納できないことを意味します。


実行結果


今回はSDKに付属するサンプルアプリを使用しました。


事前にGoogle Analyticsのアカウントを作成

f:id:bs-android:20101221174445p:image


Google Analyticsのサイトに移動し、適当なURLとアカウントを入力。URLとアカウントは実在しないドメインでも良いです。





f:id:bs-android:20101221174446p:image


連絡先情報を入力(誰が連絡してくるのだろう・・・)





f:id:bs-android:20101221174447p:image


利用規約を読んで同意するにチェック。この規約はSDKを利用するアプリにも適用されます。





f:id:bs-android:20101221174448p:image


Web用のコードスニペットが表示されるので、そこに記載されている「UA-XXXXX-Y」の文字列を保存しておく。





SDKを搭載したアプリを操作する

f:id:bs-android:20101221173337p:image


アプリ自体は至ってシンプルです。ボタンクリックによって、ページビューやイベントの追跡が発生します。





Google Analytics上のレポート例



  • レポートは初めて追跡を開始してから数時間(最大24時間)を経過しなければ表示されません。


f:id:bs-android:20101221174444p:image


f:id:bs-android:20101222004309p:image


f:id:bs-android:20101222004310p:image


f:id:bs-android:20101222004311p:image


このように、アプリから送信されたページビューやイベントの追跡情報が、Google Analyticsに表示されます。カテゴリ毎、ラベル毎、アクション毎など、様々な切り口から分析することができます。


まとめ


AndroidとGoogle Analyticsを使用すると、上記のように様々なユーザの操作などを情報を追跡する事ができます。このあたりの連携はさすがGoogle、といったところでしょうか。これらの追跡情報から、ユーザにとって使いやすいUIの配置を理論的に検討することもできるでしょう。


しかしながら、使い方によっては、ユーザーにとって知られたくない情報も収集されてしまう可能性もありますので、やはりアプリケーション内でこのSDKを使用している場合は、インストール時などにユーザーに対して情報を追跡している旨を伝え、事前の承諾を得る形で使用するべきでしょう(いわゆる、"オプトイン"方式)。



文責:技術部 植物工場研究G 瀬戸 直喜






Google Analytics SDK for Androidでユーザーのアプリ操作を追跡する



f:id:bs-android:20101221182358p:image


このトピックは「Google Analytics SDK for Android」と「Android Developer’sのブログ記事」を参考にしています。


SDKの概要


Google Analytics SDK for Androidは、Google Analytics for Mobile Apps SDKの一つで、モバイルアプリの操作を追跡し、それをGoogle Analyticsへレポートするインタフェースを提供します。SDKを利用すると主に下記のものが測定可能です。




  • 訪問者数

  • セッションの滞在時間

  • バウンスレート(直帰率:ウェブサイトを訪問し、サイト内をじっくり閲覧せずに離れた訪問者の割合のこと)

  • ユニーク訪問者数


モバイルアプリケーションの追跡は、Webサイトのページの追跡モデルに対して、若干の構造的な違いがあります。SDKはウェブサイトの訪問者を追跡し、Webページのウィジェットとやりとりを行うモデルを使用しています。したがって、以下で使用される用語は従来のウェブサイトの追跡モデルを、モバイルアプリケーションの追跡として置き換えたものとなります。


このSDKがどのように機能するかを理解するため、Analyticsで追跡する方法に詳しくなるべきです。Analyticsとやりとりするアプリを作るには、以下のモバイル追跡SDKを使用してください:


ページビュー追跡

ページビューは、伝統的なWebサイトのトラフィック量を図る標準的な指標です。モバイルアプリはHTMLページを含んでいないので、ページビューのリクエストを「いつ」「どれくらい」発生させるかを決定する必要があります。また、ページビューのリクエストはディレクトリの構造に基づいたレポートとして設計されています。そのため、Analyticsのコンテンツレポート中の、ページのパス名を使用するため、明示的な要求用の名前を用意する必要があります。選択された名前は実際のHTMLのページがなくとも、ページパスとしてAnalyticsのレポート中に設定されます。


イベント追跡

Analyticsでは、イベントはページビューのリクエストから、ユーザーのウェブページの要素とのやりとりを明確に追跡するように設計されています。Google Analyticsのイベント追跡機能を利用して、Analyticsのレポートインタフェースのイベント追跡セクション中にレポートされる呼び出しを、追加で利用できます。イベントはカテゴリを利用してグループ化され、また、イベント毎のラベルも利用でき柔軟性の高いレポートを提供します。例えば、マルチメディアのアプリはビデオのカテゴリとビデオ名毎のラベルに対して、再生/停止/一時停止を行う事ができます。Google Analyticsレポートはビデオのカテゴリにタグ付けされた全てのイベントを収集します。イベント追跡の情報については、イベントトラッキングガイドを参照して下さい。


カスタム変数

カスタム変数は名前と値のペアのタグで、Google Analyticsの追跡を強化するために追跡コード中に挿入することができます。使用方法に関する情報は、カスタム変数ガイドを参照して下さい。




はじめよう


システム要件

Google Analyticsの追跡システムにAndroidアプリを統合するには、以下のものが必要です:




セットアップ



  • libGoogleAnalytics.jar をプロジェクトの /libs ディレクトリに追加します。

  • プロジェクトのAndroidManifest.xmlに以下のパーミッションを追加します:


    • <uses-permission android:name="android.permission.INTERNET" />

    • <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />




SDKに含まれているサンプルのアプリケーションは、セットアップが成功したらプロジェクトがどのような構成となっているべきかを示すものとなっています。Analyticsが統合された自身のアプリのテンプレートとして、自由に利用しても構いません。


SDKを利用する


SDKを利用する前に、www.google.com/analyticsでフリーのアカウントを最初に取得し、適当な(偽の)WebサイトのURL(例:http://mymobileapp.mywebsite.com)を使ったアカウントに、Webサイトプロフィールを作成しなければなりません。プロフィールを生成したら、WebプロパティIDを書き写すかコピーを保管してください。



WebプロパティIDはまた、追跡コードである「UAナンバー」として知られており「UA-xxxxx-yy」のような形式となっています。xとyは各プロフィールのユニークな番号を示します。追跡オブジェクトをインスタンス化する際はWebプロパティIDを明示する必要があります。詳しくはWebプロパティを御覧ください。



SDKの使用の際には、ユーザに対して、アプリケーション自身や利用規約で、匿名でアプリケーション中のユーザの活動を追跡し、レポートする権利を有していることを示す必要があります。さらに、Google Analytics SDKの使用に関して、アカウントのサインアップ時にGoogle Analytics利用規約に同意する必要があります。


追跡を開始する

GoogleAnalyticsTracker.getInstance()をコールして、シングルトンのトラッカーを取得します。startメソッドをコールすると、WebプロパティIDが渡されて、アクティビティの追跡が開始されます。アクティビティのonCreateメソッド中で直接このメソッドをコールすると良いでしょう。例:



package com.google.android.apps.analytics.sample;

import com.google.android.apps.analytics.GoogleAnalyticsTracker;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class TestActivity extends Activity {

GoogleAnalyticsTracker tracker;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

tracker = GoogleAnalyticsTracker.getInstance();

// 手動のディスパッチモードで追跡を開始する...
tracker.start("UA-YOUR-ACCOUNT-HERE", this);

// あるいは、ディスパッチ間隔(秒)で指定して追跡を開始することも可能
//tracker.start("UA-YOUR-ACCOUNT-HERE", 20, this);

setContentView(R.layout.main);
Button createEventButton = (Button)findViewById(R.id.NewEventButton);
createEventButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
tracker.trackEvent(
"Clicks", // カテゴリ
"Button", // アクション
"clicked", // ラベル
77); // 値
}
});

Button createPageButton = (Button)findViewById(R.id.NewPageButton);
createPageButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// "Medium"名、"MobileApp"の値、セッションレベルのスコープで、このページビューにカスタム変数を追加する
// パラメータは順に、「Index (1~5)」「名前」「値」「スコープ」
// スコープの定義:訪問者=1, セッション=2, ページ=3(デフォルト)
tracker.setCustomVar(1, "Navigation Type", "Button click", 2);

// ページビューを追跡します。これはおそらく、アプリケーションを追跡する方法として良い方法でよく使われるでしょう。
// 例えば、
// tracker.trackPageView("/help");      > ヘルプ画面を誰が見たか追跡する
// tracker.trackPageView("/level2");     > 誰がゲームのレベル2に達したかを追跡する
// tracker.trackPageView("/uploadScreen"); > 誰がアップロード画面を利用したかを追跡する
tracker.trackPageView("/testApplicationHomeScreen");
}
});

Button quitButton = (Button)findViewById(R.id.QuitButton);
quitButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});

Button dispatchButton = (Button)findViewById(R.id.DispatchButton);
dispatchButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 手動でディスパッチを開始する。一定間隔でディスパッチする方法で開始した場合は不要。
tracker.dispatch();
}
});
}

@Override
protected void onDestroy() {
super.onDestroy();
// これ以上必要のない追跡を停止する
tracker.stop();
}
}


カスタム変数のスコープの定義

カスタム変数に定義できるスコープは次の3種類が存在します。




  • 訪問者スコープ(=1):端末でアプリケーションが最初に動作する時のみ呼び出します。同じインデックスでカスタム変数を生成、または、最初の変数を上書きしてはいけません。どのバージョンのアプリが使用されたか、どの種類の電話か、ライトとフルバージョンのアプリとの比較、または、アプリケーションのインストールから全く変化しない何らかのデータを送信するのに便利です。

  • セッションスコープ(=2):アクティビティが開始する毎に1度だけ呼び出します。同じインデックスで異なるカスタム変数を生成しない場合、アクティビティのライフサイクル中の全てのページビューやイベントに対して適用されます。

  • ページスコープ(=3):trackEventやtrackPageViewの直前にカスタム変数を適用する必要がある場合に呼び出します。メソッドが呼び出される度に動作します。スコープが指定されていない場合、これがデフォルトになります。


ページビューとイベントの追跡

ページビューの追跡は簡単です:ページビューを起動したい時に都度トラッカーオブジェクトの trackPageView をコールするだけです。イベントを記録するためには trackEvent をコールします。ページビューとイベントの詳細は、上記のSDKの概要を参照して下さい。


カスタム変数を利用する

カスタム変数の追加もまた簡単です:モバイルSDKによって提供される setCustomVar メソッドを利用するだけです。それぞれのカスタム変数のインデックスがどのようにマッピングされるかを前もって計画するだけで、既に存在する変数の上書きはしません。


カスタム変数の詳細な情報は、カスタム変数ガイドを参照して下さい。setCustomVar メソッドは直接、自身のデータをAnalyticsに送らないことに注意して下さい。次のページビューまたはイベント発生時に併せて送信されます(ピギーバックと呼ばれる方式です)。次の送信までの間はローカルのSQLITEデータベースに追跡情報が保存されます。ページビューやイベントの追跡前に setCustomVar をコールする必要があります。


バッチ処理で統計情報をアップロードする

接続が維持され続ける事やバッテリーの過負荷を防ぐため、追跡のリクエストをバッチ処理することをお勧めします。バッチリクエストを行いたければいつでも、追跡オブジェクトの dispatch メソッドをコールすることができ、手動でも一定間隔毎でもどちらでも実行することができます。



Tip: アプリケーション内で発生する他のHTTPリクエストにトラッカーをバンドルし、ディスパッチすることで、オーバーヘッドを低減することができます。



既知の不具合

不正確なタイムスタンプの可能性:タイムスタンプは Google Analyticsへアプリケーションからディスパッチされた時点で記録されますので、ユーザが長期間に渡りオフラインであった場合、タイムスタンプは100%正確ではないかもしれません。


リファラーの追跡


Android 1.6のOSリリースで、Androidマーケットへのダウンロードリンク中の、URLパラメータのリファラーがサポートされました。アプリケーション向けGoogle Analytics中の参照/キャンペーン情報を自動的に保持するため、Google Analytics SDK for Androidはこのパラメータを使用します。これは、(例えば、アプリの特定広告の有効性を測るといった場合に役立つような)ページビューやイベントを記録し関連付けるために、アプリケーションのソースをインストールすることを可能にします。



警告:アプリケーション中に生存する間はリファラーが残り続けるため、このリファラー操作は従来の訪問レベルのリファラーとは異なります。Androidでのリファラーのセットアップについては、原文のAndroidマーケットのリファラー追跡を御覧ください。



追跡を動作させるため、プロジェクトのAndroidManifest.xmlに、以下のブロードキャストレシーバを追加する必要があります。




<receiver android:name="com.google.android.apps.analytics.AnalyticsReceiver" android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>
</receiver>


Androidマーケットのリファラー追跡

Androidマーケットを通じてGoogle Analyticsのリファラー追跡をセットアップするには、リファラーリンクを生成するURLビルダーを利用します。Analytics SDKは自動的にリファラー情報をパースして記録し、Analyticsのレポートとして公開します。リファラーリンクを生成するには、下記のフィールドを埋めてください。*1


パッケージ名、キャンペーンソース、キャンペーンミディアムとキャンペーン名は必須です。それぞれのパラメータの詳細な説明は、下記のテーブルを参照して下さい。


リファラーリンクパラメータ







































パラメータ必須説明
utm_sourceキャンペーンソース:サーチエンジン、ニュースレター、その他のソースの特定に使用します。utm_source=google
utm_mediumキャンペーン媒体:eメールやコストパークリック(cpc)のような媒体の特定に使用します。utm_medium=cpc
utm_termキャンペーン用語:広告のために提供するキーワードを有料の検索で使用します。utm_term=running+shoes
utm_contentキャンペーンのコンテンツ:A/Bテストや、同じURLが指定された異なる広告やリンクのための、コンテンツをターゲットとした広告に使用します。 utm_content=logolink
utm_content=textlink
utm_campaignキャンペーン名:特別な製品プロモーションや戦略的なキャンペーンを特定するために、キーワードの解析に使用されます。utm_campaign=spring_sale


アプリケーションのリファラー情報を渡すためのマーケットへのリンクは、下記の通りです:


http://market.android.com/search?q=pname:<package>&referrer=<referral>


<package>はアプリケーションのパッケージ名、<referral>はAnalyticsのキャンペーン情報のリストをエンコードしたURLです。URLを生成するツールが下記にあります。


http://code.google.com/mobile/analytics/docs/android/#android-market-tracking


利用シーンの例


Androidに特徴的な例をあげると、ユーザーがフルバージョンのアプリを持っているかどうかに依存するステータス、"Full"または"Lite"の状態を有する"アプリケーションタイプ(AppType)"が挙げられるでしょう。AnalyticsのWebインタフェースを使って、"Lite"ユーザだけを確認したり、特定のバージョンがどの位使用されているかを見たり、"Lite"と"Full"セグメントとの違いを見ることができるでしょう。


利用規約を守る――個人情報は追跡してはならない!


Google Analyticsはそれ自身の利用規約に基づいているため、それらを読み、守ることは非常に重要です。特にAndroidアプリケーションの中にこれらの情報は存在するため、Analyticsサーバに個人を特定できる情報を送信してはいけません。つまり、訪問者レベルのカスタム変数に「電話番号」「名前」「eメールアドレス」を含む事はできないということです。直感的ではないですが、しかしながらこれは重要で、アプリケーションがWebアプリケーションのクライアントであれば(例えば、CRMやショッピングサイト)、Analyticsに店舗の情報(ユーザIDやトランザクションIDといった、Webバックエンドに格納される個人が特定できるものと結合可能な情報)を格納できないことを意味します。


実行結果


今回はSDKに付属するサンプルアプリを使用しました。


事前にGoogle Analyticsのアカウントを作成

f:id:bs-android:20101221174445p:image


Google Analyticsのサイトに移動し、適当なURLとアカウントを入力。URLとアカウントは実在しないドメインでも良いです。





f:id:bs-android:20101221174446p:image


連絡先情報を入力(誰が連絡してくるのだろう・・・)





f:id:bs-android:20101221174447p:image


利用規約を読んで同意するにチェック。この規約はSDKを利用するアプリにも適用されます。





f:id:bs-android:20101221174448p:image


Web用のコードスニペットが表示されるので、そこに記載されている「UA-XXXXX-Y」の文字列を保存しておく。





SDKを搭載したアプリを操作する

f:id:bs-android:20101221173337p:image


アプリ自体は至ってシンプルです。ボタンクリックによって、ページビューやイベントの追跡が発生します。





Google Analytics上のレポート例



  • レポートは初めて追跡を開始してから数時間(最大24時間)を経過しなければ表示されません。


f:id:bs-android:20101221174444p:image


f:id:bs-android:20101222004309p:image


f:id:bs-android:20101222004310p:image


f:id:bs-android:20101222004311p:image


このように、アプリから送信されたページビューやイベントの追跡情報が、Google Analyticsに表示されます。カテゴリ毎、ラベル毎、アクション毎など、様々な切り口から分析することができます。


まとめ


AndroidとGoogle Analyticsを使用すると、上記のように様々なユーザの操作などを情報を追跡する事ができます。このあたりの連携はさすがGoogle、といったところでしょうか。これらの追跡情報から、ユーザにとって使いやすいUIの配置を理論的に検討することもできるでしょう。


しかしながら、使い方によっては、ユーザーにとって知られたくない情報も収集されてしまう可能性もありますので、やはりアプリケーション内でこのSDKを使用している場合は、インストール時などにユーザーに対して情報を追跡している旨を伝え、事前の承諾を得る形で使用するべきでしょう(いわゆる、"オプトイン"方式)。



文責:技術部 植物工場研究G 瀬戸 直喜






2010年12月21日火曜日



前回エントリーで告知しました「第2回 SHARPハッカソン」が、12月18・19日に東広島で開催されました。


西は福岡、東は栃木まで各地から開発者がSHARP東広島工場に集まりました。
開発対象機種としてSHARP2010年秋冬モデル(IS03/GALAPAGOS SoftBank 003SH/LYNX 3DSH-03C)を使用し、開発者がチームを組んで2日間に渡りアプリ開発が行うイベントとなりました。


ハッカソン前の説明


朝11時、まずSHARPさんから端末の説明とSHARP APIの概要の説明がありました。
SHARP APIに関しての詳しい内容は、SHARPさんのSH Developers Squareサイトにリファレンスが上がっているので、そちらを確認してください。
f:id:bs-android:20101218112351j:image


次に司会をさせて頂いた弊社からGoogleCodeの案内と、Twitterハッシュタグの説明、USTがされる事の説明を行いました。




アイディアソン


貸し出された端末で何か面白いが出来ないかと、それぞれ何人かで固まってブレーンストーミングが行われました。
APIに詳しいSHARPの方々も混じって、APIの利用法を説明しながら和気あいあいとアイディア出しが行われました。
アイディアソン直後の各チーム毎のアイディアはこちらに書かれています。
f:id:bs-android:20101218124735j:image
f:id:bs-android:20101218124709j:image
f:id:bs-android:20101218124728j:image


ハッカソン1日目


午後はひたすらもくもくと開発する時間です。
f:id:bs-android:20101218142629j:image
f:id:bs-android:20101218142640j:image
f:id:bs-android:20101218142655j:image
f:id:bs-android:20101218142708j:image
f:id:bs-android:20101218164844j:image
一日目は17:30に終了。


ハッカソン2日目


二日目は10時スタート。ハッカソン終了の14時までどのチームもラストスパートを掛けていました。


発表


なまけもの/チームアバター

1日1回歩数を取得して、歩数に応じたキャラクターをウィジェットに表示するアプリ。
今後、Twitterと連携させる予定。
f:id:bs-android:20101219141720j:image
f:id:bs-android:20101219142048j:image





チーム3GamesCreators

3つのゲームアプリを開発。




  1. NGワードゲーム
    NGワードが表示された端末をおでこに置いて、お互いにヒントを言い合ってNGワードを言われたら負けるゲーム。

  2. 早打ちガンマン
    ゲーム参加端末で一斉に5カウントされ、0になった時に早く撃った人が勝ちのゲーム。

  3. ビーチ
    基本ルールはビーチフラッグ。一定歩数歩くと問題が出題され、その問題に早く正解した人が勝ちのゲーム。


このチームのメンバーが書かれたブログ記事です。
第2回 SHARPハッカソン@東広島に参加しました。 - stachibanaさん
第2回 SHARPハッカソン@東広島 - Toro_kunさん
android hackathon at sharp - lycheeさん


f:id:bs-android:20101219142214j:image
f:id:bs-android:20101219142400j:image
f:id:bs-android:20101219142610j:image




チーム寝坊/チーム3D

2つのアプリを開発。




  1. 寝坊しました
    ひたすら寝坊しましたと言う音声が再生され続ける。

  2. 1shotで3D画象をつくっちゃうぜ
    二台の端末を使って写真撮影して、撮影したら2つの画像を合わせて3D表示させる。


このチームのメンバーが書かれたブログ記事です。
android hackathon at sharp - lycheeさん
f:id:bs-android:20101219143125j:image
f:id:bs-android:20101219143425j:image




歩meji

simeji + 歩数計
歩いた歩数によって入力する文字が選ばれる。
これを使えば健康的且つハンズフリー。
f:id:bs-android:20101219143614j:image
f:id:bs-android:20101219143828j:image




歩数計マッシュルーム

歩いた歩数 + 単位を入力するマッシュルーム。
歩いた歩数によって、単位が「メェ」や「ウホ」と変わっていく。
f:id:bs-android:20101219144314j:image


チーム文鎮



  • SHARP秋モデルを文鎮化させる。

  • 3Dでのリモートデスクトップアプリの開発。


・・・を予定していたが、我々が文鎮になってしまった。
f:id:bs-android:20101219144808j:image
f:id:bs-android:20101219144851j:image




こども銀行

FeliCaを利用してお金のやり取りをシミュレートするアプリ
FeliCaAPIを利用すると、1トランザクションでの受領確認が困難であるため、サーバを用意する必要がある等実装が困難。


このチームのプレゼン資料です。
こども銀行 - keiji_ariyamaさん
f:id:bs-android:20101219145023j:image
f:id:bs-android:20101219145256j:image


結果発表


順位は以下のようになりました!


1位:チーム3GamesCreators
2位:歩meji
3位:チーム寝坊
4位:こども銀行
5位:チームアバター
6位:チーム文鎮
7位:歩数計マッシュルーム


1位のチームにはEdy搭載リストバンドが渡されました!
ブービーの方にはSHARPロゴの入ったフューチャーフォン用三脚。
他の参加者にはDroid君人形が配られました。


記念撮影


参加者全員の記念撮影
f:id:bs-android:20101219153353j:image


最後に


主催されたSHARPさん、全国各地から集まられた開発者の皆さん、ありがとうございました&お疲れさまでした。


このような面白いイベントの運営&司会をさせて頂き、裏方と言う立場ながら非常に楽しめたイベントとなりました。
これからも面白いイベントに関わっていきたく思います。


また、記事に使わせて頂いた写真は4gemaruさんから提供頂きました。ありがとうございます。


情報




なお、USTは結果発表時は録画に失敗していたため上がっておりません。
GoogleCodeは Apache License 2.0 を容認されたプロジェクトのみ上げられています。





第2回 SHARPハッカソン@東広島が開催されました



前回エントリーで告知しました「第2回 SHARPハッカソン」が、12月18・19日に東広島で開催されました。


西は福岡、東は栃木まで各地から開発者がSHARP東広島工場に集まりました。
開発対象機種としてSHARP2010年秋冬モデル(IS03/GALAPAGOS SoftBank 003SH/LYNX 3DSH-03C)を使用し、開発者がチームを組んで2日間に渡りアプリ開発が行うイベントとなりました。


ハッカソン前の説明


朝11時、まずSHARPさんから端末の説明とSHARP APIの概要の説明がありました。
SHARP APIに関しての詳しい内容は、SHARPさんのSH Developers Squareサイトにリファレンスが上がっているので、そちらを確認してください。
f:id:bs-android:20101218112351j:image


次に司会をさせて頂いた弊社からGoogleCodeの案内と、Twitterハッシュタグの説明、USTがされる事の説明を行いました。




アイディアソン


貸し出された端末で何か面白いが出来ないかと、それぞれ何人かで固まってブレーンストーミングが行われました。
APIに詳しいSHARPの方々も混じって、APIの利用法を説明しながら和気あいあいとアイディア出しが行われました。
アイディアソン直後の各チーム毎のアイディアはこちらに書かれています。
f:id:bs-android:20101218124735j:image
f:id:bs-android:20101218124709j:image
f:id:bs-android:20101218124728j:image


ハッカソン1日目


午後はひたすらもくもくと開発する時間です。
f:id:bs-android:20101218142629j:image
f:id:bs-android:20101218142640j:image
f:id:bs-android:20101218142655j:image
f:id:bs-android:20101218142708j:image
f:id:bs-android:20101218164844j:image
一日目は17:30に終了。


ハッカソン2日目


二日目は10時スタート。ハッカソン終了の14時までどのチームもラストスパートを掛けていました。


発表


なまけもの/チームアバター

1日1回歩数を取得して、歩数に応じたキャラクターをウィジェットに表示するアプリ。
今後、Twitterと連携させる予定。
f:id:bs-android:20101219141720j:image
f:id:bs-android:20101219142048j:image





チーム3GamesCreators

3つのゲームアプリを開発。




  1. NGワードゲーム
    NGワードが表示された端末をおでこに置いて、お互いにヒントを言い合ってNGワードを言われたら負けるゲーム。

  2. 早打ちガンマン
    ゲーム参加端末で一斉に5カウントされ、0になった時に早く撃った人が勝ちのゲーム。

  3. ビーチ
    基本ルールはビーチフラッグ。一定歩数歩くと問題が出題され、その問題に早く正解した人が勝ちのゲーム。


このチームのメンバーが書かれたブログ記事です。
第2回 SHARPハッカソン@東広島に参加しました。 - stachibanaさん
第2回 SHARPハッカソン@東広島 - Toro_kunさん
android hackathon at sharp - lycheeさん


f:id:bs-android:20101219142214j:image
f:id:bs-android:20101219142400j:image
f:id:bs-android:20101219142610j:image




チーム寝坊/チーム3D

2つのアプリを開発。




  1. 寝坊しました
    ひたすら寝坊しましたと言う音声が再生され続ける。

  2. 1shotで3D画象をつくっちゃうぜ
    二台の端末を使って写真撮影して、撮影したら2つの画像を合わせて3D表示させる。


このチームのメンバーが書かれたブログ記事です。
android hackathon at sharp - lycheeさん
f:id:bs-android:20101219143125j:image
f:id:bs-android:20101219143425j:image




歩meji

simeji + 歩数計
歩いた歩数によって入力する文字が選ばれる。
これを使えば健康的且つハンズフリー。
f:id:bs-android:20101219143614j:image
f:id:bs-android:20101219143828j:image




歩数計マッシュルーム

歩いた歩数 + 単位を入力するマッシュルーム。
歩いた歩数によって、単位が「メェ」や「ウホ」と変わっていく。
f:id:bs-android:20101219144314j:image


チーム文鎮



  • SHARP秋モデルを文鎮化させる。

  • 3Dでのリモートデスクトップアプリの開発。


・・・を予定していたが、我々が文鎮になってしまった。
f:id:bs-android:20101219144808j:image
f:id:bs-android:20101219144851j:image




こども銀行

FeliCaを利用してお金のやり取りをシミュレートするアプリ
FeliCaAPIを利用すると、1トランザクションでの受領確認が困難であるため、サーバを用意する必要がある等実装が困難。


このチームのプレゼン資料です。
こども銀行 - keiji_ariyamaさん
f:id:bs-android:20101219145023j:image
f:id:bs-android:20101219145256j:image


結果発表


順位は以下のようになりました!


1位:チーム3GamesCreators
2位:歩meji
3位:チーム寝坊
4位:こども銀行
5位:チームアバター
6位:チーム文鎮
7位:歩数計マッシュルーム


1位のチームにはEdy搭載リストバンドが渡されました!
ブービーの方にはSHARPロゴの入ったフューチャーフォン用三脚。
他の参加者にはDroid君人形が配られました。


記念撮影


参加者全員の記念撮影
f:id:bs-android:20101219153353j:image


最後に


主催されたSHARPさん、全国各地から集まられた開発者の皆さん、ありがとうございました&お疲れさまでした。


このような面白いイベントの運営&司会をさせて頂き、裏方と言う立場ながら非常に楽しめたイベントとなりました。
これからも面白いイベントに関わっていきたく思います。


また、記事に使わせて頂いた写真は4gemaruさんから提供頂きました。ありがとうございます。


情報




なお、USTは結果発表時は録画に失敗していたため上がっておりません。
GoogleCodeは Apache License 2.0 を容認されたプロジェクトのみ上げられています。





Related Posts Plugin for WordPress, Blogger...