2013年8月3日土曜日

Android開発環境ダウングレードメモ

退路確保のためメモる。

ADT

下記のサイトにまとめられています。感謝。
Bescotteeさん
Android SDKの古いADTのダウンロードURLの一覧を記載
http://andbrowser.com/development/918/old-adt-download-url/

SDK Tools

バージョン(r21.1)の部分は適宜変更してください。

SDK Platform-tools

バージョン(r16.0.2)の部分は適宜変更してください。

※SDK ToolsとSDK Platform-toolsのバージョン組み合わせが分からなくなったときはSDK Toolsのルートディレクトリに居るsource.properties(の末尾)を参照すると良いっぽい。


2013年7月10日水曜日

AndroidでCloud Save(Google Play App State)を使ってみた

Cloud Save(Google Play App State)とは

アプリのデータをGoogleのサーバ上に保存することができます。
・保存できるデータサイズは128KB × 4スロット (最大512KB)
・データフォーマットはバイト配列

公式ドキュメント
Google Play Game Services — Google Developers

今回クライアント側はAndroidですが、iOS/Webアプリからも利用できるようです。
ドキュメントではGoogle Play Game Servicesの括りで紹介されていますが、ゲーム以外でも活用できるんじゃないかと思います。

Androidアプリ向けのDeveloper's Guideはこちら。
Cloud Save in Android ― Google Play Game Services — Google Developers

Cloud Saveサービス側のセットアップ

1. Google API ConsoleにアクセスしてAPI Projectを作成。

2. Servicesから[Google Play App State]を有効化。

3. API Accessから[Create an OAuth 2.0 client ID...]をクリック。

4. [Produce name]を入力して[Next]をクリック。

5. Application typeは[Installed application]を選択。
    Installed application typeは[Android]を選択。
    [Pacage name]/[Signing certificate fingerprint]を入力。
    [Create client ID]をクリック。

そうするとClient IDがこんな感じで生成されますので、このClient IDを後述のAndroidManifest.xmlに設定します。

Androidアプリ側のセットアップ

1. Google Play servicesライブラリ(rev.7)をインストールしてEclipseにインポート
2. アプリから上記ライブラリを参照

サンプルコード

MainActivity.java
package com.example.cloudsave;

import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.TextView;

import com.google.android.gms.appstate.AppStateClient;
import com.google.android.gms.appstate.OnStateLoadedListener;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
import com.google.android.gms.common.Scopes;

public class MainActivity extends Activity implements ConnectionCallbacks, OnConnectionFailedListener,
        OnStateLoadedListener, OnClickListener {

    private AppStateClient mAppStateClient;
    private boolean mConnecting;
    private TextView mTextView;
    private EditText mEditText;

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

        String[] scopes = new String[] {
                Scopes.APP_STATE
        };
        mAppStateClient = new AppStateClient.Builder(this, this, this).setScopes(scopes).create();

        findViewById(R.id.button_update).setOnClickListener(this);
        findViewById(R.id.button_load).setOnClickListener(this);
        mTextView = (TextView) findViewById(R.id.textview);
        mEditText = (EditText) findViewById(R.id.edittext);
    }

    @Override
    protected void onStart() {
        super.onStart();

        if (!mConnecting) {
            mConnecting = true;
            mAppStateClient.connect();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();

        mAppStateClient.disconnect();
    }

    @Override
    public void onConnected(Bundle bundle) {
        mConnecting = false;
        mAppStateClient.loadState(this, 0);
    }

    @Override
    public void onDisconnected() {
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        if (connectionResult.hasResolution()) {
            try
            {
                mConnecting = true;
                connectionResult.startResolutionForResult(this, 0);
            } catch (IntentSender.SendIntentException e) {
                mAppStateClient.connect();
            }
        } else {
            // TODO:接続失敗時の処理
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (resultCode == RESULT_OK) {
            mAppStateClient.connect();
        }
    }

    @Override
    public void onStateConflict(int stateKey, String resolvedVersion, byte[] localData, byte[] serverData) {
        mAppStateClient.resolveState(this, 0, resolvedVersion, serverData);
    }

    @Override
    public void onStateLoaded(int statusCode, int stateKey, byte[] data) {
        if (data == null) {
            return;
        }
        mTextView.setText("Cloud Saveサービス上のデータ:" + new String(data));
        mEditText.setText("");
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button_update:
                String message = mEditText.getText().toString();
                mAppStateClient.updateStateImmediate(this, 0, message.getBytes());
                break;
            case R.id.button_load:
                mAppStateClient.loadState(this, 0);
                break;
            default:
                break;
        }
    }

}
ざっくり解説
【データセーブ】
データのセーブはupdateStateImmediate()で行う。
更新成功するとonStateLoaded()が呼ばれる。
コンフリクト発生時はonStateConflict()が呼ばれるのでresolveState()で解決する。
【データロード】
データのロードはloadState()で行う。
読み込みが完了するとonStateLoaded()が呼ばれる。

