主に言語とシステム開発に関して RSSフィード



2013-02-14

デブサミ2013 参加レポート

|


今年も目黒・雅叙園にて,翔泳社主催のDevelopers Summitデブサミ) 2013が開催されている。

事前申し込み制,参加は無料。

ITエンジニアやデベロッパ向けの講演・セッションが多数。年一度2月に開催される,開発者の最大のイベント。


今年の目玉は,まつもとゆきひろ氏によるRuby2.0のトークだったろう。(満席で見れなかったが)


私は,主にスマートデバイス関連で,先端情報の収集のために赴いた。

講演者のスライドは,下記URLからまとめて閲覧できる。

[東京]Developers Summit 2013

https://event.atmarkit.co.jp/events/7bcf3deb4433119c2b580dd610ad0ce1/materials

以下は簡潔なレポート。


2/14 木曜

Androidセキュアコーディング

一番実際的で,来たメリットがあった。

(1)300ページ以上ある無料のセキュリティ・ガイドラインが,PDFで存在。

JSSECによる『Android アプリのセキュア設計・セキュアコーディングガイド』

http://www.jssec.org/report/20121119_securecoding.html

  • 忙しい開発者のために,コピペ可能なサンプルコードが多い。
  • ※NGなコードを列挙して詳しく掘り下げて説明しても,忙しい人には読んでもらえないため。

(2)Androidの脆弱性データベースが,オンラインに存在。

JVNのiPediaでAndroidの脆弱性を検索

http://jvndb.jvn.jp/search/index.php?mode=_vulnerability_search_IA_VulnSearch&lang=ja&useSynonym=1&keyword=Android


(3)脆弱性の事例,内部仕様の観点から

  • Activityにexported="false"がないので,他アプリから踏み台として利用されてしまう
  • SDカードにデータを保存してしまうため,パーミッションがかけられず他アプリから読み取られてしまう(MODE_PRIVATE)
    • アプリ専用のフォルダに保存せよ
  • LogCatに機密情報をログ出力している状態のままリリースしてしまった
    • リリースビルドではログレベルを抑制せよ
  • Intent, WebView, BrowsableIntentのscheme,AccountManagerなどの利用に注意。
    • 特にWebViewは全体の7割が使用。全体の3割がaddJavaScriptInterfaceしている

何とも実用的。


■外出

ここで昼ご飯を買出しに。

なぜなら,会場の近くには飲食店やコンビニがないので,早めに調達しないと痛い目を見るから。

雅叙園には何度も来ているが,ITイベントで来るたびに,玄関やエスカレータが金色なのが目につく。メタリックかシルバーだといいのだが。


会場ではDeNAの手さげがもらえて,Amebaのペットボトルウォーターが飲み放題。なぜかカップうどんが配布された(クラウドんというジョークだそうだ)


ラウンジでは飲食自由だが,席が少ない。

エンジニア同士の「出会いの場」とするには,ちょっと無理がある。


廊下の各種コミュニティや企業スペースは,一巡の価値はある。

首から下げる受付参加証には,twitterのアカウントIDを記載。ハンドルネームで自己紹介することになる。



自動改札機の運賃計算プログラムのデバッグ手法 〜10の40乗のパターンをいかにテストするか〜

これはPublickeyで既に読んで知っている内容だったのだが,スピーカーさんのパーソナリティが面白くて,飽きずに聞けた。

自動改札機の運賃計算プログラムはいかにデバッグされているのか? 10の40乗という運賃パターンのテスト方法を開発者が解説

http://www.publickey1.jp/blog/12/_1040.html

  • テストケースの全体を定義する
    • 安全なものをカット(あり得ないケース)
    • 妥当なものをカット(まずないのでいらないケース)
    • 許容可能なものをカット(同じようなものだからまあいいかというケース)

全体を定義するからこそ,

  • 絞り込む事が可能。
  • バグ発見時に,テストケースを省いた理由が明確なので,修正とトラッキングが容易。
  • 仕様書をテストする。
    • 仕様記述言語VDM++を使う。

■グリーにおけるスマホアプリ開発〜ネイティブ編

これは正直,期待した割には,あまり面白くなかった。

なぜだろう,本音を言うと,発表の内容も発表自体も,大学1・2年の「宿題の発表」みたいなものを聞いているふうな感じに過ぎなかったのだが…。

周りでノートPCで一生懸命メモを取っていた人も複数いたが,途中で飽きてやめてしまっていた。

Twitterのハッシュタグで見る限り,周りも期待外れの様子だった。


内容的には,Webアプリとスマホアプリの通信タイミングの設計方法は違う,と述べたようなぐらい。

  • UIをブロックしないでサーバと整合性を保つには,非同期通信。
  • 非同期通信が成立するためにいるのは,ユーザのアクションログ。
  • 更新頻度が低いデータはローカルに。
  • Webアプリ時代にCDNに置いていたようなアセットは,初回ダウンロード。
  • グリーではUnityを使用。画面遷移図とコード骨格はYAMLGraphVizで自動生成。
    • ※グラフビズのことをグラファイズと読んでいた。グラフを可視化(Visualize)するからグラフビズなのだが。

RubyiOS/Androidアプリを作るMobiRuby

一番面白かった。


MobiRubyは,Ruby(mruby)でスマホアプリが作れる不思議ツール。

その作者が,よりによってJavaScriptでスマホアプリが作れるあのTitaniumを出している会社の人だったとは…

  • Luaとかでスマホアプリを開発する試みは,DSLが書けないので微妙。

面白いのは,ツールだけでなく,開発しているご本人も。

  • 「風呂で仕事をする人」として知られている
  • 「(〜〜さまざまな理由)で,順調に遅れています。」

なお,各セッションのスライドで「PROS/CONS」を「長所/短所」の略語として使っている点が共通していた。

"pros and cons"、"nature and nurture"とはどういう意味か?

http://frbourbon.cocolog-nifty.com/english/2004/11/pros_and_consna.html

  • ラテン語の"pro"="for":「賛成の」、"con"="against":「反対の」から派生した表現

明日も期待。



2/15 金曜

■実践!スマホアプリのマネタイズ

スピーカーさん本人も言っておられたが,一番「生々しい」話。


来てよかった。初日同様,10時始まりのセッションはいきなり密度が濃い。


始まり方からして,いかにも今風:

  • 「このスライド,SlideShareにもうアップしてあるんで,今URLをつぶやきますから,手元で見てください。・・・はい,つぶやきました。拡散してくださいって事ですからね」

内容

  • Google PlayもApp Storeも,マーケットの上位アプリはほぼ100%,無料アプリのフリーミアムであり,有料アプリという発想は基本的にあり得ない
  • ロングテールでどうやって多数インストール?
    • ASO(アプリストア最適化):KW選定,アイコン命(色・枠・線),スクリーンショット。わかりやすくて落としやすいように
    • 母数が多くなくても,アンインストール数が少なければ高評価になる
    • 30日間の新着アプリ掲載期間を大事に
    • アプリ内からソーシャルポストを促せ
    • AppAnnieで競合アプリを解析せよ http://www.appannie.com/

フリーミアムでマネタイズするためのアプリ内課金(IAB,In App Billing)

  • Consumableなものは,提供タイミングに注意(必要性を納得して感じさせること。あともう少し,というタイミング)

■グリーにおけるスマホアプリ開発〜HTML5

混み具合がすごい。

昨日のネイティブ編とは,質が完全に異なっており,凝縮された時間を過ごせた。

昨日のセッションは,同時刻の競合セッションとの兼ね合いで力を入れない発表だったという事だろうか?

それほど,今日のグリーは昨日に比べて面白かった。


チーム体制

  • 企画(画面遷移図)
  • →デザイン(ワイヤフレーム)+演出(HTML5)
  • →フロントエンド(画面をマークアップ)+バックエンド(PHPEthnaSmarty

という体制で,画面遷移図はデザイン屋に降りてくる。

  • バックエンド屋がリッチUIに手を出すのは難しい。
  • フロントエンド屋に,はじめからテンプレート化を意識させ,ハードコーディングさせない。(強く同感)

テクニカル

  • Android4.xでは画面の解像度が高すぎて重くなり,Canvasを使うと2FPSだったりする。その場合,うまくCSS利用に切り替わるようライブラリを使う。
  • SWFはまだ生きている技術。HTML5に変換して使う。
  • ビルド作業を自動化するとか,画像はアプリ内にできるだけキャッシュするとか

お昼ご飯を食べる際,雨が。しかし夜,帰宅時には止んでいた。


■モバイル HTML5 による世界への挑戦

DeNAの人。昨日のテスト手法の人と同じく,パーソナリティが面白い。

海外と照らした場合の国内事情がわかって興味深かった。

  • 日本はモバイルHTML5については米国を抜いており,最先端を行っている
  • しかしAndroidのHTML5+CSS3対応は絶望的。Webの世界で言うIE6対策よりもひどい状況。
    • 各ベンダが,自社端末のベンチマークを上げるためにあれこれ手を入れているため。
  • HTML5については,アプリ追求ではなく,Webでしかできないことを追求すべき
    • 複数端末でメーラをインストールするのではなくGMailを開けば済む,という利便性と同じ

■Androidアプリケーション開発者必見、 今求められている暗号化、難読化、セキュリティテクノロジ

これは,きわめてよろしくなかった。

途中退席者が多すぎ。

どのようによろしくなかったのかについては,Togetterにまとめられているので見てみるとよい。

私もツイートしている。

2013/02/15 デブサミ2013【15-E-4】Androidアプリケーション開発者必見、 今求められている暗号化、難読化、セキュリティテクノロジ #devsumiE

http://togetter.com/li/451438

念のため断わっておくが,何かスピーカさんが悪いというわけではない。

日本のパスポートの中身のデータの暗号化をしてくれている,ありがたい会社の人だ。


■ブラブラ

この時間は空き。


廊下にオライリーのコーナーがあったので,本を買い物。

全部の書籍が10%オフで消費税もカット,ということだったので,この機を逃すまいと。


■開発者の "資産形成" につながる Action とは?

こういう啓発系のセッションがあると,頭が切り替わってモチベーションが湧く。

  • 開発って何?(→人間にいいものを生み出す)
  • SW開発って何?(→ユーザのニーズをSWに変換)
  • 価値って何?(→使用価値・交換価値)
  • 開発したSWの価値はどう決まるの?(→まずは交換価値で決まり,その後に使用価値で増減)

という根本的な問いかけから。


あとあと思いだしたくなる名言が多かったような。

  • 技術者としての路線は,
    • お客にビジネスやサービスを提供するか,
    • 新しい技術を提供するか,
    • 基礎的なアルゴリズムなどを研究するかの3択
  • 行なった作業のうち,再現可能な部分が多いほど,エンジニアとしての自分に資産が残る
  • 自分が価値を見いだすポイントを,テクノロジ以外で何か見つけよ。UXとか。
  • マクガイバリズム=目の前にある困難に対して,とにかく今あるものだけで何とか対処してしまおう
  • SWは目に見えず,UIを通して見えるようになる。だから,そこで差がつく。
  • どういうスキルをエンジニアとしての自分に貯金して,その貯金で何をしたいのかを真剣に考えなさい

あとは,コミュニケーション能力に「英語」という一点が加わるだけで,圧倒的に年収が変化する点とか。


■反復型ソフトウェア開発の勘所

この人も良いパーソナリティだった。マイクロソフトの方。

下記の本を書いた人。

ここからの引用が多かったらしい。


開発に限らず,時間術に関連した物の考え方としても参考になった。

  • 全開発期間=タイムボックスという箱
  • 入りきらないとわかった場合,どうしたらよいか
    • 全部入れるのをあきらめて,いらないものを探し,次回のタイムボックスにpantするのが正解。
    • 目の前のゴールが動いてはならない。まずは目下のゴールをクリアして,その次のタイムボックスでまたトライすればよい。
    • 優先順位が大事。15分のミーティングも,15分でいきなりブツッと切るのではなく,優先順位の低いものを見極めて,それは次回のミーティングに回せばよいということ。

大事なことを思い返させてくれた気がする。


あとは,CI(継続的インテグレーション)の話も。

Windows NTは毎週水曜日にウィークリービルドしてましたよ,と。



タブレット進化論

ラスト。しかし,これが本番だったと言ってもよいのでは。

今回,スマートデバイス狙いで参加した私にとっては,これは最高の締めセッションだった。

スピーカーは,下記のブログを書いているITジャーナリストさんである。

ITジャーナリスト星暁雄の"情報論"ノート

http://hoshi.air-nifty.com/


使われたスライド

Developers Summit 2013【15-B-8】タブレット進化論

http://www.slideshare.net/AkioHoshi/developers-summit-201315b8-16548123


いまのタブレットの勢い

  • iPadは,iPhoneの3倍の速度で普及した。(スライドの16ページ目のグラフを見よ。)
  • Androidは,iPhoneの6倍の速度で普及した。
  • OSシェアは,もはやPCよりもスマートデバイスのほうが多くなった。→「モバイルファースト」
  • 従来,PCを持ちこめない現場にも,タブレットなら持ちこめる。そこに新しいユースケースがある。手術室,工事現場,学習塾,カード決済レジ,接客業等でたくさん活用事例あり。

今後のタブレットの目の付けどころ

  • 今の世界人口の分布は
    • 10億人はある程度お金を持っており
    • 20億人はあまりお金を持っておらず
    • その他は極貧である。

1番目は,台数からして伸びきった。

とすると,次は2番目の層に目が向けられる。

そして,低価格タブレットが販売されるようになる。

  • インドでは学生向けで1600円など。
  • iOSのシェアは,安いデバイスを出すかどうかで今後大きく左右されうる。

質疑応答の時間があったので,思い切って質問をぶつけてみた。

  • Q. スマホとタブレットは,今後,共存しうるのか?
  • A. 境目があいまいになっていくと思う。何にでも「〜〜フォン」という名前はつけられるから。

なるほど。


すると…。。



2日間を通して,今後やるべき事,やりたい事,身につけたい事,調べたい事が一挙に色々見えてきた気がする。

例のごとく,ここには書けないけども。有意義なイベントだった。



デブサミ2013、講演関連資料まとめ

http://codezine.jp/article/detail/7003


全セッションのテーブル

http://event.shoeisha.jp/detail/1/timetable


【デブサミ2013】セッションレポート:CodeZine(コードジン)

http://codezine.jp/article/corner/476


 

2013-01-21

今から5分で,開発中のAndroidアプリを単体テストしよう (JUnitで自動テストする方法)

|


開発中のAndroidアプリを,単体テストフレームワークJUnitを使ってテストする。


Eclipse上で,サンプルアプリを作り始めてから,

アクティビティ内のロジックやUI操作をテストする所までを5分で行なう。

早ければ3分。


記事の末尾には,Androidアプリの自動テストに役立つ情報を集約したリンク集を掲載。


なお,エミュレータを立ち上げていると重くて5分で終わらないので,アプリの動作確認は実機で行なうこととする。


以下から開始。

手順


ここでは新規PJ作成時にテストPJを同時生成するのではなく,

既存PJに対して,テストPJを後付けで新規作成する。



まず,テスト対象のプロジェクトを作成する。


ファイル>新規>Android Application Project で,

「HelloJUnit」という名称で新規PJを作成。

Create Activityはチェック付のまま。



サンプルアプリの仕様を考える。

入力ボックスが2つと,ボタンが1つある。

ボタン押下時には,一方の入力文字列が「Hello, 〜!」と加工されて,もう一方に出力される。



画面のレイアウトとして,下記をコピペ。

activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <EditText
        android:id="@+id/et1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
    />

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

    <EditText
        android:id="@+id/et2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
    />


</LinearLayout>


アクティビティのコードとして,下記をコピペ。

MainActivity.java


package com.example.hellojunit;

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

public class MainActivity extends Activity {

    private EditText et1;
    private EditText et2;
    private Button btn1;


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

        et1 = (EditText)findViewById(R.id.et1);
        et2 = (EditText)findViewById(R.id.et2);
        btn1 = (Button)findViewById(R.id.btn1);

        // 押下時
        btn1.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                // 取得
                String s = et1.getText().toString();

                // 加工
                s = addHello(s);

                // 表示
                et2.setText(s);
            }
       });
    }


    // 加工
    public String addHello(String s) {
        return "Hello, " + s + "!";
    }
}



次に,テスト実行用のプロジェクトを作成する。

新規>その他>Android>Androidテスト・プロジェクト

で,次へ。

PJ名として,HelloJUnitTestを入力。


「Select Test Target」のダイアログで,

テスト対象のPJとして「HelloJUnit」を選択。

完了。



このプロジェクトのAndroidManifest.xmlを開いてみると,

instrumentationというエレメントがある。

この要素は,テスト対象のアプリをモニタする役目を持つ。

要素のtargetPackage属性には,テスト対象のPJのパッケージ名(com.example.hellojunit)がセットされている。

instrumentation

https://sites.google.com/a/techdoctranslator.com/jp/android/guide/manifest/instrumentation-element

  • アプリケーションのシステムとの相互作用をモニタすることを可能とする Instrumentation クラスを宣言します。
  • android:targetPackage : Instrumentation オブジェクトを実行するアプリケーションです。アプリケーションは、マニフェストファイルで <manifest> 要素により割り当てられたパッケージ名により識別されます。

また,srcフォルダ内に

  • com.example.hellojunit.test

というパッケージが追加されている。

このフォルダの中にテストケースを追加してゆく。


testフォルダを右クリックし,新規>クラス>適当な名前のクラスを作る。