このコードではデータのセーブにAppStateClient.updateStateImmediate()を使っていますが、AppStateClient.updateState()を使った方がバッテリー・ネットワークに優しいそうです。

AndroidManifest.xml
    <application>
        <meta-data
            android:name="com.google.android.gms.appstate.APP_ID"
            android:value="<Your Client ID>" />
    </application>
<application>タグの中にClient IDを設定する。
※レイアウトXMLは割愛

動かしてみた

ActivityのonStart()でconnect()を読んでいるので
アプリを起動するとログインを求めるダイアログが出ます。

サンプルアプリの動きは下記の通りです。
[Update State]ボタンでEditTextに入力した内容をCloud Saveに保存
[Load State]ボタンでCloud Saveからデータを読み込みTextViewに表示

2台の端末からデータのセーブを行うケースで、ローカルの情報が古い状態でデータのセーブを行うとコンフリクトが発生します。
サンプルでは、コンフリクト発生時はサーバ側のデータを採用するようにしています。

コンフリクトの解決についての公式ドキュメントはこちらです。#まだ読んでません...
Resolving Cloud Save Conflicts | Android Developers http://developer.android.com/training/cloudsave/conflict-res.html

最後に

API Consoleを見ると、20,000,000 requests/dayとあります。
上限を超過したときってどうなるんでしょう?
ご存知の方がおられましたら教えてください。

2013年6月8日土曜日

Activity遷移時のアニメーションを無効化する方法

やり方

Activityクラスの下記のメソッドを使う。
public void overridePendingTransition (int enterAnim, int exitAnim)
Activity | Android Developers
http://developer.android.com/reference/android/app/Activity.html#overridePendingTransition(int, int)

公式リファレンスに
Parameters
enterAnimA resource ID of the animation resource to use for the incoming activity. Use 0 for no animation.
exitAnimA resource ID of the animation resource to use for the outgoing activity. Use 0 for no animation.
とあるので
引数enterAnim/exitAnimに0を指定すると、Activity遷移時のアニメーションを無効化できる。

コード

MainActivityのButtonからSubActivityを呼び出す構成。
SubActivityからBackキーで戻って来る時のアニメーションも無効化するため
SubActivityのfinish()をオーバーライドしてoverridePendingTransition()を呼んでいる。

MainActivity.java
public class MainActivity extends Activity {

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

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, SubActivity.class));
                overridePendingTransition(0, 0);
            }
        });
    }

}

SubActivity.java
public class SubActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sub);
    }

    @Override
    public void finish() {
        super.finish();

        overridePendingTransition(0, 0);
    }

}
※Layout XMLは割愛

まとめ

今回のやり方は、下記の動画で知りました。

この動画のサンプルコードはChet HaaseさんのGoogle+のリンクからダウンロードできます。
https://plus.google.com/104755487586666698979/posts/i38BBQnjoFU

この他にも有用な動画があるので、Android Developers LiveのDEV.BYTESシリーズおすすめです。
DevBytes - YouTube
http://www.youtube.com/playlist?list=PLWz5rJ2EKKc_XOgcRukSoKKjewFJZrKV0

2013年5月24日金曜日

Android Live Wallpaper Thumbnail Image Guideline(オレオレ版)

ライブ壁紙一覧に表示されるサムネイルイメージの話。


サムネイルイメージのサイズをいくつにすればジャストサイズになるんだろう?
Launcher Iconみたいにガイドラインがあるのかな?と思い
Android Developersのサイトで検索するも見つからず。

ということで、実測によるオレオレGuidelineを作ったメモ。

測定方法

UI Automatorを使用。

ライブ壁紙一覧を表示した状態でDDMSパースペクティブから
上記の[Dump View Hierarchy for UI Automator]ボタンをクリック。
表示されたDumpからサムネイル画像部分のImageViewを選択。
boundsに表示される値からサイズを取得した。

測定に使用した実機およびエミュレータ
ldpi:Emulator
mdpi:Emulator
tvdpi:Nexus 7
hdpi:Nexus S
xhdpi:SH-04E(AQUOS PHONE EX)
xxhdpi:SO-02E(Xperia Z)

Size and Format

フォーマット
Launcher Iconで推奨されている32-bit PNGでよいかと思う。
【参考】
サイズ
ldpi 56 x 56 px
mdpi 75 x 75 px
tvdpi 100 x 100 px
hdpi 113 x 113 px
xhdpi 150 x 150 px
xxhdpi 225 x 225 px

その他

<wallpaper />で指定するandroid:descriptionは表示されない端末がある。
※僕の手持ち端末調べでは、4.1以上はdescriptionが表示されない。

2013年3月30日土曜日

WebStormでLocal DirectoryにDeployする方法

環境

  • WebStrom 6.0
  • Mac OS X Mountain Lion

設定

Preferences...([,] + [command])を開いて
[Project Settings]の[Deployment]を選択。

[+]をクリックして
[Name]を入力。
[Type]は”Local or mounted folder”を選択。

[Connection]タブを開いて
[Folder]に”Deploy先のディレクトリ”を入力。
[Web server root URL:]に”http://localhost”を入力。


[Mappings]タブを開いて
[Local path]にプロジェクトのパスを入力。(初めから入力されていると思います)
[Deployment path on server...]に”ドキュメントルートからの相対パス”を入力。
[Web path on server...]に”Web server root URLからの相対パス”を入力。


以上で設定は終わりです。

Deployしてみる

Menuバーの[Tools]の[Deployment]から
[Upload to <Deployment設定名>]を選択。
こんな感じのメッセージが表示されればDeploy完了です。


※[Automatic Upload]にチェックを入れると、編集する度にDeployしてくれます。

参考サイト

MacのApacheの設定は、こちらのサイトを参考にさせていただきました。
ありがとうございます。

Androidはワンツーパンチ 三歩進んで二歩下がる 
Mac OS X Mountain Lionにシステム環境設定の「共有」パネルに Web 共有のオプションが入っていない件について

最後に

「HTML5/JavaScriptでつくるスマホアプリ」みたいな本を読んでまして、スマホからの動作確認のために上記の環境を作りました。
スマホは開発PCと同一無線LAN内にいて、LocalのApacheで公開しているWebページを見に行くという構成です。

もっとスマートなやり方があったら教えてください。

2013年3月25日月曜日

プロジェクト作成時にSupport Libraryのソース関連付けを行う方法

注意

Android SDKのファイルを変更しますので、自己責任において行ってください。

環境

Mac OS X Mountain Lion
Eclipse Juno Service Release 2
ADT 21.1.0

はじめに

プロジェクトにSupport Libraryのソースを関連づけるには
/libsディレクトリに
android-support-v4.jar.propertiesファイルを
下記の内容で作成します。
src=<android-sdk-root>/extras/android/support/v4/src
※<android-sdk-root>の部分は、Android SDKを配置したパスを指定します。
設定を反映させるには、Eclipseから該当プロジェクトの開き直しを行います。
[Close Project] -> [Open Project]
反映されない場合は、Eclipseの再起動で反映されるかもしれません。

今回は、上記の作業を新規プロジェクト作成時に自動でやってしまおうというものです。

設定

<android-sdk-root>/tools/templates/projects/NewAndroidApplication/以下のファイルに変更を加えて行きます。
※適宜バックアップを取ってから、変更を行う事をおすすめします。

1. テンプレートへandroid-support-v4.jar.propertiesを追加
<android-sdk-root>/tools/templates/projects/NewAndroidApplication/root/にlibsディレクトリを作成して、android-support-v4.jar.propertiesを配置します。

2. テンプレートのrecipe.xml.ftlファイルを編集
<android-sdk-root>/tools/templates/projects/NewAndroidApplication/root/の
recipe.xml.ftlをテキストエディタで開きます。
下記の21、22行を追記して、ファイルを保存します。
<?xml version="1.0"?>
<recipe>
    <instantiate from="AndroidManifest.xml.ftl" />