ここではTestMainという名称にした。



まずは,画面に関係の無いロジックだけをテストしてみよう。

ソースコードとして下記をコピペ。


package com.example.hellojunit.test;

import com.example.hellojunit.MainActivity;

import android.test.ActivityInstrumentationTestCase2;

/**
 * hellojunitプロジェクトのテスト。
 *
 */
public class TestMain extends ActivityInstrumentationTestCase2<MainActivity> {

    private MainActivity mActivity;


    // 必須のコンストラクタ
    public TestMain() {
        super(MainActivity.class);
    }


    @Override
    protected void setUp() throws Exception {
        super.setUp();

        // アクティビティを取得
        mActivity = getActivity();
    }


    public void testAddHelloLogic() {
        assertEquals(mActivity.addHello("山田"), "Hello, 山田!");
    }

}

これで,プロジェクトを右クリック>実行>Android JUnit Test

で,実機を選択して実行する。


Logcatの表示:


Launching instrumentation android.test.InstrumentationTestRunner on 〜〜
Sending test information to Eclipse
Test run finished


Eclipse上では「JUnit」のタブが出現し,実行結果が出る。

  • 実行:1/1 エラー:0 失敗:0

全テストを通過したので,緑色のバーが表示される。


Android SDKが提供するTestCase

http://www.atmarkit.co.jp/fsmart/articles/androidtest02/02.html

  • Instrumentationは、Androidのシステムをフック/制御するメソッドの集まり。アクティビティにキーイベントも送信できる
  • ActivityInstrumentationTestCase2は,モックを利用しない機能テスト(システムテスト)での使用を想定。テスト対象のアクティビティのライフサイクルが実行される
  • ActivityUnitTestCaseは、モックのContextを注入して独立した環境下でテストを実行できる。しかしActivityのライフサイクルは自動的に呼び出されない。

Androidのテストプロジェクト – JUnit test

http://www.netplan.co.jp/archives/1967

  • パッケージ名を指定するコンストラクタはdeprecated(使用すべきではありません,と警告される)

ActivityInstrumentationTestCase2

http://wikiwiki.jp/android/?ActivityInstrumentationTestCase2

  • クラス名の末尾に「2」がつく理由:ActivityInstrumentationTestCaseというクラスも存在しますが、非推奨となっている


では次は,UI操作も含めてテストを書き直してみよう。


package com.example.hellojunit.test;

import com.example.hellojunit.MainActivity;
import com.example.hellojunit.R;

import android.test.ActivityInstrumentationTestCase2;
import android.widget.Button;
import android.widget.EditText;

/**
 * hellojunitプロジェクトのテスト。
 *
 */
public class TestMain extends ActivityInstrumentationTestCase2<MainActivity> {

    private MainActivity mActivity;
    private EditText et1;
    private EditText et2;
    private Button btn1;


    // 必須のコンストラクタ
    public TestMain() {
        super(MainActivity.class);
    }


    @Override
    protected void setUp() throws Exception {
        super.setUp();

        // アクティビティを取得
        mActivity = getActivity();

        et1 = (EditText)mActivity.findViewById(R.id.et1);
        et2 = (EditText)mActivity.findViewById(R.id.et2);
        btn1 = (Button)mActivity.findViewById(R.id.btn1);

    }


    public void testAddHelloLogic() {
        assertEquals(mActivity.addHello("山田"), "Hello, 山田!");
    }


    public void testUI() {
        // 最初は空
        assertEquals(et1.getText().toString().length(), 0);
        assertEquals(et2.getText().toString().length(), 0);

        assertTrue(btn1.isEnabled());


        // UIスレッド上で画面操作
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                et1.setText("World");

                btn1.performClick();

                assertEquals(et2.getText().toString(), "Hello, World!");
            }
        });
        // UIスレッドが終了するまで待つ
        getInstrumentation().waitForIdleSync();

    }

}


JUnitタブ上で「テストの再実行」というボタンを押下。


テストを実行すると,実機上では,一瞬だけ画面が表示される。

テストにあわせて素早く,画面が自動操作されているのだ。


実行結果が「実行2/2」で,グリーンバーが表示されればOK。


ロジックもUIも,JUnitからテストを実行することができた。



※ここで,画面部品であるビューの操作は,すべてrunOnUiThreadでUIスレッド上で行っている。

もしそれを守らないと,下記のようなエラーになる。


android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRoot.checkThread(ViewRoot.java:2961)
at android.view.ViewRoot.invalidateChild(ViewRoot.java:646)
at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:672)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:2511)
at android.view.View.invalidate(View.java:5268)
at android.widget.TextView.invalidateCursor(TextView.java:3797)
at android.widget.TextView.spanChange(TextView.java:6660)
at android.widget.TextView$ChangeWatcher.onSpanAdded(TextView.java:6785)
at android.text.SpannableStringBuilder.sendSpanAdded(SpannableStringBuilder.java:906)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:611)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:514)
at android.text.Selection.setSelection(Selection.java:74)
at android.text.Selection.setSelection(Selection.java:85)
at android.text.method.ArrowKeyMovementMethod.initialize(ArrowKeyMovementMethod.java:268)
at android.widget.TextView.setText(TextView.java:2816)
at android.widget.TextView.setText(TextView.java:2652)
at android.widget.EditText.setText(EditText.java:81)
at android.widget.TextView.setText(TextView.java:2627)
at com.example.hellojunit.test.TestMain.testUI(TestMain.java:56)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:204)
at android.test.InstrumentationTestCase.runTest(InstrumentationTestCase.java:194)
at android.test.ActivityInstrumentationTestCase2.runTest(ActivityInstrumentationTestCase2.java:186)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:529)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1448)




役立つリンク集


Androidアプリ開発に特有のテスト事情(と,その解決策):

Android開発で泣かないための「テスト」の重要性 / 第1回Androidテスト祭りレポート(2011年9月)

http://www.atmarkit.co.jp/fsmart/articles/android_event01/01.html

  • 端末ごとに性能差。メーカーの独自カスタマイズで有名アプリが動作しないことも。キャリアとメーカーとOSバージョンの膨大な組み合わせ
  • 試験コストを掛けて大ヒットを狙うか。 or 互換性を低くして,超高速開発でニッチ市場に対して小ヒットを狙うか。
  • 組み込み機器では割り込みに起因する不具合が多く,Androidも同じ。ある状態から別の状態に遷移中に通話やメール、アラームなど何らかの割り込みが入っても不具合が生じなければ,きちんと実装されているという判断も。

トップAndroidデベロッパーはこうしてアプリの品質テストを行っている(2012年6月)

http://jp.techcrunch.com/archives/20120602android-qa-testing-quality-assurance/

  • Googleが公表するシェアデータを見ながら,2:8の法則(80/20ルール)に従って,ユーザーの大半をカバーする少数の機種を識別。
  • 自社アプリの利用ユーザ層のデバイス分布を調べて,さらに対象機種を調整する。
  • 弱い機種や古いOSにノーと言うことも大切


一度は読み通すべき,良質なまとめやリファレンス

o. テスト - ソフトウェア技術ドキュメントを勝手に翻訳

http://www.techdoctranslator.com/android/guide/testing


連載インデックス「Androidアプリ開発テスト入門」 - @IT(2011〜2012年)

http://www.atmarkit.co.jp/fsmart/index/androidtest.html

  • 基礎知識とテスト自動化から,モックやCI,システムテスト・受け入れテストまで

ニュース - Androidアプリのテストガイドライン、OESFが公開:ITpro(2012年11月)

http://itpro.nikkeibp.co.jp/article/NEWS/20121128/440557/



単体テストのチュートリアル集:

AndroidアプリケーションをJUnitでテストする | Android開発メモ

http://itinfo.main.jp/tan/?p=35

  • プロジェクト作成時に,テストプロジェクトを同時生成する方法
  • ActivityInstrumentationTestCase2を使用

JUnitを使ってAndroidの自動テスト(1) - スマートフォンアプリ開発会社のエンジニアブログ

http://d.hatena.ne.jp/glpgsinc/20110504/1304499331


AndroidでのUnitTestの始め方 | ひたすらメモするだけのブログ

http://www.yaunix.com/2011/01/08/android%E3%81%A7%E3%81%AEunittest%E3%81%AE%E5%A7%8B%E3%82%81%E6%96%B9/

  • 既存のプロジェクトに,テストプロジェクトを後から新規作成する方法
  • ActivityUnitTestCaseを使用

JUnitについて:

Android 向け 単体テスト UnitTest(JUnit) を作成する

http://uguisu.skr.jp/Windows/android_junit.html

  • JUnitで用意されているメソッドの一覧表

JUnitチュートリアル

http://www.次世代創造機構.jp/android/androidLecture/JUnit/JUnit.html

  • JUnitそのものの解説・入門と,Androidアプリでの実行方法

技術 / Android / TestAutomation

http://www.glamenv-septzen.net/view/987

  • JUnit + Instrumentationは,Android上でのActivityTestの基本。
  • instrumentは,仮想マシンとクラスの間に入り込んでインターセプトする。android.testパッケージ以下にクラスが存在。
  • 各種Test実行時に,内部的には,UIを操作「したことに」している。


他のテスト方法やツール:

ASCII.jp:NTTレゾナント、Androidの実機テストをクラウド

http://ascii.jp/elem/000/000/743/743044/

  • データセンターにある「実機」の画面をパソコンに転送し、インターネット経由で、パソコンから実機を遠隔操作
  • Androidアプリをさまざまな機種で動作テストする費用は,アプリ開発にかかる全コストの55%にあたるケースもある。
  • 2012年の情報

GroovyなAndroidテスト #atest_hack

http://www.slideshare.net/alterakey/groovyandroid

  • Groovy(JVM上の動的言語)とRobolectric+Spockを使ってAndroidアプリをBDD (振舞駆動開発)する方法のスライド。
  • 2012年9月の情報