<#if copyIcons>
    <copy from="res/drawable-hdpi" />
    <copy from="res/drawable-mdpi" />
    <copy from="res/drawable-xhdpi" />
</#if>
    <instantiate from="res/values/styles.xml.ftl" />
<#if buildApi gte 11 && baseTheme != "none">
    <instantiate from="res/values-v11/styles_hc.xml.ftl"
                   to="res/values-v11/styles.xml" />
</#if>
<#if buildApi gte 14 && baseTheme?contains("darkactionbar")>
    <copy from="res/values-v14/styles_ics.xml"
            to="res/values-v14/styles.xml" />
</#if>

    <instantiate from="res/values/strings.xml.ftl" />
    <copy from="libs/android-support-v4.jar.properties"
     to="libs/android-support-v4.jar.properties" />
</recipe>
これで、設定は完了です。

試してみる

新規作成ウィーザードからAndroid Application Projectを作成すると
/libs配下にandroid-support-v4.jar.propertiesが追加されるようになりました。


まとめ

今回の記事ですが、メソッドをコード補完やOverrideした時に「引数の変数名がarg0, arg1...で分かりにくいよね?」というのが発端でした。
結論としては
Support Libraryのクラスを利用したときに
Support Libraryのソース関連付けを行っていなかったため
Eclipseにより変数名がarg0, arg1... と設定されていたということです。

この辺りの話は、下記の本に記載があります。
炎のAndroid開発道場
リックテレコム 書籍情報
http://www.ric.co.jp/book/contents/book_901.html

.gitignoreなんかも、プロジェクト作成時に追加されるようにすると便利かも知れません。


2013年3月14日木曜日

NFC端末片手にサンシャイン水族館に行ってきた


お目当てはこれです。

ITpro
サンシャイン水族館でNFCを活用したスマートフォン向け情報サービス
http://itpro.nikkeibp.co.jp/article/NEWS/20130205/454421/

機材

AQUOS PHONE EX(SH-04E)
docomo with series AQUOS PHONE EX SH-04E | 製品 | NTTドコモ http://www.nttdocomo.co.jp/product/with/sh04e/

アプリのインストール

入り口にこんな看板が置いてあり、NFC or QRコードからGoogle Playにリンクして「Ikesu」アプリをダウンロードする仕組みになっています。

行ってアプリ動かなかったらアレなので、事前にインストールして動作確認済みでしたが一応タッチしてみたところ、なかなかNFCタグを認識してくれず苦戦...
(AQUOS PHONE EXはNFCの感度が悪いのかもしれません)
やっと認識したと思ったら、今度はWiMAX圏外でGoogle Playにアクセスできず。。
装備A(WiMAXモバイルルーター)から装備B(LTEモバイルルーター)に切り替えて進軍。

タッチしてみる

入り口でアプリをインストールして進むと

水槽の周りに、こんな感じにタグが置いてあるのでペタペタとタッチしていくと「Ikesu」アプリの中にお魚がコレクションされていきます。
(ここでも、なかなかタグが認識されずペタペタとはいかず...)

お魚をゲットすると、アプリの画面はこんな感じに。
(同時に表示できる魚は5種類まで)

まとめ

帰ってから気付いたのですが、このサービス「NFC」だけじゃなく「おさいふケータイ(Felica)」も対応していた模様。
NFC/Felica両対応のAQUOS PHONE EXはどちらで動いていたのかが謎。。

こんな感じに壁にタグが設置してあるところでは、意外とタッチするのが億劫でした。
(僕の手首関節の柔軟性の問題かもしれません...)
アプリ内蔵のQRコードリーダーも使ってみましたが、QRコードでいいんじゃね?と思ったり・思わなかったり。


因みに「Ikesu」アプリですが、はじめから一種類魚が登録されているので、現地に行けない方もインストールして雰囲気は試せます。

Google Play
Ikesu -Touch Aquarium-
https://play.google.com/store/apps/details?id=air.jp.co.brilliantservice.TouchAquarium2


最後に

写真たくさん撮ったのでペタペタ。
(Ikesuアプリとカメラを行ったり来たりして、AQUOS PHONE EX頑張った!)