Androidアプリの自動テストツールで最も有望か - 「NativeDriver」,Google製「WebDriver」の拡張 (公式のAndroid版Selenium

http://d.hatena.ne.jp/language_and_engineering/20110930/p1

  • 2011年9月の情報
  • UIテストに特化。ビジネスロジックなどの内部レイヤ向けではない。

Taosoftware: AndroidのテストツールMonkey

http://www.taosoftware.co.jp/blog/2009/04/android_monkey.html

  • adbを経由してモンキーテストを実施する方法。2009年の情報。


関連する記事:

Androidアプリの自動テストツールで最も有望か - 「NativeDriver」,Google製「WebDriver」の拡張 (公式のAndroid版Selenium) - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20110930/p1


Androidでライブラリ・プロジェクトを作成し,Eclipse上でコードを共有しよう - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20130116/AndroidLibraryProjectOnEclipse


Ruby on Railsのテストの書き方 (モデルの単体テストと,コントローラの機能テスト) - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20091023/p1

 

2013-01-16

Androidでライブラリ・プロジェクトを作成し,Eclipse上でコードを共有しよう

|


Androidアプリ開発において,共通部分のJavaコードをあちこちで流用したいケースはよくある。


こういった共通コードや便利クラス類は,ライブラリプロジェクトにまとめる。

そして複数のプロジェクトから参照可能にするのがよい。

ライブラリ自体の改良やデバッグ・新機能を,ワークスペース内の全プロジェクトで楽に共有できるからだ。


下記はその方法と手順。


サンプルコードと手順


新規Android Applicationプロジェクト作成。

プロジェクト名は

AndroidLibTest

とする。

  • minimum required SDK,
  • Target SDK,
  • Compile With

は,それぞれ自由に決めてよいが,手元に控えておく。

次へ。

Create Custom Launcher Iconにはチェックしない。

Create Activityにもチェックしない。

Mark this project as a libraryにチェック。


src内にLibHogeクラスを作成:


package com.hoge;

public class LibHoge {

    public static String hello(String s)
    {
        return "Hello," + s;
    }

}

これが,ライブラリプリプロジェクトとなる。


次に,利用側のPJを作成。

PJ名は

AndroidUseLibTest

とする。

各種APIレベルは,とりあえずライブラリ側のPJとあわせる。

次へを連打。


プロジェクトが作成されたら,

プロジェクト→プロパティ→Android→ライブラリに,AndroidLibTestを追加。


このライブラリを呼び出してみよう。

メインのアクティビティに,初回でトーストが表示されるようにコードを1行だけ追加。

onCreateメソッド内に,下記を追記する。


        Toast.makeText(this, LibHoge.hello("fuga"), Toast.LENGTH_LONG).show();

LibHogeにカーソルをあてて,必要なクラスを普通にインポート可能。


PJを実行してみる。

画面に「Hello, fuga」とToast表示される。

ライブラリとして参照しているプロジェクト内のコードを,無事に呼び出せている。



では,ライブラリに変更を加えてみよう。

LibHogeに下記のメソッドを追加。


    public static String hello2(String s)
    {
        return "Hello," + s + "!";
    }


ライブラリ利用側のコードも,「LibHoge.hello2("fuga")」に書き換える。


すると,メソッドの未定義エラーになる。

これは,ライブラリ側のプロジェクト内で,jarファイルが更新されていないためだ。


ライブラリのプロジェクトで プロジェクト→クリーン すれば,jarファイルが自動的にビルドし直される。

ライブラリを利用する側のコードもエラーが出なくなる。



このようにして,ライブラリ・プロジェクトの資産を,複数の外部プロジェクトから共有することができる。


なお,リソースも共有できる。

ライブラリ側にlayoutとしてxmlを作成した場合,すぐにライブラリ使用側のプロジェクト用のR.javaが再生成され,レイアウトを呼び出すことが可能。



参考リンク

Androidでライブラリプロジェクトを作成する - iPhone/iPad/Androidアプリ開発ブログ

http://d.hatena.ne.jp/waochi/20101108/1289181917

  • 手順の略述。プロジェクトのプロパティで「Is Library」にチェックを入れ,プロジェクトの右クリックで手動でJARをエクスポートする。
  • 2010年の情報。

ADTによるLibrary Projects - Kazzzの日記

http://d.hatena.ne.jp/Kazzz/20100522/p1

  • "is Library"をチェックする方法のおかげで,Javaのビルド・パスの設定変更が不要になった。
  • 2010年の情報。

Android Tips #13 ライブラリプロジェクトを活用する | クラスメソッド開発ブログ

http://dev.classmethod.jp/smartphone/android-tips-13-library-project/

  • ライブラリプロジェクトを追加する実例。2012年の情報。

穀風: Android プロジェクトを jarライブラリ化する

http://kokufu.blogspot.jp/2011/06/android-jar.html

  • res以下をjarに含められず困っていた頃の情報。2011年。

Androidでライブラリプロジェクトを作成する際の考慮事項 - Develop with pleasure!

http://d.hatena.ne.jp/techmedia-think/20110622/1308740751

  • 共通のリソースIDの定義による競合を防止せよ
  • GoogleMapを使いたい場合は,両プロジェクトのマニフェストXML内でuses-libraryで参照せよ
  • 互換性。ライブラリプロジェクトで使用するAPIは、アプリケーションプロジェクトのAPIと同じバージョンか、それより低いバージョンであるべき
  • 2011年の情報。

Libraryプロジェクトのテスト - hidecheckの日記

http://d.hatena.ne.jp/hidecheck/20120623/1340445496

  • ライブラリを利用したアプリケーションのテストではなく,ライブラリのプロジェクトを単体でテストするための方法。
  • 2012年の情報。


補足

プロジェクト作成時に,APIレベル(Android SDKのバージョン)を3つ指定する。

これらの項目の意味をメモ。


  • Minimum Required SDK:アプリの最低サポートレベル。
    • →ユーザ層をどれぐらい広げるか,という問題。
  • Target SDK:アプリのいわば公式・推奨サポートレベル。
    • →どのユーザ層に最も焦点をあてたアプリか,という問題。
  • Compile With:コンパイルするレベル。
    • →プログラマが利用する開発ツールに関する問題。言うまでもなく,最新のツールが良い。
    • Google Mapsを使いたい場合,ここでGoogle APIsを選択しておく必要がある。

最初のAndroidアプリ プロジェクトの作成

http://techfun.cc/android/firstandroid1.html

  • Minimum Required SDK: 動作するために必要な最低限のレベル
  • Target SDK: 対象のレベル
  • Compile With: コンパイルするレベル

[Android] 公式チュートリアルをなぞる

http://www.technosketch.com/ja/resources/technotes/2012/12/06/android-official-tutorials/

  • Minimum Required SDK:
    • Choose the lowest version of Android that your application will support.Lower API levels target more devices, but means ferwer features are available. By targeting API 8 and later, you reach approximately 95% of the market.
    • アプリがサポートする最低のレベル。APIレベルを下げるとサポートデバイスが増える。しかし使える機能が少なくなる。
  • Target SDK
    • Choose the highest API level that the application is known to work with. This attribute informs the system that you have tested against the target version and the system should not enable any compatibility behaviours to maintain.
    • アプリが動作することがわかっている最も高いAPIレベル。このターゲットバージョンでテストが行われたことをシステムに知らせる。おかげで,システムは互換性保持の処理をしなくて済む。
  • Compile With
    • Choose a target API to compile your code against, from your installed SDKs. This is typically the most recent version, or the first version that supports all the APIs you want to directly access without reflection.
    • コードをコンパイルするAPIレベル。通常は最新バージョン。

Your First Android Hello World Java Program

http://tekeye.biz/2012/your-first-android-java-program

  • 3つのAPIレベルの組み合わせ方の例。API 8: Android 2.2 (Froyo),API 6: Android 4.1 (Jelly Bean),API 17: Android 4.2 を使う。

My First Android Project

http://androidrstar.blogspot.jp/2013/01/my-first-android-project.html

  • Minimum Required SDK: Choose the lowest version of android that your application will support. Always prefer API level 8 to reach approximately 95% of the market.
  • Target SDK: Choose the Target Version, against which you want to test your application. You can prefer to latest version.
  • Compile With: Choose a target API to compile your code against, from your installed SDK. Always choose the highest API level.

http://webcache.googleusercontent.com/search?q=cache:y5deIwsiedEJ:https://www.shoeisha.co.jp/static/images/faq/365/Android-programing_p30.pdf+&cd=10&hl=ja&ct=clnk&gl=jp&client=firefox-a

  • Minimum Required SDKは、最小 SDK バージョン
  • Target SDK は,ビルドターゲット
  • Compile With では,特に問題なければ、デフォルトのままでも良い。ビルドターゲットに合わせることもできる

わざわざ最低バージョンを指定する目的は,互換性のため。


例えば,1.6系のスマホでもFragmentを使用したいのでandroid-support-v4.jarを組み込む場合など。

このような場合,メインのターゲットとなるバージョン層とは違い,もっと古いバージョンをどこまでサポートするかが問題になる。

Android 2.xでAndroid Compatibility Packageを使ってFragmentを動かしてみた

http://www.swingingblue.net/mt/archives/003373.html

  • タブレットではないHoneycomb以前のバージョンでもFragmentを使えるようにするライブラリが提供された

Android Tips #28 Android 1.6 から Fragment を使う

http://dev.classmethod.jp/smartphone/android/android-tips-28-support-fragment/

  • Fragment は Android 3.0 (APIレベル11) から導入されたコンポーネントで,Support Package でサポートされているので Android 1.6 (APIレベル4) から使用することができる

Android Compatibility packageで2.x系でもマルチサイズ対応

http://www.atmarkit.co.jp/fsmart/articles/android25/01.html

  • アプリの根幹にかかわる機能をCompatibility packageで提供すると、「せっかく互換性を持たせたのに、実際にはエラーが発生しなくなっただけで結局使えない」という事態を招く恐れが



 

2013-01-07

今から3分で,Smartyの使い方を覚えよう (PHPテンプレートエンジンの入門)

|


PHPのテンプレートエンジンであるSmartyの使い方を,

Windowsマシン上で,今から3分で覚える。



まずは前提知識から。


位置付けを解説:「テンプレートエンジン」「Smarty」とは何なのか

ある程度の規模のWebサイトやWebアプリを構築する際には,優れたフレームワークの選定が重要だ。


他の言語と同じく,PHPにもMVCフレームワークが存在し,

主要なものは「4大フレームワーク」などと呼ばれる。

PHPフレームワークの種類(2011年の情報)

http://winroad.info/codeigniter/2011/08/05/php%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF%E3%81%AE%E7%A8%AE%E9%A1%9E/


そういった各種フレームワークの中でも,

  • MVCの中から「M」に相当するDBがなくなって,
  • 「VをCでうまく運用する」

というフレームワークが「テンプレートエンジン」だと言える。



DBのレコードを順番に表示するのではなく,

設定ファイルの書き換えや,画面の断片の継ぎはぎだけで運用されている

というサイトも多数存在する。

※主な作業者は,デザイナやマークアップエンジニア達である。


そうすると,アーキテクチャにはモデル層がほぼ不要になる。(もちろん付与することもできる)



現時点までで,テンプレートエンジンの中で最もスタンダードなのがSmarty。

Webでテンプレートエンジンと言えば大抵,Smartyのことね,と通じる状況だ。


最新は3系だが,既に枯れた2.6系は広く普及している。

この扱い方を習得しておけば,レガシー小規模Webサイトのメンテに役立てる事ができる。



ここから3分で使ってみる。


作業を開始


ApacheとPHPのインストールは済んでいるものとする。

Windows Vista上に XAMPP と XOOPS Cube をインストールし,サイトをカスタマイズする手順

http://d.hatena.ne.jp/language_and_engineering/20110730/p1

  • (1)Vista上でインストール環境を整える。
  • (2)XAMPPをインストールする。


(1)Smartyの利用環境を作る

Apacheの公開ディレクトリ xampp\htdocs\ の中に,「smarty_test」というフォルダを作成。

その中に,「templates」「templates_c」という空フォルダを作成。




smartyのダウンロードページへ遷移。

http://www.smarty.net/download



「Download」の項目には3系のファイルが並んでいるが,一番下の

「Smarty 2 Releases:Smarty 2.6.26  June 18th, 2009」という所から,

zipファイルをダウンロードする。保存先はデスクトップ。



ダウンロードは一瞬で終わる。なんせ,200kbもないから。


すぐに解凍。

Smarty-2.6.26というフォルダが生成される。

その中の「libs」というフォルダを,さっき作った「smarty_test」フォルダの中に設置(丸ごとコピペ)。



これで,Smartyの利用環境が完成。



(2)Smartyで動くWebサイトを作る

ここからは,Smartyを使ってWebページを作る。


「smarty_test」フォルダの直下に,sample.phpというファイルを作成。


<?php

require( dirname( __FILE__ ).'/libs/Smarty.class.php' );

$smarty = new Smarty();

$smarty->template_dir = dirname( __FILE__ ).'/templates';
$smarty->compile_dir  = dirname( __FILE__ ).'/templates_c';

$smarty->assign('name', 'ワールド');
$smarty->display('sample.tpl');

?>



また,templatesフォルダの中に,sample.tplというファイルを作成。


<html>
<head>
<title>Smartyのテスト</title>
</head>
<body>
Hello, {$name}!
</body>
</html>


これで完成。


Webページの動作確認をする。

ブラウザで,下記のURLにアクセス。


「Hello, ワールド! 」と表示されていれば,テンプレートのWebページ化に成功している。



最後に,templates_cフォルダの中をチラッとのぞいてみる。


変なファイルが自動生成されているのが見えるだろう。

これは,自作のテンプレートをSmartyがコンパイルした結果生まれたファイルだ。



以上,3分もかからなかったはず。



動作原理の説明

コードの中身の説明に入る。

簡単にいえば,下記のような流れで動いている。

  • ブラウザからリクエストされたPHPファイルの中で,Smartyライブラリを呼び出す。
  • Smartyに,テンプレートの場所と,コンパイル結果の保存場所を教える。
  • Smartyに,変数と値の組み合わせをセットする。
  • Smartyに,特定のテンプレートを使って画面を描画するように指示する。
  • Smartyは,指示されたテンプレートをコンパイルし,そのテンプレートの中に変数の値を埋め込んで,画面を表示する。

「変数操作」と「画面描画」のコードが分離しているのがわかる。

  • 変数の値を操作・構築するのは,ブラウザからリクエストされたPHPファイル。(=コントローラ)
  • 画面のレイアウトのひな型を記述するのは,テンプレートファイル。(=ビュー)

MVCパターンで言うと,C層とV層が分離している。



Webサイト全体を構築する際には,

  • リクエストされる各URLごとに,上記のような描画ロジックとテンプレート(CとVに相当するファイル)を個別に作成。
  • Smartyの設定事項のコードは共通のPHPファイルに切り出して,全URLでインクルードする。

という形態になる。



補足:今後の学習に役立つリンク集

Smartyの入門記事:

PHP-Smartyの使い方

http://park.geocities.jp/gdfsm000/php/php13.html

  • 2008年の情報,バージョンは2.6.20
  • 1ページだけで短い

Smarty入門者のための逆引きSmartyリファレンス

http://d.hatena.ne.jp/sotarok/20080327/1206548198

  • 2008年の情報,バージョンは2系
  • よく使うもの・最悪これだけ知ってれば大体かけるよ、というものをピックアップして1ページにまとめたもの

PHPプロ yossy先生のSmarty講座

http://www.phppro.jp/school/smarty/

  • 2007〜2009年の情報,バージョンは2.6.18
  • 解説が詳しい

バージョン2系と3系の違い:

Smarty2.0からSmarty3.0への変更点(2010年の情報)

http://suin.asia/2010/03/06/smarty_3.0

  • PHP5のみになった。その他シンタックスの変更

【メモ】Smarty2 → 3のハマリどころ

http://nplll.com/archives/2010/04/smarty2_3.php

  • デリミタの前後で改行とかスペースがあると無効になった

Smartyのさまざまな用途:

XOOPS USERS GROUP / Smarty小ネタ

http://www.xugj.org/modules/xpwiki/?Smarty%E5%B0%8F%E3%83%8D%E3%82%BF

  • XOOPS向けに,Smarty を使う上で知っておくと便利な小ネタ集
  • 2011年9月の情報

突貫工事で携帯向けとスマホ向けサイトを作る

http://blog.ichino.be/web-engineer/278/

  • サイトをガラケーにも対応させる場合,Smartyで表示する直前に文字コードをUTF-8からSJISに変更
  • 2011年8月の情報


 

2012-11-16

Androidアプリ開発作業時に,頻繁に参照するサイトのリンク集

|


Androidアプリ開発時に,よく参照するページがある。

このエントリ内で,そのようなリンクを集約する。


作業工程別:

(1)企画:

  • アイデア,情報収集

(2)システム要件定義:

  • プラットフォーム要件
  • 画面サイズと解像度

(3)設計と実装:

  • 開発環境
  • 開発作業の概要
  • 画面デザイン,UIの実装

(4)正念場:



(1)企画

アイデア,情報収集

目的:

  • 現在のアプリのトレンドを把握し,インスピレーションを得るために必要。

ニュース等

Touch Lab

http://ipodtouchlab.com/


Android Developer's Blog

http://android-developers.blogspot.jp/


Andronavi

http://andronavi.com/tool


対抗陣営であるiPhone/iOSワールドで,UI面で参考になるサイト

App Mode

http://app-mode.com/


App Bank

http://www.appbank.net/category/iphone/iphone-utility



(2)システム要件定義

プラットフォーム要件:OSのバージョンとAPIレベル,および普及状況のシェアについて

目的:

  • アプリがターゲットとする端末のバージョンを決定するために必要。
    • 利用可能な機能が絞られる。
    • 作業の進め方や,設計・コーディングの方法にも影響する。

SDKバージョン番号の一覧表:

Codename Platform Version
(OSのバージョン)
API Level
Cupcake 1.5 3
Donut 1.6 4
Eclair 2.1 7
Froyo 2.2 8
Gingerbread 2.3 - 2.3.2 9
2.3.3 - 2.3.7 10
Honeycomb 3.1 12
3.2 13
Ice Cream
Sandwich
4.0.3 - 4.0.4 15
Jelly Bean 4.1 16
Jelly Bean 4.2 17

APIレベルとは?

https://sites.google.com/a/techdoctranslator.com/jp/android/appendix/api-levels#intro


Platform Versions / Current Distribution + Historical Distribution

http://developer.android.com/intl/ja/about/dashboards/index.html

  • バージョン別のシェア,Googleによる最新情報。グラフで図示


画面サイズと解像度

目的:

  • 画面設計に着手するために必要。また,動作テスト用に基準となる画面サイズを決定しておくことが必要。

一覧表:

解像度、画面サイズ(インチ)、画面縦横(cm)、画面の細かさ(dpi)の対照表

http://d.hatena.ne.jp/gkkj/20080315/1205584699

  • XGA・WXGAなど

Android端末各種 AVD設定表

http://genmaicha460.blog27.fc2.com/blog-entry-70.html

  • キャリア別の各種端末の画面サイズタイプの一覧表。エミュレータ設定用


(3)設計と実装

開発環境:IDEとSDK,およびアプリの動作環境の導入

目的:

  • 開発作業を開始するために必要。

構築手順

今から1時間で,64ビットWindows 7上にAndroid開発環境を構築し,サンプルを動作させる手順 (※4.x系のSDKを使用)

http://d.hatena.ne.jp/language_and_engineering/20121017/AdnroidDevelopmentEnvOnWindows7

  • OS : Windows 7, 64bit
  • Eclipse 3.7, Pleiades

今から1時間で,Androidアプリの開発環境を構築し,Windows上でサンプルを動作させる手順

http://d.hatena.ne.jp/language_and_engineering/20110724/p1

  • OS : Windows Vista, 32bit
  • Eclipse 3.5, Pleiades

J:COMでネット利用時に,無線LANルータを導入し,PCとiPadからWi-Fi接続する方法 (ケーブルモデムにつながらない時のための,設定手順のメモ)

http://d.hatena.ne.jp/language_and_engineering/20111015/p1

  • Wifi無線LANルータの設定方法

実機接続用 USBドライバ

SHARP


Samsung



開発作業の概要

目的:

  • 設計〜実装作業で必要な,初歩的な知識を網羅するために必要。

全般的なキーワードの説明

WebアプリとAndroidアプリのアナロジー (「Androidのアレは,Webで例えるなら○○だ」)

http://d.hatena.ne.jp/language_and_engineering/20110813/p1


プログラミング用のリファレンス

Android SDKプログラミング APIリファレンスのリンク集

http://d.hatena.ne.jp/language_and_engineering/20130302/AndroidSDKProgrammingAPILinks


Android開発ガイド:ソフトウェア技術ドキュメントを勝手に翻訳

https://sites.google.com/a/techdoctranslator.com/jp/android


公式のAPIドキュメンテーション(英語)

http://developer.android.com/intl/ja/reference/packages.html


フレームワークライブラリ

android-mvc-framework

http://code.google.com/p/android-mvc-framework/


Androidでライブラリ・プロジェクトを作成し,Eclipse上でコードを共有しよう

http://d.hatena.ne.jp/language_and_engineering/20130116/AndroidLibraryProjectOnEclipse



画面デザイン,UIの実装

目的:

  • モバイルアプリは「UI命」な面があるので。

レイアウト:

Android UIデザイン 公式ガイドライン

http://developer.android.com/intl/ja/design/index.html


Androidアプリのモック(紙芝居)の作成時に,必要なソースコード:コピペ用の一覧表

http://d.hatena.ne.jp/language_and_engineering/20110903/p1


AndroidのUIで,レイアウトXMLの記述を簡素にするための,7つの基礎知識

http://d.hatena.ne.jp/language_and_engineering/20121114/SimpleAndroidLayoutXmlTips



組み込みの画像素材(本番環境では直接使用しないこと。):

Android 2.1 android.R.drawable Icon Resources

http://docs.since2006.com/android/2.1-drawables.php


Android R Drawables / Taken from Android 2.2 (android.jar: /drawable /drawable-mdpi)

http://androiddrawableexplorer.appspot.com/


【android システムアイコン利用】Resource is not public.が出たときの対処法

http://www.imamura.biz/blog/google/android/747

レイアウトXML中での参照方法:


android:src="@android:drawable/ic_menu_help"


android:src="@*android:drawable/ic_menu_home"


色:

色相環 / CMYKカラーシステム

http://iro-color.com/colorchart/munsell-color-system.html


色相環

http://rock77.fc2web.com/main/color/color1-2.html



(4)正念場

デバッグ

目的:

  • 情報源はほとんど英語だったり,一応先端技術なので。

困ったときの質問サイト

StackOverFlow / Android

http://stackoverflow.com/questions/tagged/android


人力検索はてな / コンピュータ

http://q.hatena.ne.jp/c/computer


テスト


今から5分で,開発中のAndroidアプリを単体テストしよう (JUnitで自動テストする方法)

http://d.hatena.ne.jp/language_and_engineering/20130121/UnitTestOfAndroidAppOnEclipse


セキュリティ

アンドロイドスマートフォン プライバシーガイドライン by タオ

http://www.taosoftware.co.jp/android/android_privacy_policy/


Android WebViewのセキュリティリスクと攻撃の実際

http://dl.dropbox.com/u/439702/slide/android-webview.html#0


リリース

目的:

  • 一番ミスりやすく,問題を起こしやすい段階なので。

公開作業の手順書

Androidアプリをマーケットに公開する方法の作業手順メモ (リリース時とアップグレード時のチェックリスト)

http://d.hatena.ne.jp/language_and_engineering/20120310/AndroidMarketReleaseSteps



その他

Android関連で執筆した情報

http://d.hatena.ne.jp/language_and_engineering/archive?word=*[Android]




 

2012-11-14

AndroidのUIで,レイアウトXMLの記述を簡素にするための,7つの基礎知識

|


AndroidアプリのUIは,レイアウトXMLに記述する。

が,これが結構面倒くさい。


各ビューごとに大量の属性をコーディングすることになり,

あとから見返すと,何の目的で属性を付与したのか不明だったりする。

また記述量が多いと,その分だけ変更時の修正作業量も増えてしまう。


どうすれば,AndroidのレイアウトXMLの記述をシンプルに短くできるか?

下記では,基本的なTipsを7つ列挙する。

  • (1)IDE上で自動整形して見やすくする
  • (2)複数のプロパティを,スタイルXML中でまとめて一括指定する
  • (3)共通部分をincludeする
  • (4)独自の性質を持ったViewを作成する
  • (5)アプリ全体のスタイルをテーマに集約する
  • (6)XMLに書かず,コードで動的に操作する
  • (7)HTML5を使う

(1)IDE上で自動整形して見やすくする

まず,本質的でない外見の部分によって,コードの可読性が損なわれている場合がある。

それを解消する。


特に,ビューに付与された複数の属性が,縦方向にインデントがそろっているかどうか?

という点が見やすさを左右する。


この自動フォーマット操作は,Eclipse上のショートカットキーで実行できる。

Androidアプリ開発向け Eclipse XML Formatter

http://andbrowser.com/development/knowhow/413/android-eclipse-xml-formatter/

  • [Window]→[Preference]でPreferenceを開いて、[XML]→[XML Files]→[Editor]

Split multiple attriutes each on a new line

複数の属性をそれぞれ新規行に分割

Align final bracket in multi-line element tags

複数行要素タグに最終括弧を位置合わせ

キーボードショートカットの Shift+Ctrl+f で実行可能。


これで,ゴチャッとしたXMLの内容が,まずは表面的な見かけだけでも綺麗になる。



(2)複数のプロパティを,スタイルXML中でまとめて一括指定する

見かけの整理整頓が終わったら,次は記述量の削減である。


各Viewに対して,

  • 「このViewは,どのような目的で存在するViewなのか」

という,Viewの機能的な存在目的があるはずだ。


そして,その「Viewの機能的役割」さえ分かれば,各種のアトリビュートは後付けである。

ちょうどHTMLの各種タグが文書中で果たすセマンティクスと,CSSによる具体的なデザインとがうまく分離されているように。


同じように,AndroidのレイアウトXML上でも「スタイル」を作る。

CSSのclassのようなもの。

特定のスタイル名を持つViewは,そのスタイルで定義された属性セットを付与される。



下記はサンプルコード。


valuesフォルダ内にstyles.xmlを作成:


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

    
    <style name="HogeStyle">

        <item name="android:layout_height">fill_parent</item>
        <item name="android:layout_width">0dp</item>

        <item name="android:layout_weight">0.3</item>
        <item name="android:layout_margin">10dp</item>

        <item name="android:src">@drawable/icon</item>

    </style>

    

</resources>


レイアウトXML側で参照:


	<ImageView
		android:id="@+id/img1"
		android:layout_marginLeft="125px"
		
		style="@style/HogeStyle"
	/>

	<ImageView
		android:id="@+id/img2"
		android:layout_marginLeft="100px"
		
		style="@style/HogeStyle"
	/>


こうすれば,

  • レイアウトXML中では,各Viewごとに属性値の異なる部分だけを記述すればよく,コーディングの量が減る
  • レイアウトXML中で,各Viewの持つ役割を把握しやすくなる。(Hogeという目的・役目を持つViewなのだ,とわかる)
  • スタイルXML中での変更が,スタイルを適用している全Viewに反映されるので,デザインをすばやく変更でき,保守性が高くなる。

スタイルは,継承できる。

ベースになるスタイルに,バリエーションを持たせた子スタイルをたくさん作れる。

styleタグにparent属性を指定すればよい。

ThemeとStyle

http://renapp.kakoku.net/menu2_android/android_style.html


※なお,id属性はスタイルに含めてはいけない。

レイアウトXML上に直にidを書かないと,Rクラスの中にidを含めてもらえなくなるので,Java側からViewを参照できなくなってしまう。


これで,レイアウトXML内での各Viewに関する記述量が大幅に削減される。



(3)共通部分をincludeする

各Viewに関する記述量が減ったら,次は,Viewそのものに関する記述を簡略化したい。

つまり,個別にViewを指定するのではなく,複数のViewをまとめてグループとして呼び出せるようにする。


このためには,再利用可能なレイアウト部品を作り,それをincludeする。


そうすれば,いちいちView部品を記述せず,

「この機能を持ったレイアウト部品」というふうに部分的なUIを呼び出して組み合わせられるので,

UIの実装効率も,XMLの可読性も,デザイン全体の修正効率も向上する。


サンプルコード:

メインのレイアウトXML側で


        <include layout="@layout/_fuga" />


_fuga.xml


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

    <RelativeLayout style="@style/hoge" >

        <ImageView
            android:id="@+id/iv1"
            style="@style/HogeImage" />

        <ImageView
            android:id="@+id/iv2"
            style="@style/HogeImage" />

    </RelativeLayout>

</merge>

こうすれば,レイアウト部品がメインのXML中に埋め込まれて置換されたような状態になる。


なお,レイアウト部品側のルートViewをmergeタグにしている理由は,

includeする側のXMLから見て透過的に部品を埋め込むため。


もし普通のLinearLayoutなんかをルートにした場合,

Javaコード側から部品レイアウト内へのアクセスがちょっと面倒になるので,それは避ける。

Android開発のちょっとしたお話

http://alpha.mixi.co.jp/2011/10805/

  • XMLで画面を作っていると、あるパーツは他の画面でも使いたいので、その部分だけ切り出したい
  • そのような場合には、切り出したい部分だけを記述したXMLを用意し、使いたい場所でインクルードする

AndroidのレイアウトXMLで別のレイアウトを再利用してみる

http://devslog.com/article/20111207235726.html

  • IncludeしたViewを操作したい時:findViewByIdでレイアウトを取得し、そのレイアウトからからビューを取得

Androidトレーニング <include/>でレイアウトを再利用する

http://firespeed.org/diary.php?diary=kenz-1511

  • <include/>タグを使用して)他のレイアウトにこのレイアウトを含めるとき、システムはmergeエレメントを無視し、2つのボタンを統合して直接<include/>タグの代わりに置きます。

これで,個別のViewに対しても,また複数のViewのまとまりに対しても,

共通部分をまとめる事で記述をシンプルにできるようになった。



(4)独自の性質を持ったViewを作成する

ある場合,たくさんのビューを組み合わせても実現が困難,

というUI部品を作りたいときもある。


そういう複雑なViewを実現したい場合は,既存のViewを拡張して,

独自のビュークラスを作る。


独自ビューを作れば,動的な性質を持つ複雑なViewを,共通化してシンプルに記述することができる。

  • 独自Viewのコンストラクタ内で各種プロパティをsetすれば,XML内にViewの属性をセットしなくて済む。
  • 各種イベントリスナも独自クラス内で共通化して定義できるので,個別のViewインスタンスごとにイベントリスナを設定する手間が無くなる。

ViewとかButtonとかをextendしたクラスを作成。

独特のコンストラクタを作る必要があるので注意。


HogeView.java


public class HogeView extends View
{
    public HogeView(Context context, AttributeSet attr){
        super(context, attr);
    }
}

もし,このメソッドがないとエラーになったりする。親クラスのコンストラクタの仕様に気を配ること。


ERROR/AndroidRuntime(8164): Caused by: java.lang.NoSuchMethodException: HogeView(Context,AttributeSet)
ERROR/AndroidRuntime(8164):     at java.lang.Class.getMatchingConstructor(Class.java:643)
ERROR/AndroidRuntime(8164):     at java.lang.Class.getConstructor(Class.java:472)
ERROR/AndroidRuntime(8164):     at android.view.LayoutInflater.createView(LayoutInflater.java:480)
ERROR/AndroidRuntime(8164):     ... 21 more

カスタムビューの実装

http://d.hatena.ne.jp/Korsakov/20100803/1280827017

  • 継承したクラスでは、親クラスのコンストラクタをすべて記述しておかないと、何故かエラーが発生するので注意

レイアウトXML側では,パッケージ名を含めて指定。


	<com.example.HogeView
		android:id="@+id/hoge1"
		android:layout_width="300px"
		android:layout_height="300px"
	/>

これで表示できる。

Hoge.javaの中に,Clickイベントに関する処理内容や,キャンバスの描画処理などを含めておけば,

このビューが現れる箇所では共通して同じUIの挙動を実現できる。



具体例としては,下記のエントリで,

Bitmapの表示内容をピクセルごとに制御するような独自Viewを動作させているので,そちらを参照のこと。

Androidで,「ビットマップのピクセル操作」をリアルタイムに実行するサンプルコード

http://d.hatena.ne.jp/language_and_engineering/20120626/AndroidManipulateBitmapPixels



(5)アプリ全体のスタイルをテーマに集約する

テーマを使うと,アプリ内の全体を対象としてデザインのカスタマイズが可能。

  • 基本的な文字色等を統一する
  • プログレスバーやテキスト選択アイコンなど,レイアウトXML中に明示的に現れないUI部分のデザインをカスタマイズする
  • アプリ内の全EditTextのスタイルを一括して変更する

など。

エディットテキストのデザインを変える

http://books.google.co.jp/books?id=jhSYh19EY_AC&pg=PA74&lpg=PA74&source=bl&ots=dha09yX_Z_&sig=Hctv2GQ-IN51De7AwdMlRAoy9_M&hl=ja&sa=X&ei=gGWjUKC2Nu6ZmQXhhIGwDg&ved=0CD8Q6AEwAw#v=onepage&q&f=false

  • style中でThemeを継承し,android:editTextStyleを定義

テーマを使ってレイアウトを定義する

http://techbooster.jpn.org/andriod/ui/792/


Androidアプリ全体で文字色や背景色を統一させる方法(Theme/テーマ)

http://android.roof-balcony.com/resource/theme/


システムテーマで統一感を出す

http://techbooster.jpn.org/andriod/ui/10253/

ただし,この方法は情報源が少ない。

基本となる組み込み済みのテーマを指定して,ちょっと加工したら,

あとはstyleで個別対応ということになるかもしれない。



(6)XMLに書かず,コードで動的に操作する

Viewのインスタンスを生成してsetContentView()してしまえば,XMLなしでビューを動的に生成できる。

この場合,XMLの記述量はゼロになる。


その分だけ,Javaコード側のコーディングは増えるのだが,

それを楽にコーディングできるようにしたのが,Android-MVCフレームワークのV層である。

Androidアプリの画面レイアウトを,まるでjQueryのようなコードで動的構築できるライブラリ (の試作品。UIコーディングのためのDSL

http://d.hatena.ne.jp/language_and_engineering/20120210/p1

上記のエントリの,詳細設計の要点は下記の通り。

  • TOPのレイアウトから,内部に含まれるレイアウトまで,再帰的にinflate処理を行なっている。
  • レイアウトにビューを追加する際には,parentLayout.addView(v, new LinearLayout.LayoutParams(intWidth, intHeight)); のように,高さと幅の情報を込みで渡す必要がある。
  • JavaコードがUIの構造をわかりやすく表現している。

これなら,確かにレイアウトXMLの記述量は減る。


(7)HTML5を使う

最後に,究極の手段。

  • レイアウトXMLには,WebViewのみを設置する。
  • WebView内にはHTML(5)を表示する。
  • addJavascriptInterfaceで,HTML側とJava側のロジックを結びつける。

たった2ファイルで,HTML+JS製のネイティブAndroidアプリを作る手順 (動作するサンプルコード付き。WebViewの活用方法)

http://d.hatena.ne.jp/language_and_engineering/20120710/CreateAndroidAppByHtml5JavaScript


AndroidやiOSの「ハイブリッドアプリ」で,JavaScriptとネイティブ・コードが連携する仕組みを図解 (おまけ:HTML側で施すべき,クロスプラットフォーム対策)

http://d.hatena.ne.jp/language_and_engineering/20120713/p1


jQuery Mobile と HTML5 で、Androidのネイティブアプリを作成する手順

http://d.hatena.ne.jp/language_and_engineering/20120717/CreateAndroidNativeAppsByJQueryMobile

これで,レイアウトXMLには実質的に何も記述せず,別の使い慣れたHTMLファイル上にUIを記述してゆける。



以上,7つの手段を使って,レイアウトXMLに記述するコードの量を極力減らし,シンプルなUI実装を実現できる。

style・includeと独自Viewぐらいは,自由に使いこなせるようになっておきたい。



その他


不要なレイアウト部品を検出して警告するツール:

Android Tips #5 layoutoptでレイアウトを最適化

http://dev.classmethod.jp/smartphone/android-tips-5-layoutopt/


補足

本エントリでは,後方互換性を考慮して,fragmentに関する記述は省略した。

ここで述べた7つの方法は,SDKバージョンを問わずに使える,基本的なテクニックばかり。



関連する記事:

Androidアプリで,レイアウト用XMLの名前をいちいち指定せずに,自動的に画面を描画させよう (Rails風のCoCなレンダリング) - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20110910/p1


Androidアプリで,_("リソース名") と書くだけで,簡単に文字列を参照しよう - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20110815/p1


Androidで,複数のAnimationを「順番に」実行するためのライブラリ (XMLを使わずに「連続した動きの変化」を指定し,逐次実行するDSL) - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20120416/AndroidAnimationSetSequentialDSL

 

2012-11-07

Androidで,音声入力と音声合成をシンプルに記述するためのライブラリ

|


Androidアプリ開発時に,スピーチ周りのコードをシンプルに記述したい。

  • 音声入力・音声認識(ASR
  • 音声合成・Text To Speech(TTS

これらの両者について,便利なラッパークラスとDSLを作り,コード量を削減してみる。


サンプルコード

下記のような記述ができる。

それぞれ,処理が完了したときのタイミングでイベントを発行できる。


音声入力:


final Activity context = this;

// 音声入力
new ASRUtil(context)
    .lang(Locale.US)
    .events(new ASRUtil.ASREventsListener(){

        @Override
        public void beforeSpeech() {
            Toast.makeText(context, "お話し下さい。", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void handleResults(List<String> results)
        {
            // 認識結果を表示
            String s = "";
            for( String word : results )
            {
                s += word + "\n";
            }
            Toast.makeText(context, s, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onNoInputError() {
            Toast.makeText(context, "入力されませんでした。", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onNoResultsMatch() {
            Toast.makeText(context, "入力内容を理解できませんでした。", Toast.LENGTH_SHORT).show();
        }

    })
    .listen()
;


音声合成:


// 音声合成
new TTSUtil(context)
    .lang(Locale.US)
    .pitch(1.2f)
    .speed(1.0f)
    .forceSoundIfSilent() // マナーモードでも強制的に音を出す場合
    .words( "Hello, World !" )
    .onSpeakCompleted(new TTSUtil.AfterSpeakHandler(){
        @Override
        public void exec(){
            Toast.makeText(context, "発話終了", Toast.LENGTH_SHORT).show();
        }
    })
    .speak()
;


端末内で,特定の言語の音声合成を利用可能かどうか判定:


final Context context = this;

TTSUtil.judgeLocaleAvailable(context, Locale.US, new TTSUtil.JudgeListener(){
    @Override
    protected void onJudgeLocale( boolean usable )
    {
        if( usable )
        {
            Toast.makeText(
                context, 
                "このロケールの音声合成は利用可能です。", 
                Toast.LENGTH_SHORT
            ).show();
        }
        else
        {
            Toast.makeText(
                context, 
                "このロケールの音声合成は利用不可能です。\n音声データをインストールしてください。", 
                Toast.LENGTH_SHORT
            ).show();
        }
    }
});


それぞれの機能を個別に,便利クラスの中に閉じ込めた感じ。


このサンプルが動作するためのライブラリ


ASRUtil


package com.example.speechtest;

import java.util.List;
import java.util.Locale;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.util.Log;

/**
 * Speech Recognitionに関する便利クラス。
 * @author id:language_and_engineering
 *
 */
public class ASRUtil {

    private Activity activity;
    private Locale locale;
    private ASREventsListener asrEventsListener;


    public ASRUtil(Activity activity) {
        this.activity = activity;
    }


    /**
     * ロケール・言語を設定
     */
    public ASRUtil lang(Locale locale) {
        this.locale = locale;
            // ※ロケールの一覧表
            //   http://docs.oracle.com/javase/jp/1.5.0/api/java/util/Locale.html
        return this;
    }


    /**
     * 各種イベントのリスナを設定
     */
    public ASRUtil events(ASREventsListener asrEventsListener) {
        this.asrEventsListener = asrEventsListener;
        return this;
    }


    /**
     * 音声認識の各種イベントのリスナ。
     * 必要なメソッドをOverrideすること
     * @author id:language_and_engineering
     *
     */
    public static abstract class ASREventsListener implements RecognitionListener {

        @Override
        public void onReadyForSpeech(Bundle params) {
            this.beforeSpeech();
        }


        @Override
        public void onResults(Bundle results) {
            // 結果を受け取る
            List<String> candidates = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);

            // 処理する
            handleResults(candidates);
        }


        @Override
        public void onError(int errorCode){
            // エラーコードの一覧表
            // http://developer.android.com/intl/ja/reference/android/speech/SpeechRecognizer.html#ERROR_AUDIO

            // NOTE: 主要なエラーのシナリオは,実装を強制させる。
            // その他のエラーは,必要に応じて実装させる。
            switch (errorCode)
            {
            case SpeechRecognizer.ERROR_SPEECH_TIMEOUT: //6
                this.onNoInputError();
                break;

            case SpeechRecognizer.ERROR_NO_MATCH: //7
                this.onNoResultsMatch();
                break;

            default:
                onOtherErrors(errorCode);
            }

        };


        // ------- 必ず実装すべきもの -------


        /**
         * 音声認識の準備が完了した時
         */
        public abstract void beforeSpeech();


        /**
         * 認識結果の文字列リストを処理
         */
        public abstract void handleResults(List<String> results);


        /**
         * 音声入力されなかったエラー
         */
        public abstract void onNoInputError();


        /**
         * 認識結果に候補がなかったエラー
         */
        public abstract void onNoResultsMatch();


        // ------- 必要ならOverride -------


        /**
         * その他のエラー
         */
        public void onOtherErrors(int errorCode){
            switch(errorCode)
            {
            case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS: //9
                // RECORD_AUDIOなどパーミッション不足
                break;
            case SpeechRecognizer.ERROR_NETWORK_TIMEOUT: //1
                // ネットワークタイムアウト
                break;
            case SpeechRecognizer.ERROR_NETWORK: //2
                // その他ネットワーク関係
                break;
            case SpeechRecognizer.ERROR_AUDIO: //3
                // 録音データの保存に失敗
                break;
            case SpeechRecognizer.ERROR_SERVER: //4
                // サーバ側エラー
                break;
            case SpeechRecognizer.ERROR_CLIENT: //5
                // クライアント側のその他のエラー
                break;
            case SpeechRecognizer.ERROR_RECOGNIZER_BUSY: //8
                // サービスがビジー
                break;
            }

            Log.e("ASRUtil", "エラーコード:" + errorCode);
        }

        @Override
        public void onBeginningOfSpeech() {
            // 音声入力が開始
        }

        @Override
        public void onPartialResults(Bundle partialResults) {
            // 部分的な認識結果を処理
        }

        @Override
        public void onEndOfSpeech() {
            // 音声入力が終了
        }


        // ------- 無視してよいメソッド -------


        @Override
        public void onBufferReceived(byte[] buffer) {
            // 入力内容に対するユーザへのフィードバック用だが,
            // このメソッドが呼ばれる保証はないし,呼ばれても処理内容は端末依存
        }

        @Override
        public void onEvent(int eventType, Bundle params) {
            // 将来の拡張のための予備メソッド
        }

        @Override
        public void onRmsChanged(float rmsdB) {
            // 音量が変わった場合に呼ばれることになっているが
            // このメソッドが本当に呼ばれるという保証はない。
        }

    }


    /**
     * 認識を開始
     */
    public void listen() {
        // 音声認識APIに自作リスナをセット
        SpeechRecognizer sr = SpeechRecognizer.createSpeechRecognizer(this.activity);
        sr.setRecognitionListener(this.asrEventsListener);

        // インテントを作成
        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, this.activity.getPackageName());

        // 入力言語のロケールを設定
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, this.locale.toString());

        // 音声認識APIにインテントを処理させる
        sr.startListening(intent);
    }
}

TTSUtil


package com.example.speechtest;

import java.util.HashMap;
import java.util.Locale;

import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;

/**
 * Text To Speechに関する便利クラス。
 * @author id:language_and_engineering
 *
 */
public class TTSUtil {

    //@see http://d.hatena.ne.jp/language_and_engineering/20121022/p1
    // http://www.techdoctranslator.com/resources/articles/articles-index/tts

    // TODO: Androidが3.0以上なら,読み上げの音量も制御できる。
    // http://techbooster.jpn.org/andriod/application/4017/


    private static TextToSpeech ttsForStatic;

    private Activity activity;
    private Locale locale;
    private float pitch = 1.0f;
    private float speed = 1.0f;
    private boolean forceSoundIfSilentFlag = false;
    private String words;
    private AfterSpeakHandler afterSpeakHandler;
    private TextToSpeech tts;


    // ---- クラメソ ----


    /**
     * 特定のロケールが利用可能か,非同期で判定する
     */
    public static void judgeLocaleAvailable(Context context, final Locale target_locale,
            final JudgeListener judgeListener) {

        // onInitだけのために初期化
        ttsForStatic = new TextToSpeech(context, new TextToSpeech.OnInitListener(){
            @Override
            public void onInit(int status) {

                if(
                    (status == TextToSpeech.SUCCESS)
                     &&
                    (ttsForStatic.isLanguageAvailable(target_locale) >= TextToSpeech.LANG_AVAILABLE)
                )
                {
                    judgeListener.onJudgeLocale(true);
                }
                else
                {
                    judgeListener.onJudgeLocale(false);
                }

            }
        });

    }


    /**
     * ロケールの利用可能判定のコールバック処理をするクラス
     * @author id:language_and_engineering
     *
     */
    public abstract static class JudgeListener {
        abstract protected void onJudgeLocale( boolean usable );
    }


    // ---- インメソ ----


    /**
     * 初期化
     */
    public TTSUtil(Activity activity) {
        this.activity = activity;
    }


    /**
     * ロケール・言語を設定
     */
    public TTSUtil lang(Locale locale) {
        this.locale = locale;
            // ※ロケールの一覧表
            //   http://docs.oracle.com/javase/jp/1.5.0/api/java/util/Locale.html
        return this;
    }


    /**
     * 音の高低を設定
     */
    public TTSUtil pitch(float pitch) {
        this.pitch = pitch;
        return this;
    }


    /**
     * 話す速さを設定
     */
    public TTSUtil speed(float speed) {
        this.speed = speed;
        return this;
    }


    /**
     * マナーモードでも強制的に音を出す
     */
    public TTSUtil forceSoundIfSilent() {
        this.forceSoundIfSilentFlag = true;
        return this;
    }


    /**
     * 話す文章を設定
     */
    public TTSUtil words(String s) {
        this.words = s;
        return this;
    }


    /**
     * 発音終了後の挙動を設定
     */
    public TTSUtil onSpeakCompleted(AfterSpeakHandler afterSpeakHandler) {
        this.afterSpeakHandler = afterSpeakHandler;
        return this;
    }


    /**
     * 発音終了後の挙動を記述するクラス
     */
    public abstract static class AfterSpeakHandler {
        public abstract void exec();
    }


    /**
     * 発音を実行
     */
    public void speak() {
        final TTSUtil ttsUtil = this;

        tts = new TextToSpeech(this.activity, new OnInitListener(){

            // 端末のマナーモード設定のキャッシュ用
            private AudioManager audioManager;
            private int ringerModeCache;

            @Override
            public void onInit(int status) {

                // 音声の設定
                tts.setPitch(ttsUtil.pitch);
                tts.setSpeechRate(ttsUtil.speed);
                tts.setLanguage(ttsUtil.locale);

                // 発話終了イベントのリスナを登録
                tts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener() {
                    // http://d.hatena.ne.jp/rudi/20100810/1281447191

                    public void onUtteranceCompleted(String utteranceId) {

                        // 必要ならマナーモード設定を元に戻す
                        if( ttsUtil.forceSoundIfSilentFlag )
                        {
                            // マナーモード設定を復元
                            audioManager.setRingerMode(ringerModeCache);
                        }

                        // 発話完了イベントの処理をユーザ指定されていればUIスレッド上で実行
                        if( afterSpeakHandler != null )
                        {
                            activity.runOnUiThread(new Runnable(){
                                // http://u6kyu1.blogspot.jp/2012/09/android-ttsonutterancecompleted.html

                                @Override
                                public void run() {
                                    ttsUtil.afterSpeakHandler.exec();
                                }
                            });
                        }

                        // リソースを自動解放
                        tts.shutdown();
                    }
                });

                // 発話終了イベントを有効化する
                HashMap<String, String> params = new HashMap<String, String>();
                String utteranceId = ttsUtil.words + System.currentTimeMillis(); // この発話を指すユニークな識別子
                params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId); // イベント発行のために必要

                // マナーモードでも音を出すか
                if( ttsUtil.forceSoundIfSilentFlag )
                {
                    audioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
                    int ringerMode = audioManager.getRingerMode();
                    if(ringerMode != AudioManager.RINGER_MODE_NORMAL ) {
                        // マナーモード解除
                        audioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
                    }
                    this.ringerModeCache = ringerMode;
                }

                // 音声合成して発音
                if(tts.isSpeaking()) {
                    tts.stop();
                }
                tts.speak(ttsUtil.words, TextToSpeech.QUEUE_FLUSH, params);

            }
        });

    }

}


音声合成の結果を聞くだけのために,毎回マナーモードの設定を手動で操作する,というのはよろしくない。

音が出るときだけ,自動的にマナー解除して,あとから自動的に設定を復元する,という処理があれば便利。


もちろん,マナーモードなら音を出したくない場合もあるはずだから,そのへんはアプリの仕様によって使い分ける。


参考:

[AndroidSDK]マナーモード判定

http://ameblo.jp/yolluca/entry-10909829634.html


マナーモード状態の取得

http://d.hatena.ne.jp/hyoromo/20101002/1286051079



利用例

以前,素のAPIを使って「音声入力した内容をそのまま音声合成」というアプリを作った。

Androidで音声入力した内容を認識し,そのまま音声合成。「おうむ返し」アプリのソースコード

http://d.hatena.ne.jp/language_and_engineering/20121022/p1


あのときのソースコードを,今回のライブラリを使って書き換えてみる。


package com.example.speechtest;

import android.os.Bundle;
import android.app.Activity;

import java.util.List;
import java.util.Locale;

import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

/**
 * 音声入力(Input)と音声読み上げ(Output)のテスト。
 * マイクに入った音声を認識して,そのまま音声合成し,おうむ返しにスピーカ出力を試みる。
 * @author id:language_and_engineering
 *
 */
public class MainActivity extends Activity implements OnClickListener
{

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

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener( this );

    }


    @Override
    public void onClick(View v)
    {
        final Activity context = this;

        // 音声入力
        new ASRUtil(context)
            .lang(Locale.US)
            .events(new ASRUtil.ASREventsListener(){

                @Override
                public void beforeSpeech() {
                    Toast.makeText(context, "お話し下さい。", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void handleResults(List<String> results)
                {
                    // 結果を表示
                    String s = "";
                    for( String word : results )
                    {
                        s += word + "\n";
                    }
                    Toast.makeText(context, s, Toast.LENGTH_SHORT).show();

                    // 音声合成
                    new TTSUtil(context)
                        .lang(Locale.US)
                        .pitch(1.2f)
                        .speed(1.0f)
                        .words( results.get(0) )
                        .onSpeakCompleted(new TTSUtil.AfterSpeakHandler(){
                            @Override
                            public void exec(){
                                Toast.makeText(context, "発話終了", Toast.LENGTH_SHORT).show();
                            }
                        })
                        .speak()
                    ;
                }

                @Override
                public void onNoInputError() {
                    Toast.makeText(context, "入力されませんでした。", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onNoResultsMatch() {
                    Toast.makeText(context, "入力内容を理解できませんでした。", Toast.LENGTH_SHORT).show();
                }

            })
            .listen()
        ;
    }

}

きわめてシンプル。

エラーハンドリングもしっかりやっている。


記述すべきなのは,アプリの仕様の本質的な部分にかかわることだけ。

SDKには,始めからこういうfluentなコーディングができる仕様であってほしい。



雑感

私が書くコードって,下位レイヤ向けのプログラムだと,ほとんど下記の組み合わせなんだよね。

  • コンポジション。
  • Builderパターン。
  • fluent interface。

面倒なAPI仕様のクラスを,インスタンス化してメンバで持っておく。

そして,各種の設定事項を,直感的に分かりやすい名前のsetterメソッドでセットして,ラッパークラス内に持っておく。

すべてのsetterはthisを返すので,メソッドチェインによる利用が前提になる。

次いで,設定項目が出そろったら,ワンコールで一気に組み立てて実行する。


これにより,「できるだけ短くて,できるだけ変更しやすくて,できるだけ再利用しやすいコード」が書けるようになってゆく。



そして上位レイヤでは,下記のような雰囲気のコードを書こうとするんだよね。

  • まるで設定ファイルのようなコード。縦幅も,横幅も,拍子抜けするほど短いコード。
  • 機能仕様書を書いたら,その仕様書がそのまま動くようなコード。
  • 上から下に読み流すだけで,処理の順番のシナリオを把握できるような,人間の思考に最適化された,直列化されたコード。
  • どんな言語を使っていようと,RailsとjQueryをミックスしたようなコード。


ちなみに,上記のBuilderうんぬんというのは,静的型付け言語の場合。

JavaScriptとかRubyのような,柔軟な動的言語であれば,ハッシュ・JSONが大活躍する。

ハッシュの中に,好きなだけ情報を詰め込むことができる。

JSONが,設定ファイルの役目を果たすのである。

だから,わざわざ1項目ごとにsetterメソッドを切り分ける必要など生じない。

逆にJavaのような静的言語だと,複数の型を1箇所に混在させることがしづらいから,仕方なく1タイプごとにsetterをチマチマ作っているのである。




毎回,この作業の繰り返し。

下位レイヤと上位レイヤを,うまく連結させようと試みている。

この段階を通過すれば, いつも生産性は激増する。


下位レイヤが十分隠ぺいされているので,システムの設計すなわちプログラミングという事になってくる。

設計をプログラムで表現できるようになる,ということ。


(ドキュメント大好きでコーディングが超苦手な人がよくいるけど,そういう人々には理解しがたい作業フローだろう。)



コツコツ書き溜めたDSLが蓄積され,そのプラットフォームの上で自由に遊びまわれるようになる。

これは楽しい。



プログラミングにおいて,

  • 下位レイヤを深く掘り下げる計算機科学的な楽しみ

というのももちろん存在するのだが,

  • 上位レイヤをサクサク取り扱って,高度なものを量産する楽しみ

というのも存在するんだよね。

私は,この両方とも好きだ。






 

2012-11-02

AndroidのListViewを,いかにシンプルかつ楽にコーディングするか。Adapterを記述不要のライブラリ

|


ListViewを楽に使うためのDSLを考案する。


はじめに

AndroidUIで,ListViewの実装はめんどい。


スマホアプリで,要素がリスト形式に並ぶ,というシーンは頻繁にある。

だが特にAdapter周りを毎回コーディングするのが面倒で,どこか回りくどい。

データ構造も微妙に煩雑。

AndroidアプリでListViewをカスタマイズし,Web上の画像を行ごとに表示するサンプルコード (SimpleAdapterクラスを独自に拡張)

http://d.hatena.ne.jp/language_and_engineering/20111014/p1


この手間を,いかに省くか。


一つの方法は,そもそもListViewを使わないことだ。

別の手段で,複数の要素を「動的に」列挙する。

この方法については,Android-MVCフレームワークの初版公開時にすでに提案してある。

Androidアプリの画面レイアウトを,まるでjQueryのようなコードで動的構築できるライブラリ (の試作品。UIコーディングのためのDSL)

http://d.hatena.ne.jp/language_and_engineering/20120210/p1


forループ中で要素を動的にaddしているサンプルコード

http://code.google.com/p/android-mvc-framework/source/browse/tags/20120215_ver0.1/src/com/android_mvc/sample_project/activities/func_db/DBListActivity.java



そして,今回提案するもう一つの方法は,ListViewを手軽に扱うためのDSLを構築することだ。


f:id:language_and_engineering:20121102190958p:image


上図のような単純なUIを実現するために,

準備済みのライブラリがあれば,どれぐらい短くコーディングできるのか。


サンプルコード

Activityのコード:


package com.example;

import com.android_mvc.framework.ui.UIBuilder;
import com.android_mvc.framework.ui.list.ListViewDataStore;
import com.android_mvc.framework.ui.list.LineUIBuilder;
import com.android_mvc.framework.ui.list.MListView;
import com.android_mvc.framework.ui.view.MButton;
import com.android_mvc.framework.ui.view.MLinearLayout;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;

/**
 * ListViewのDSLのサンプル画面。
 * @author id:language_and_engineering
 *
 */
public class ListViewSampleActivity extends Activity {

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

    final Context context = this;

    new UIBuilder(this)
      .withoutScroll()
      .add(

        new MButton(this)
          .text("ボタン1")
        ,

        new MLinearLayout(this)
          .heightPx(350)
          .add(

            // ここからListView
            new MListView(this)
              .bgcolor(Color.parseColor("#ffffff"))
              .widthFillParent()
              .defineLines(

                // 行のUIを定義
                new LineUIBuilder(R.layout.menu_listline)
                {
                  @Override
                  protected void describeOneLine( final int position, View lineView )
                  {
                    // 動的に文言を表示
                    ((TextView)lineView.findViewById(R.id.tv1)).setText(
                      (position + 1)
                      + ": "
                      + getLineData(position, "word_title").toString()
                    );

                    // 行タップ時の挙動
                    lineView.setOnClickListener(new OnClickListener(){
                      @Override
                      public void onClick(View v) {
                        // 指定されたアクティビティに画面遷移
                        context.startActivity( new Intent(
                          context, (Class<?>) getLineData(position, "dest")
                        ) );
                      }
                    });

                  }
                }
                ,

                // 行内のデータを定義
                new ListViewDataStore()
                  .newLine()
                    .put("word_title", "日本語")
                    .put("dest", AActivity.class)
                  .newLine()
                    .put("word_title", "英語")
                    .put("dest", BActivity.class)
                  .newLine()
                    .put("word_title", "ンデベレ語")
                    .put("dest", CActivity.class)
                  .newLine()
                    .put("word_title", "C言語")
                    .put("dest", DActivity.class)
              )

          ) // ListView ここまで

          ,
          new MButton(this)
            .text("ボタン2")

      )
    .display();

  }

}


リストビューの1行分のレイアウトXML

menu_listline.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/linearLayout1"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:orientation="horizontal"

  android:padding="20dp"
  android:background="#ffffff"
>

  <TextView
    android:id="@+id/tv1"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"

    android:textColor="#222222"
  />


  <ImageView
    android:layout_alignParentRight="true"

    android:layout_height="30dp"
    android:layout_width="30dp"

    android:layout_marginRight="20dp"

    android:src="@android:drawable/ic_media_play"
  />

</RelativeLayout>


ソースコードが,画面の仕様をそのまま表現している。


1行分のUI定義と,各行に渡したいデータとを記述するだけ。

付随的なことは一切,コードに現れない。



※各行内のレイアウトを動的に構築するという発想もあったのだが,没とした。

ListViewの性質上,各行の描画処理(getView)が何度も繰り返し頻繁にコールされるので,

その度に見た目を1行ずつ動的に構築する方法には無理があったため。

なので,ベースとなる行のレイアウトはXMLで定義する必要がある。



サンプルコードを動作させるための,ライブラリ側のコード

以下はフレームワーク側のコードになる。

Android-MVC framework ver0.3に機能追加する形での提案となる。

わりと簡単な事しかやっていない。



上述のサンプルコード中で,

LineUIBuilderは,単なるメソッドのラッパ。

ListViewDataStoreは,ArrayListとHashMapのラッパ。


残りのクラスが本質的であり,その中でも要点となる部分の処理を掲載する。


まず,ビュー部品のラッパ。

MListView.java


package com.android_mvc.framework.ui.list;

import java.util.HashMap;
import java.util.List;

import com.android_mvc.framework.annotations.SuppressDebugLog;
import com.android_mvc.framework.ui.view.IFWView;
import com.kids_be_genius.EnglishGreetingVoice.util.Util;


import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.SimpleAdapter;

/**
 * ListViewのラッパークラス。
 * @author id:language_and_engineering
 *
 */
@SuppressDebugLog(true)
public class MListView extends ListView implements IFWView
{

    // NOTE:
    // ・ScrollViewの中では使わないこと。高さがおかしくなる。
    //   http://stackoverflow.com/questions/10709411/android-listview-rows-in-scrollview-not-fully-displayed-clipped
    // ・高さを変えるには,ラッパーViewとしてLinearLayoutを設け,そこでheightを指定すること。

    // ListViewとアダプタの基本的な使い方 @see http://d.hatena.ne.jp/language_and_engineering/20111014/p1


〜中略(属性操作など)〜


    // 行の定義

    /**
     * 行のUIとデータを定義
     */
    public MListView defineLines(LineUIBuilder line_builder, ListViewDataStore listViewDataStore) {

        // アダプタを作ってセットする
        this.setAdapter(
            new MSimpleAdapter(
                context,
                listViewDataStore.getResult(),
                line_builder.line_template_layout_id,
                new String[]{},
                new int[]{},
                line_builder
            )
        );

        return this;
    }

    /**
     * アダプタ
     */
    protected class MSimpleAdapter extends SimpleAdapter
    {
        private LayoutInflater mInflater;
        private LineUIBuilder line_builder;


        // 初期化
        public MSimpleAdapter (
            Context context,
            List<HashMap<String, Object>> list_data,
            int resource,
            String[] from,
            int[] to,
            LineUIBuilder line_builder
        )
        {
            super(context, list_data, resource, from, to);

            this.line_builder = line_builder;
            this.line_builder.registerData(list_data);

            // リストの動的な描画のためにインフレータを生成
            this.mInflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );

            Util.d("ListView:アダプタの初期化が完了");
        }


        // 1行を描画するたびに呼ばれるメソッド
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            // 行を表すビュー
            View v = convertView;
            if(v == null){
                v = mInflater.inflate(line_builder.line_template_layout_id, null);
            }

            // この行のUIを構築
            //Util.d("ListView:" + position + "行目の描画を開始");
            line_builder.describeOneLine(position, v);
            //Util.d("ListView:" + position + "行目の描画を完了");

            return v;
        }

    }

}


また,UIBuilder.javaに,少し追記する。


    /**
     * ルートビューからスクロールをなくす
     */
    public UIBuilder withoutScroll()
    {
        // ScrollViewではないレイアウトを雛形として使う
        context.setContentView(R.layout.fw_template_noscroll);
        this.rootLayout = (MLinearLayout)context.findViewById(R.id._FWRootLayout);

        // NOTE: setContentViewを複数回読んだら,後者が優先される
        // http://ichitcltk.hustle.ne.jp/gudon/modules/pico_rd/index.php?content_id=44

        return this;
    }


そして,スクロールのないレイアウトXMLを定義。

(ListViewにはスクロール機能がついているので,ScrollViewの中に配置できないから)

fw_template_noscroll.xml


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



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

    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

<com.android_mvc.framework.ui.view.MLinearLayout
    android:id="@+id/_FWRootLayout"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">


    


</com.android_mvc.framework.ui.view.MLinearLayout>
</LinearLayout>


これでOK。

無いよりはずっとましだろう。


補足

ンデベレ語について:

しりとりで勝つ

http://www.eonet.ne.jp/~arawashi/ur/hakidame/report/shiritori/shiritori.htm

  • 「ン」でしりとりに勝つには、アフリカの力を借りるのが最もスマート  ※「ンジャメナ」など
  • ジンバブエには大きくわけて二つの民族が住んでいます。ひとつはジンバブエの首都ハラレを中心としたショナ族、そしてもう一つがジンバブエ第二の都市ブラワヨ周辺の民族、ンデベレ族

関連する記事:

AndroidのUIで,レイアウトXMLの記述を簡素にするための,7つの基礎知識 - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20121114/SimpleAndroidLayoutXmlTips


jQuery Mobile と HTML5 で、Androidのネイティブアプリを作成する手順 - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20120717/CreateAndroidNativeAppsByJQueryMobile


Android SDK の動かないコード(中級編) ListView内の要素にアクセスしようとするとNullPointerExceptionで落ちるエラー - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20111013/p1

 

2012-10-22

Androidで音声入力した内容を認識し,そのまま音声合成。「おうむ返し」アプリのソースコード

|


音声入力した内容を,そのまま音声出力してみよう。

というAndroidアプリのサンプルコード。


RecognizerIntentを使う場合と,SpeechRecognizerを使う場合の2通り掲載。



前置き

Google音声入力は,とても便利だ。

いちいち手動で文字を打たなくても,端末が音声を聞き取って自動認識し,文字を入力してくれる。

アプリ開発者としても,自然言語処理を気軽にAPI経由で行なえる。

人間と自然に対話する高度なUIを実装していくために,活用シーンが増えてゆくだろう。(例:iOSのSiri)



また,テキストの読み上げ(自動スピーチ)も比較的かんたんに実現できる時代になった。

音声認識に比べ利用の機会が少ないかもしれないが,

合成音声を使った遊びは密かにブレイクしている。(例:初音ミクによる歌声合成)

UIのアクセシビリティ向上にもつながる。



では,両者を合体させて

  • 音声による入力
  • 音声による出力

の両方を兼ね備えたらどうなるか。

ソフトウェアと人間が,より自然に対話しやすくなる。



このようなUIを実現できるデバイスは,なんと言っても電話・スマホである。

最初からマイクもあるし,スピーカーもあるから。

音声によるI/Oが考慮された構造なので,PCよりもずっと上記機能を試しやすいのだ。



以下では,Androidによる「自動音声入力+自動音声出力の組み合わせ」の骨組みを掲載する。

両方ともAPI化されており,きわめて簡単に動作する。


※ただ,日本語に特化した読み上げは,標準SDKではまだ未対応。

漢字の読み上げを自動化するのが厳しいから。


さしあたり,英語でしゃべった内容は,正確に英語で「おうむ返し」される。

インプットが下手だと誤認識されるので,アウトプットも混乱して「おうむ返し」できないが…。

→上手にしゃべること。 自分の外国語の発音レベルを測定するツールにもなる。


サンプルコード


アクティビティ:


package com.example.speechtest;

import android.os.Bundle;
import android.app.Activity;

import java.util.ArrayList;
import java.util.Locale;

import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.speech.RecognizerIntent;
import android.speech.tts.TextToSpeech;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

/**
 * 音声入力(Input)と音声読み上げ(Output)のテスト。
 * マイクに入った音声を認識して,そのまま音声合成し,おうむ返しにスピーカ出力を試みる。
 * @author id:language_and_engineering
 *
 */
public class MainActivity extends Activity implements OnClickListener, TextToSpeech.OnInitListener
{
    // ダミーの識別子
    private static final int REQUEST_CODE = 0;

    // 音声合成用
    TextToSpeech tts = null;


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

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener( this );

        tts = new TextToSpeech(this, this);
    }


    @Override
    public void onClick(View v)
    {
        try {
            // "android.speech.action.RECOGNIZE_SPEECH" を引数にインテント作成
            Intent intent = new Intent(
                    RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
            intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                    RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);

            // 「お話しください」の画面で表示される文字列
            intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "音声認識中です");

            // 音声入力開始
            startActivityForResult(intent, REQUEST_CODE);
        } catch (ActivityNotFoundException e) {
            // 非対応の場合
            Toast.makeText(this, "音声入力に非対応です。", Toast.LENGTH_LONG).show();
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        // インテントの発行元を限定
        if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {

            // 音声入力の結果の最上位のみを取得
            ArrayList<String> results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            String s = results.get(0);

            // 表示
            Toast.makeText(this, s, Toast.LENGTH_LONG).show();

            // 音声合成して発音
            if(tts.isSpeaking()) {
                tts.stop();
            }
            tts.speak(s, TextToSpeech.QUEUE_FLUSH, null);

        }

        super.onActivityResult(requestCode, resultCode, data);
    }


    @Override
    public void onInit(int status) {
        if(status == TextToSpeech.SUCCESS) {
            // 音声合成の設定を行う

            float pitch = 1.0f; // 音の高低
            float rate = 1.0f; // 話すスピード
            Locale locale = Locale.US; // 対象言語のロケール
                // ※ロケールの一覧表
                //   http://docs.oracle.com/javase/jp/1.5.0/api/java/util/Locale.html

            tts.setPitch(pitch);
            tts.setSpeechRate(rate);
            tts.setLanguage(locale);
        }
    }


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

        if( tts != null )
        {
            // 破棄
            tts.shutdown();
        }
    }

}


レイアウトXML


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >


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


</RelativeLayout>


とても短いソースコードで,実現できてしまう。


動作にあたって下記の点に注意すること。

  • 読み上げのためには,音声データをインストールしておく必要がある。後述の手順を参照。
  • 音声を出すためには,マナーモードを解除する。

音声認識画面で端末のメニューキーを押せば設定画面が開き,言語を選択できる。



英語の読み上げのテスト材料としては,BBCのトップニュースなんかを即興で朗読してみるとよい。

http://www.bbc.co.uk/



インプット:音声認識または音声入力(ASR, Auto Speech Recognition)

標準APIのRecognizerIntentクラスで,音声入力して漢字かな交じりの日本語文を取得する方法

AndroidでGoogle音声入力のサンプルコード

http://www.adakoda.com/android/000164.html

  • SDK1.5以降はインテントとして利用可能

音声で文字入力する

http://wiki.livedoor.jp/moonlight_aska/d/%B2%BB%C0%BC%A4%C7%CA%B8%BB%FA%C6%FE%CE%CF%A4%B9%A4%EB

  • Androidの音声認識機能は, 端末とサーバとで処理を分担する分散型音声認識(DSR:Distributed Speech Recognition)方式。音声認識機能を使用する際にはサーバに接続するために3GまたはWiFiが必要

SpeechRecognizerを使った音声認識を行う

http://techbooster.org/android/application/14927/

  • オプション類の詳しい使い方の説明

音声入力結果をひらがなで取得できるかどうか(できれば読み上げもやりやすくなるのだが)

日本Androidの会 / 音声認識のかな取得について

https://groups.google.com/forum/?fromgroups=#!topic/android-group-japan/3Z0DnZYMR54

  • 認識結果をひらがなオンリーの文字列として取得することは不可能(端末によってブレもある)
  • Yahooの形態素解析APIと連携するなど,サードパーティーに頼るしかない


アウトプット:音声合成,読み上げ(TTS, Text To Speech)

標準APIのTextToSpeechクラスで,文字列から音声に変換して読み上げる方法のサンプルコード

テキストを読み上げる

http://wiki.livedoor.jp/moonlight_aska/d/%A5%C6%A5%AD%A5%B9%A5%C8%A4%F2%C6%C9%A4%DF%BE%E5%A4%B2%A4%EB

  • 2011年の情報。日本語はサポートされていない。英語, フランス語, ドイツ語, イタリア語, スペイン語が対象。

テキストの読み上げ(TextToSpeech)を利用する

http://techbooster.jpn.org/andriod/application/550/

  • TextToSpeechはAndroid1.6から実装,音声合成を利用してテキストを読み上げる機能
  • 動作させる実機に音声データのインストールが必要。端末の設定画面で「音声データをインストール」がグレーアウトされていない状態だと、まだインストールされていないので,マーケットからダウンロードする必要がある。「SpeechSynthesis Data Installer」を無料で入手可能
  • 詳しい実装方法

SpeechSynthesis Data Installer / Android TextToSpeech

https://play.google.com/store/apps/details?id=com.svox.langpack.installer&hl=ja


サードパーティ製,日本語を読み上げる機能

アクエストの軽量Android用音声合成エンジン

http://juggly.cn/archives/3781.html

  • ひらがなとアクセント記号の文章を音声に変換。漢字は不可能。

Android JaTTS

http://gimite.net/pukiwiki/index.php?Android%20JaTTS

  • Androidに日本語をしゃべらせる音声合成アプリケーションとライブラリ
  • サーバ側でGalatea Talkを動かして、音声ファイルに変換したものをAndroidで再生している

アンドロイドで日本語音声出力(TextToSpeech):音声読み上げ

http://foonyan.sakura.ne.jp/wisteriahill/website/jqm_template15/index.html

  • KDDIが開発した「N2 TTS」をマーケットからインストール

音声データの「SpeechSynthesis Data Installer」について。

現時点での対応言語:

  1. 英語(アメリカ合衆国)
  2. 英語(イギリス)
  3. フランス語(フランス)
  4. イタリア語(イタリア)
  5. ドイツ語(ドイツ)
  6. スペイン語(スペイン)

アンインストール方法:

SpeechSynthesis Data Installer 最新レビュー

http://android.giveapp.jp/RecentReview/index3.html?app_id=com.svox.langpack.installer

  • これ自体は単なる音声データなので単独では何もできないです。対応アプリを入れましょう。勝手に喋ってウルサイといってる人はユーザー補助の設定を見直しましょう。ちなみにアンインストはSDカード内のsvoxフォルダを消せばOK
  • SDカード内の SVOX というフォルダを削除すれば削除できました

音声データがインストール済みかどうかの判定ロジック:


    if(tts.isLanguageAvailable(locale) >= TextToSpeech.LANG_AVAILABLE)
    {
        tts.setLanguage(locale);
    }
    else
    {
        Toast.makeText(getApplicationContext(), "この言語の音声合成に対応していません。", Toast.LENGTH_LONG).show();
    }

この判定ができれば,未インストール時に音声データのインストールを促すような処理が可能。



補足:Google音声入力について

本稿執筆時点での,Google音声認識の全対応言語の一覧


  1. アフリカーンス語(南アフリカ)
  2. アラビア語(エジプト)
  3. アラビア語(イスラエル)
  4. アラビア語(ヨルダン)
  5. アラビア語(クウェート)
  6. アラビア語(レバノン)
  7. アラビア語(カタール)
  8. アラビア語(サウジアラビア)
  9. アラビア語(アラブ首長国連邦)
  10. ブルガリア語(ブルガリア)
  11. カタロニア語(スペイン)
  12. 中国語,標準語(中国,簡体)
  13. 中国語,標準語(香港,簡体)
  14. 中国語,標準語(台湾,繁体)
  15. 中国語,広東語(香港,繁体)
  16. チェコ語(チェコ共和国)
  17. オランダ語(オランダ)
  18. 英語(オーストラリア)
  19. 英語(カナダ)
  20. 英語(インド)
  21. 英語(ニュージーランド)
  22. 英語(南アフリカ)
  23. 英語(イギリス)
  24. 英語(米国)
  25. 英語(一般)
  26. フィンランド語(フィンランド)
  27. フランス語(フランス)
  28. ガリシア語(スペイン)
  29. ドイツ語(ドイツ)
  30. ヘブライ語(イスラエル)
  31. ハンガリー語(ハンガリー)
  32. アイスランド語(アイスランド)
  33. インドネシア語(インドネシア)
  34. ズールー語(南アフリカ)
  35. イタリア語(イタリア)
  36. 日本語(日本)
  37. 韓国語(韓国)
  38. マレー語(マレーシア)
  39. ノルウェー語ボークモール(ノルウェー)
  40. ポーランド語(ポーランド)
  41. ポルトガル語(ブラジル)
  42. ポルトガル語(ポルトガル)
  43. ルーマニア語(ルーマニア)
  44. ロシア語(ロシア)
  45. セルビア語(セルビア,キリル)
  46. スロバキア語(スロバキア)
  47. スペイン語(アルゼンチン)
  48. スペイン語(ボリビア)
  49. スペイン語(チリ)
  50. スペイン語(コロンビア)
  51. スペイン語(コスタリカ)
  52. スペイン語(ドミニカ共和国)
  53. スペイン語(エクアドル)
  54. スペイン語(エルサルバドル)
  55. スペイン語(グアテマラ)
  56. スペイン語(ホンジュラス)
  57. スペイン語(メキシコ)
  58. スペイン語(ニカラグア)
  59. スペイン語(パナマ)
  60. スペイン語(パラグアイ)
  61. スペイン語(ペルー)
  62. スペイン語(プエルトリコ)
  63. スペイン語(スペイン)
  64. スペイン語(米国)
  65. スペイン語(ウルグアイ)
  66. スペイン語(ベネズエラ)
  67. スウェーデン語(スウェーデン)
  68. トルコ語(トルコ)
  69. Euskara(=スペイン・フランスの国境のバスク語。http://www.weblio.jp/content/Euskara
  70. Latin(=ラテン語


補足2:処理対象となる言語をプログラムから指定


上述のサンプルコードでは,音声合成の対象言語をプログラム中で指定している。


しかし,音声入力の画面で,手動で言語を選ぶことになる。

それだとアプリのユーザにとっては面倒。


プログラム中から,対象言語を動的に指定することができる。

たとえば音声入力の対象言語を英語に固定したい場合,RecognizerIntentに下記のようなextraを渡す。


// Englishの場合
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.US.toString());
            //intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.JAPAN.toString());
            //intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.CHINA.toString());

これで,端末の言語設定などとは関係なく,このIntentによる音声入力の画面ではロケールとして英語が利用される事になる。


逆に,音声入力画面から対象言語を変えたとしても,プログラム的にはこのインテントで指定した言語として処理されることになり,変更できないので注意。

How to set the language in speech recognition on android?

http://stackoverflow.com/questions/10538791/how-to-set-the-language-in-speech-recognition-on-android

  • RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE を "en-US" としても,認識対象の言語は変わらない

日本語の音声認識

http://d.hatena.ne.jp/android_dev/20100312/1268372386



補足3:専用の画面を表示しない場合

音声入力用のダイアログを表示せず,画面遷移なしで音声入力させることもできる。

その場合,RecognizerIntentではなく,SpeechRecognizerを使う。



package com.example.speechtest;

import android.os.Bundle;
import android.app.Activity;

import java.util.ArrayList;
import java.util.Locale;

import android.content.Intent;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.speech.tts.TextToSpeech;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

/**
 * 音声入力(Input)と音声読み上げ(Output)のテスト。
 * マイクに入った音声を認識して,そのまま音声合成し,おうむ返しにスピーカ出力を試みる。
 * @author id:language_and_engineering
 *
 */
public class MainActivity extends Activity implements OnClickListener, TextToSpeech.OnInitListener
{
    // 音声入力用
    SpeechRecognizer sr;

    // 音声合成用
    TextToSpeech tts = null;


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

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener( this );

        tts = new TextToSpeech(this, this);
    }


    @Override
    public void onClick(View v)
    {
        // 音声認識APIに自作リスナをセット
        sr = SpeechRecognizer.createSpeechRecognizer(this);
        sr.setRecognitionListener(new MyRecognitionListener());

        // インテントを作成
        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getPackageName());

        // 入力言語のロケールを設定
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.US.toString());
        //intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.JAPAN.toString());
        //intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.CHINA.toString());

        // 音声認識APIにインテントを処理させる
        sr.startListening(intent);
    }



    @Override
    public void onInit(int status) {
        if(status == TextToSpeech.SUCCESS) {
            // 音声合成の設定を行う

            float pitch = 1.0f; // 音の高低
            float rate = 1.0f; // 話すスピード
            Locale locale = Locale.US; // 対象言語のロケール
                // ※ロケールの一覧表
                //   http://docs.oracle.com/javase/jp/1.5.0/api/java/util/Locale.html

            tts.setPitch(pitch);
            tts.setSpeechRate(rate);
            tts.setLanguage(locale);
        }
    }


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

        if( tts != null )
        {
            // 破棄
            tts.shutdown();
        }
    }



    // 音声認識のリスナ
    class MyRecognitionListener implements RecognitionListener {

        @Override
        public void onBeginningOfSpeech() {
        }

        @Override
        public void onBufferReceived(byte[] buffer) {
        }

        @Override
        public void onEndOfSpeech() {
        }

        @Override
        public void onError(int error) {
            Toast.makeText(getApplicationContext(), "エラー: " + error, Toast.LENGTH_LONG).show();
                // エラーコードの一覧表
                // http://developer.android.com/intl/ja/reference/android/speech/SpeechRecognizer.html#ERROR_AUDIO

                // 認識結果の候補が存在しなかった場合や,RECORD_AUDIOのパーミッションが不足している場合など
        }

        @Override
        public void onEvent(int eventType, Bundle params) {
        }

        @Override
        public void onPartialResults(Bundle partialResults) {
        }

        @Override
        public void onReadyForSpeech(Bundle params) {
            Toast.makeText(getApplicationContext(), "認識を開始します。", Toast.LENGTH_LONG).show();
        }


        @Override
        public void onResults(Bundle results) {
            // 結果を受け取る
            ArrayList<String> candidates = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
            String s = candidates.get(0);

            // トーストで結果を表示
            Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show();

            // 音声合成して発音
            if(tts.isSpeaking()) {
                tts.stop();
            }
            tts.speak(s, TextToSpeech.QUEUE_FLUSH, null);
        }


        @Override
        public void onRmsChanged(float rmsdB) {
        }

    }

}

マニフェストXMLのmanifestタグ直下に,パーミッションの追記が必要。


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



SpeechRecognizerを使った音声認識を行う

http://techbooster.org/android/application/14927/

  • RecognizerIntentを利用した方法では、マイクの絵が表示されているダイアログが表示される。このAndroid標準のダイアログを表示せず、独自の画像等で音声入力状態を表示したい場合などに利用するのがSpeechRecognizer
  • SpeechRecognizerは、Androidの音声認識用に用意されているServiceにアクセスするためのクラス
  • エラーハンドリングつきの詳しいサンプルコード

音声認識(SpeechRecognizer)

http://office-matsunaga.biz/android/description.php?id=12

  • マイクのプロンプトが表示されないですむ

エラーコードの一覧表

http://developer.android.com/intl/ja/reference/android/speech/SpeechRecognizer.html#ERROR_AUDIO





このエントリの続き:

Androidで,音声入力と音声合成をシンプルに記述するためのライブラリ

http://d.hatena.ne.jp/language_and_engineering/20121107/AndroidTTSAndSpeechRecognitionDSL



 

2012-10-17

今から1時間で,64ビットWindows 7上にAndroid開発環境を構築し,サンプルを動作させる手順 (※4.x系のSDKを使用)

|

※この記事は,過去の記事をバージョンアップさせたリメイク版です。


Android本体を買わなくても,Androidアプリの開発と動作確認だけなら,無料で可能。

  • アプリの開発環境:EclipseやAndroid SDKを無償で利用できる。

以下は,Windows 7 (64bit版)上

Androidアプリの開発環境をゼロからセットアップし,

サンプルアプリケーション(Hello Worldプログラム)を動かすための手順。


開発環境 構築手順

  • (1)JDKの導入
  • (2)Android SDKの導入
  • (3)Eclipseの導入
  • (4)Eclipse上でAndroid SDKをセットアップ

サンプルアプリケーション 動作手順

  • (5)サンプルプロジェクトの作成
  • (6)サンプルアプリの起動
  • (7)おまけ:実機上でのテスト方法

途中,ダウンロードのために通信が発生する。それを含めて,全体で1時間以内



※なお,Windows Vista上のインストール手順は,下記のエントリを参照。

今から1時間で,Androidアプリの開発環境を構築し,Windows上でサンプルを動作させる手順

http://d.hatena.ne.jp/language_and_engineering/20110724/p1

  • OSはWindows Vista 32bit
  • Eclipseは Galileo (3.5) のPleiades日本語パック
  • 2011年7月時点での情報

ちなみに,最初に注意すべき点だが,

もしWindowsのユーザ名が日本語だと,Androidの仮想マシンが起動しない。

最初にユーザ名を日本語で設定してしまうと,後から変えても最初のユーザ名がフォルダ名として残り,利用され続けてしまう。

「ユーザプロファイルの移行」を実施しても無駄。

観念して今までのアカウントを捨て,全角文字を使わずに新規アカウントを作り直すこと。



開発環境 構築手順


(1)JDKの導入

JDKのダウンロードページに遷移。

Java SE Development Kit 7 Downloads

「Java SE Development Kit」の中の,

「Accept Licence Agreement」を押下し,

「Windows x86」の欄のリンクからJDKをダウンロード。(x64ではないので注意。あえて32ビット版を落とす。)


jdk-7u6-windows-i586.exeのようなファイルを保管。


実行。

「Java SE Development Kit インストールウィザードへようこそ」のダイアログが開く。

「次へ」を押下して,インストール先のパスを変更。

※デフォルトでは,C:\Program Files (x86)\Java\jdk1.7.0_06\ みたいにスペースとか括弧とか含んだファイルパスが設定されているが,そんなパスをまともに使おうと思ってはいけない。

D:\dev\java\jdk1.7.0_06\ のような代わりのフォルダを作って,そこを選択する。


「次へ」を押下するとインストール開始。

途中に別ダイアログが出て,JREのインストール先を聞かれるが,これはデフォルトのパスのままで「次へ」を押下。

少しすると,「Java SE Develelopment Kit が正常にインストールされました」となる。

「閉じる」を押下。



PATHを通す。

コンパネ>システムとセキュリティ>システム

で,左サイドバーの「システムの詳細設定」>「環境変数」

から,システム環境変数のPathの末尾に,JDKのbinフォルダのフォルダパスを入力してOK。


新しくコマンドプロンプトを立ち上げて,javacコマンドが動くことを確認。

(※OSの再起動は必要ない。だが,既に起動しているコマプロ上では,環境変数の変更は反映されない。)



参考:

Windows7(x64)上でのAndroid開発環境構築でハマった箇所

http://www.abe3.net/2011/01/android-dev-windows7-64/

  • Android SDKのインストーラがチェックしているのは32bit用のJDKなので、64bit版のJDKが入っていても見つからず、エラーとしているためらしい(2011年1月の情報)

Windows7 64bit版にAndroid SDKとEclipse3.6をインストール

http://midnightblue.jp/blog/2011/04/02/104604

  • 64ビット版のJDKでうまくいっている例。2011年4月の情報


(2)Android SDKの導入

下記ページにアクセス。

右上からLanguagesを選択できる箇所があるので,「日本語」を選択。

※IEだとこの部分のUIが使いづらく,ボックス内で「日本語」を選ぼうとしてドロップダウンの下の方にマウスカーソルを移動すると,ボックス自体が消えてしまう事がある。その場合は,プルダウンにフォーカスを当てた状態で,下キーやエンターキーを使って言語選択する。


デベロッパーへのお知らせ」画面が開く。

少し下の

「ダウンロード Android SDK には、優れたアプリケーションの作成に必要となるツール、サンプル コード、ドキュメントが含まれています。 」

の部分にあるリンクをクリック。


「Get the Android SDK」の画面になる。

「Download the SDK for Windows」のボタンを押下。


installer_r20.0.3-windows.exe

のようなファイルがダウンロードされる。


適当な場所に設置し,実行。

Nextで「Detect whether Java SE Development Kit is installed.」となり,

インストール済みのJDKのパスが表示される。

※JDKを検出できなかった場合はここでエラーになる。


Next。

Choose Usersは,Install just for meでNext。


Choose Install Locationでは,デフォルトでCドライブのユーザディレクトリが選択されている。

(C:\Users\〜\AppData\Local\Android\android-sdk のように。)

Cドライブに負担をかけたくないので,このパスを変更する。

Eclipseの重さやAndroidエミュレータの重さを知っていれば,当然変えたいと思うだろう。

D:\dev\Android\android-sdk のようなフォルダを作り,そのフォルダパスを入力してNext。


Installを押下。

プログレスバーがCompletedになるので,Nextを押下。

Finishを押下。


ふつうにFinishすると,「Android SDK Manager」が起動する。

このプログラムは,マシン内にインストールされているAndroid SDKを自動的に検出して一覧表示する。

スキャン(fetch)に少し時間がかかるが,表示が完了した時点で,インストール済みのSDKのバージョンにチェックが入っているはずだ。



関連するツール(adb)や,他のAPIバージョンのSDKも入れたいので,

SDK Manager右下の「Install 9 Packages」のようなボタンを押下。



「Choose Packeges to Install」のダイアログで,追加したいパッケージの一覧と各ライセンスが表示される。

「Accept All」を選んで,Installを押下。


ダウンロードが始まり,作業の実行状況がログで表示される。

たくさんログが出るが,下記のような感じで終了する。


・・・

Preparing to install archives
Downloading Android SDK Platform-tools, revision 14
Installing Android SDK Platform-tools, revision 14
Stopping ADB server failed (code -1).
Installed Android SDK Platform-tools, revision 14
Downloading Documentation for Android SDK, API 16, revision 3
Installing Documentation for Android SDK, API 16, revision 3
Installed Documentation for Android SDK, API 16, revision 3
Downloading SDK Platform Android 4.1.2, API 16, revision 3
Installing SDK Platform Android 4.1.2, API 16, revision 3
Installed SDK Platform Android 4.1.2, API 16, revision 3
Downloading Samples for SDK API 16, revision 1
Installing Samples for SDK API 16, revision 1
Installed Samples for SDK API 16, revision 1
Downloading Sources for Android SDK, API 16, revision 2
Installing Sources for Android SDK, API 16, revision 2
Installed Sources for Android SDK, API 16, revision 2
Downloading Google USB Driver, revision 7
Installing Google USB Driver, revision 7
Installed Google USB Driver, revision 7
Downloading ARM EABI v7a System Image, Android API 16, revision 3
Installing ARM EABI v7a System Image, Android API 16, revision 3
Installed ARM EABI v7a System Image, Android API 16, revision 3
Downloading Intel x86 Atom System Image, Android API 16, revision 1
Installing Intel x86 Atom System Image, Android API 16, revision 1
Installed Intel x86 Atom System Image, Android API 16, revision 1
Downloading Mips System Image, Android API 16, revision 2
Installing Mips System Image, Android API 16, revision 2
Installed Mips System Image, Android API 16, revision 2
Downloading Google APIs, Android API 16, revision 3
Installing Google APIs, Android API 16, revision 3
Installed Google APIs, Android API 16, revision 3
Updated ADB to support the USB devices declared in the SDK add-ons.
Stopping ADB server succeeded.
Starting ADB server succeeded.
Done. 10 packages installed.
Done loading packages.

この状態でSDK Managerのプログレスバーが停止したら,ログのウィンドウを×ボタンで閉じる。

×ボタンでAndroid SDK Managerも終了させる。


ここで,インストール対象フォルダ android-sdk の中に,

「platform-tools」というフォルダが新規生成されていることを目視で確認。

そのフォルダの中にadb.exeがある。

Android SDKのアップデート

http://namakemono.6.ql.bz/web/index.php?a=ubuntu&b=updateAndroidSDK

  • (初期状態では)adbが見当たりません。adbを探している途中で「adb_has_moved.txt」なんてファイルをみつけましたが、そもそも「Platform-tools」なんフォルダは存在しない
  • アップデートを再度実行すれば解決

Android 開発ガイド > 付録 > 1. Android API レベル

https://sites.google.com/a/techdoctranslator.com/jp/android/appendix/api-levels

  • Platform VersionとAPI Levelの対応表
  • API Levelの中に,さらに細分化してPlatform Versionがある。例えば,API Level 10をサポートしているプラットフォーム(OSバージョン)は2つあり,Android 2.3.3と2.3.4がある。


後々の事を考えて,このフォルダにPATHを通しておく。

コマンドラインで「adb.exe」などのツールを呼び出す事が多いから。

  • D:\dev\Android\android-sdk\platform-tools

システム環境変数のPathに上記パスを追加したら,新規コマンドプロンプトを立ち上げて,adbコマンドが通ることを確認。



ここまでで,IDEが使う資材はそろった。

次は,IDEそのものを導入する。




(3)Eclipseの導入

入手と起動

ここでは,動作パフォーマンスを考慮して,Indigo (3.7)なおかつ日本語パックであるPleiadesを使う。

Pleiadesのページ掲載のパッケージ別シェアの円グラフを見てわかる通り,本稿執筆時点では3.7の利用比率が最大で,2番手が4.2である。


※バージョン4.2 Junoを選ばない理由:

Pleiades All in One 4.2.1.v20121007 (Eclipse 4.2.1 SR1 Juno for Windows ベース)

http://mergedoc.sourceforge.jp/index.html#/pleiades_distros4.2.html

  • 「注意: Eclipse Juno は環境により長時間使用すると重くなる不具合が報告されています。」

Eclipse Juno のパフォーマンス問題

http://www.infoq.com/jp/news/2012/10/eclipse-juno-performance

  • バージョン3.8に比べて4.2Junoは重く,パフォーマンスの違いがとても大きい。
  • 2012年10月の情報。


以下のページにアクセス。


「Pleiades All in One ダウンロード」

の「3.7 Indigo」をクリック。

Javaの列の

  • Standard All in One (JRE なし)

のDownloadを選択。

ダウンロード。ファイルサイズは400MBぐらい。


適当な場所に解凍。(WinRARなどを使う。)

容量は480MBぐらい。


解凍内容を特定のフォルダ(D:\dev\java など)の配下にコピー。

  • .metadata.default
  • eclipse

※なお常識的な点だが,半角スペースを含むようなフォルダを利用してはいけない。C:\Program Filesとか。



eclipse.exeを起動。初回起動は時間がかかる。

ワークスペースを聞かれるので,eclipseフォルダと並ぶ位置を指定。

  • D:\dev\java\workspace

「この選択をデフォルトとして使用し,今後この質問を表示しない」にチェック。

OKを押下。

eclipse本体が開く。


JREの設定

Eclipse上で

ウィンドウ→設定→Java→「インストール済みのJRE」で,

もしjdkではなくjreが標準VMになっている場合,

jdkを標準VMに設定しなおす。

追加ボタンを押下。

JREの型として「標準VM」を選択し「次へ」を押下。

「JREの追加」ダイアログで,JREホームとしてJDKのホームディレクトリを入力。

  • D:\dev\java\jdk1.7.0_06

すると,「JRE名」「JREシステムライブラリー」の欄が自動的に埋まる。

「完了」を押下。

「インストール済みのJRE」ダイアログ上で,

いま追加したJDKにチェックを入れて「OK」を押下。


参考:

Eclipse−1.インストール

http://www.javaroad.jp/opensource/js_eclipse1.htm

  • EclipseのJavaVMの設定がJREではなく、JDKになっていることを確認します。JDKを指定することにより、より詳細なコーディング情報を得ることができます。
  • 標準VMの項目がJREへのパスになっている場合はJDKのパスに変更します。「編集」ボタンをクリックし、「JRE名」項目と「JREのホーム・ディレクトリー」項目の値を変更します。


(4)Eclipse上でAndroid SDKをセットアップ

プラグインのインストール

Eclipse上で,ヘルプ→新規ソフトウェアのインストール を選択。

インストールダイアログ上で,「作成対象」の右の「追加」ボタンを押下。


サイトの追加ダイアログ上で,名前に「Android」,

ロケーションに「https://dl-ssl.google.com/android/eclipse/」を入力。

OKを押下。


「フィルター入力」欄の下に「保留中」と表示され,

Eclipse本体の右下にプログレスバーと共に「Androidの子を取り出し中」と表示される。


しばらく(数秒〜1分)待つと,

「開発ツール」というチェックボックスが現れるのでチェックして,「次へ」を押下。

(NDK Pluginsのチェックボックスは無視する。)


「要件および依存関係を計算」のプログレスバーが表示されて,数秒〜数分ほど時間がかかる。


インストール詳細ダイアログが開く。

こんな項目が表示されている。

  • Android DDMS com.android.ide.eclipse.ddms.feature.group
  • Android 開発ツール com.android.ide.eclipse.adt.feature.group
  • Android 階層ビューアー com.android.ide.eclipse.hierarchyviewer.feature.group
  • Android トレースビュー com.android.ide.eclipse.traceview.feature.group
  • Tracer for OpenGL ES com.android.ide.eclipse.gldebugger.feature.group

「次へ」を押下。

ライセンスが表示されるので、「使用条件の条項に同意します」を選択した上で,

「完了」を押下。


しばらく待つ。

Eclipse本体の下部に,インストールの進捗状況が表示される。


突然,セキュリティダイアログとして


警告:署名なしコンテンツを含むソフトウェアをインストールしています。
このソフトウェアの真正性または妥当性を実証できません。
インストールを続行しますか?

と表示される。

OKを押下。



インストール変更内容を有効にするには,Eclipseを再始動する必要があります。
再開せずに,変更を適用することができますが,問題が発生する可能性があります。

のダイアログが表示される。

(Pleiadesなので)再起動はせずに,変更だけ適用する。

Eclipse本体を閉じ,終了させる。


プラグインの追加を反映したうえで最適化してEclipseを起動するために,

  • 「D:\dev\java\eclipse\eclipse.exe -clean.cmd」

というcmdファイルをダブルクリックして実行。


Eclipse本体が安全に再起動する。


※参考:

Android開発環境の構築 Eclipse編

http://sky.geocities.jp/izeefss/develop/android/env_eclipse.html

Pleiadesのドキュメントでは、新規Pluginインストール後は、[eclipse.exe -clean]を実行して起動を最適化するようにとの記載がある。


プラグインの設定

Eclipse本体で,ウィンドウ→設定→Android を選択。

Android設定の「SDKロケーション」右の「参照」ボタンを押下。

SDKの場所を入力。

  • D:\dev\Android\android-sdk

(※ただし,パスを手入力すると「OK」ボタンが有効にならない。「参照」ボタンを使ってパスを選択する必要がある。)

OKを押下。



Eclipseのメニューショートカットを設定

ウィンドウ→パースペクティブのカスタマイズ から,

「ショートカット」タブを選択。

サブメニューを「新規」にして,

ショートカット・カテゴリーの「Android」にチェック。

「OK」を押下。


参考:

Android 開発環境

http://server-free.com/android/devenv.htm

  • メニューのカスタマイズ:「ファイル」→「新規」でAndroidプロジェクトが表示されるように設定します。

Android SDK & Eclipse 覚書

http://jumbuck.cocolog-nifty.com/blog/2011/02/android-sdk-942.html

  • 「新規」のところに「Android Project」を出しておく方法です。デフォルトだと「Javaプロジェクト」と「プロジェクト」だけでAndroid系のプロジェクトを作るのがちょっと面倒


Androidアプリの開発環境の構築が完了した。

次に,この環境を使ってサンプルを動かす。


※サンプルコードは,Androidドキュメントに掲載されているものを改変している。

Technical Resources / チュートリアル / Hello, World

http://developer.android.com/intl/ja/resources/tutorials/hello-world.html

  • デベロッパーにとって、開発フレームワークの第一印象は、どれだけ簡単に「Hello, World」を記述できるかで決まります。Android では、非常に簡単に記述できます。総合開発環境として Eclipse を使用している場合には、開発は特に簡単です。


サンプルアプリケーション 動作手順

(5)サンプルプロジェクトの作成

もしWindows Vistaの場合は,ここでいったんEclipseを閉じて

eclipse.exe を「管理者として実行」し,起動し直すこと。

(そうしないと,プロジェクトのビルド時にフォルダアクセスの権限不足でエラーになる恐れがある。)


Windows 7のタスクバーに配置したい場合は,

まずeclipse.exeのショートカットをつくり,プロパティの詳細設定から「管理者として実行」にチェックして,

そのショートカットをタスクバーにドラッグ。



Eclipse本体のメニューバーから,

ファイル→新規→Android Application Project を選択。

「New Android App」ダイアログ上で,

Project Nameとして「AndroidHelloWorld」と入力。

アプリケーション名に「com.example.androidhelloworld」と入力されている。

警告は無視する。


ビルド・ターゲットとして,

現時点で「まず無難である」と言い切れるバージョンとして

「Android 2.3.3」にチェック。


参考:

Android OSのバージョン別シェア、過半数が「Android 2.3」(2012年10月)

http://www.itmedia.co.jp/promobile/articles/1210/05/news095.html

  • 「Android 4.0.3」および「Android 4.0.4」(Ice Cream Sandwich)は24%に達しているものの、いまだにGingerbreadこと「Android 2.3」が過半数を占める

Build SDKとして,API 16が選択されている。

Minimum Required SDKとして,API 10が選択されている。

「次へ」を押下。


Configure Launcher Iconとして,アプリのアイコンをカスタマイズできる。

初回なので何もしないで「次へ」を押下。


Create Activityの画面になる。

BlankActivityが選択された状態で,「次へ」を押下。


New Blank Activityの設定ダイアログになる。

ここも,とりあえずそのままで「次へ」を押下。


「Install Dependencies」のダイアログになる。

もし,特定のバージョンのSDKをインストールするように促されたら,

「Install/Upgrade」を押下。

Android SDKマネージャーが,プロジェクトに必要なバージョンのSDKをインストールしてくれる。

その際,確認ダイアログでは「すべて受諾」してインストールする。

その後「完了」を押下。


これで,プロジェクトが作成され,アクティビティとレイアウトの雛形も生成される。



Eclipse本体の左ペインの「パッケージ・エクスプローラ」から,

AndroidHelloWorld→src→com.example.androidhelloworld→MainActivity.java

を選択してダブルクリック。


エディタ上に,下記のようなコードが表示される。


package com.example.androidhelloworld;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

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

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
}

特に手は加えないでおく。



(6)サンプルアプリの起動

パッケージエクスプローラで,

MainActivity.javaにカーソルをあてる。


Eclipse本体のメニューバーの「実行」→「実行」を選択。

「次を実行」ダイアログ上で,「Androidアプリケーション」を選択。

「OK」を押下。


Android仮想マシンの設定がまだなので,

「No compatible targets were found. Do you wish to add a new Android Virtual Device?」

というダイアログが出る。

「はい」を押下。

※Android Virtual Device(AVD)とは,Androidアプリが動作するための仮想マシンのこと。


「Android Device Chooser」のダイアログ上で,

「Launch a new Android Virtual Device」を選択。

まだ仮想マシンが無いので,No AVD Availableと表示される。

「マネージャー」を押下。


「Android Virtual Device Manager」ダイアログが開く。

「新規」を押下。


「新規Android仮想デバイスの作成 (AVD)」のダイアログ上で,

名前に「my_avd」と入力。

ターゲットとして「Android 4.1.2 - API Level 16」を選択。

(これは,このAndroidアプリのプロジェクトを作る際に指定したBuild用API Levelと同じである。)

CPU/ABIとして「armeabi-v7a」を選択。


「AVDの作成」ボタンを押下。

AVDの一覧に追加される。


※押下しても,何も起きない場合がある。

その場合,押下するたびに毎回,Eclipse本体のコンソールログに

  • 「Unable to find a 'userdata.img' file for ABI armeabi to copy into the AVD folder.」

と表示されていたりする。

原因は2通り考えられる。

  • SDKマネージャから「ARM EABI v7a System Image」をインストールし忘れている。
  • CPU/ABIの項目が空欄のままAVDを作成しようとしている。

「Unable to find a 'userdata.img' file for ABI armeabi to copy into the AVD folder.」というエラーの対処法

http://d.hatena.ne.jp/nakamura001/20120906/1346937805

  • 4.0以降で発生しうるエラー

How do you create or find a “userdata.img” file for ABI armeabi to copy into the AVD folder

http://stackoverflow.com/questions/10039616/how-do-you-create-or-find-a-userdata-img-file-for-abi-armeabi-to-copy-into-the

  • in the end i found out, that i didn't made a choice in the CPU/ABI

ABI

http://ja.wikipedia.org/wiki/Application_Binary_Interface

  • Application Binary Interface。CPUの命令セットみたいな概念。EABIは組み込みシステムの場合。

これでAVDができたので,「Android Virtual Device Manager」のダイアログを閉じる。

「Android Device Chooser」のダイアログ上で「リフレッシュ」を押下。

先ほど作成したAVDが表示されるので,選択して「OK」を押下。



正常にAVDを起動できた場合,数秒すると「5554:(AVD名)」というウィンドウが現れ,

エミュレータが表示される。

左側に端末画面,右側に端末キーボードが表示される。

このエミュレータは,Eclipseとは別プロセスである。


※「5554」とは,AVDが利用するポート番号。下記URLを参考。

Androidエミュレータについて

http://tech.cm55.com/wiki/Android/Emulator

  • AVDは単にJavaコードを実行するのではなく,QEMUを使って実機のCPUをエミュレートする。根本的にCPUの命令セットが異なるので,エミュレータ動作時のCPUの負荷は大きい
  • エミュレータの使用するポートは5554から2刻み。telnet経由で接続できる
  • AVDはハードウェア的な仮想化であるが,その内部ではadb(Android Debug Bridge:ソフトウェアデバッガ)が動作可能で,デバイス中のlinuxシェルを操作できる

少し待つと,エミュレータの画面上にHOMEが表示される。

そしてさらに待つと,実行したいサンプルアプリが自動起動する。

「MainActivity」というタイトルのもと,

白い背景に黒字で,

念願の「Hello, World!」が表示される。



だいたい同時に,「Auto Monitor Logcat」のダイアログが起動し,

logcatを自動的に利用するかと聞かれる。

これはYesを選択しておく。


起動に至るまでの,Eclipseのコンソールログ:

[AndroidHelloWorld] New emulator found: emulator-5554

[AndroidHelloWorld] Waiting for HOME ('android.process.acore') to be launched...

[AndroidHelloWorld] HOME is up on device 'emulator-5554'

[AndroidHelloWorld] Uploading AndroidHelloWorld.apk onto device 'emulator-5554'

[AndroidHelloWorld] Installing AndroidHelloWorld.apk...

[AndroidHelloWorld] 成功!

[AndroidHelloWorld] Starting activity com.example.androidhelloworld.MainActivity on device emulator-5554

[AndroidHelloWorld] ActivityManager: Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.androidhelloworld/.MainActivity }



ここで,いま実行されているサンプルアプリの実体を確認してみる。

Windowsのエクスプローラから,Eclipseの該当ワークスペースのbinフォルダを開くと,下記のようなファイルがある。

D:\dev\java\workspace\AndroidHelloWorld\bin

  • AndroidHelloWorld.apk (アプリケーション本体。配布用のファイル)
  • classes.dex (JavaのclassファイルをAndroid用に変換したもの)
  • resources.ap_ (ソースコード以外のリソースをまとめたもの)

※.apkとresources.ap_はいずれもzipフォーマットのファイルであり,拡張子を変えれば中身を閲覧できる。


参考:

.apkファイルとは:Android Developers / 開発の基礎

http://developer.android.com/intl/ja/guide/topics/fundamentals.html

  • Android パッケージは、拡張子が .apk のアーカイブ ファイル。ユーザーは、このファイルをデバイスにダウンロードして利用する。
  • 1 つの .apk ファイルに含まれているすべてのコードが、1 つのアプリケーション

.dexファイルとは:Androidの仕組みを知る(2) Android Runtimeとアプリケーション・フレームワーク

http://itpro.nikkeibp.co.jp/article/COLUMN/20091208/341738/

  • Dalvik VMが実行するのは,「Dalvik Executable(DEX)」と呼ばれる独自形式のバイナリ・プログラム
  • 開発者はDEXの作成を意識しないでよい。「dx変換ツール」がJavaのクラス・ファイルからDEXに自動で変換する

resources.ap_ファイルとは:What is resources.ap_

http://stackoverflow.com/questions/4995965/what-is-resources-ap-and-why-does-it-have-a-penchant-for-not-existing

  • The resource.ap_ is all the resources for you file zipped up. Everything from the res, assets folders and the Manifest file.
  • Both of these files are in .zip format. They can easily be view by changing the extension to .zip and opening them.

また,

  • D:\dev\java\workspace\AndroidHelloWorld\bin\com\example\androidhelloworld

というフォルダの中には,javaのクラスファイルが格納されている。



ここまでわかったら,エミュレータを閉じてサンプルアプリを終了する。


Eclipseも閉じて,本作業はおひらき。



(7)おまけ:実機上でのテスト方法

念のため,エミュレータを使わないデバッグ方法も述べる。

もし端末とUSBケーブルを持っている場合,すぐに実機上でも動作確認できる。


端末上で

設定→アプリケーション→提供元不明のアプリ にチェック。

設定→アプリケーション→開発→USBデバッグ にチェック。

設定→アプリケーション→開発→スリープモードにしない にチェック。



次に,PC上にUSBドライバをインストール。

端末のメーカやモデルごとにドライバを入れる必要があり,結構面倒。



PCと実機をUSBケーブルでつなぐ。

USBドライバが自動認識されれば,それでよし。

自動認識されなかった場合は,個々のメーカのUSBドライバ認識手順に従って手動で設定。



Eclipse上で,メニューバーの実行→実行構成 で「実行構成」ダイアログが出る。

「ターゲット」タブ上で,「手操作」を選択し「適用」を押下。

「実行」を押下。



「Android Device Chooser」ダイアログが出る。

上の欄が実機。下の欄がエミュレータ。

対象となる実機を選択し,OKを押下。



すぐに,PCから実機へアプリが転送される。

そして自動的に実機上で該当アプリが起動される。

(エミュレータと比べると非常に速いので,感動するだろう。)


コンソール上には,下記のようなログが出る。

Android Launch!

adb is running normally.

Performing com.example.HelloAndroidActivity activity launch

Uploading HelloAndroid.apk onto device '(端末ID)'

Installing HelloAndroid.apk...

成功!

tarting activity com.example.HelloAndroidActivity on device (端末ID)

ActivityManager: Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example/.HelloAndroidActivity }


実機でのデバッグの速さを一度経験すると,エミュレータを手放したくなる。



補足

古いバージョンのEclipseプラグインとは,若干使い勝手が異なっている。


例えば,画面キャプチャを撮る方法が最初分からないかもしれない。

実機端末のスクリーンショットを取得するには,DDMSパースペクティブを開き,

ウィンドウ>ビューの表示>Devices で該当デバイスを選択し,カメラのアイコンを押下すればよい。





関連する記事:

Androidアプリ開発作業時に,頻繁に参照するサイトのリンク集 - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20121116/AndroidAppsDevelopmentFrequentLinks


WebアプリとAndroidアプリのアナロジー (「Androidのアレは,Webで例えるなら○○だ」) - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20110813/p1


Androidでライブラリ・プロジェクトを作成し,Eclipse上でコードを共有しよう - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20130116/AndroidLibraryProjectOnEclipse


AndroidのUIで,レイアウトXMLの記述を簡素にするための,7つの基礎知識 - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20121114/SimpleAndroidLayoutXmlTips


今から5分で,開発中のAndroidアプリを単体テストしよう (JUnitで自動テストする方法) - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20130121/UnitTestOfAndroidAppOnEclipse


Androidアプリをマーケットに公開する方法の作業手順メモ (リリース時とアップグレード時のチェックリスト) - 主に言語とシステム開発に関して

http://d.hatena.ne.jp/language_and_engineering/20120310/AndroidMarketReleaseSteps


android-mvc-framework - AndroidのMVCフレームワーク - 「Android-MVC」

http://d.hatena.ne.jp/language_and_engineering/archive?word=*[Android-MVC%20framework]


※2013年追記:エントリのURLをスペルミスして「Adnroid」にしてしまった…orz