2010年10月29日金曜日

MapView でアニメーション (AnimationDrawable編)

MapView を使って、Google マップを表示させている上に、
画像をアニメーションさせるサンプル・プログラムを2パターン作成してみました。

まずは1つ目。

<設計方針>

画面のレイアウトは、FrameLayout にして、
FrameLayout 一杯に MapView と ImageView を順番に表示します。
そして、ImageView を AnimationDrawable でアニメーションさせます。

<問題点>

画像を表示させる位置が、Google Map 上の位置 (GeoPoint) に対応させることができません。
(というか分かりません。ひょっとしたら何らかの方法があるのかもしれません。。)

そのため、画面の左上のすみっこ等、Google Map の座標(緯度/経度)とは関係なく表示させればよい、
というときくらいには使えるかな~という方法です。


以下にポイントとなるソースを貼り付けます。

まずはレイアウトです。
FrameLayout に対し、MapView、ImageView の順で表示させます。

main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<com.google.android.maps.MapView
 android:id="@+id/mapview"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:enabled="true"
 android:clickable="true"
 android:apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
 />

<ImageView
 android:id="@+id/imageview"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 />

</FrameLayout>


次は、AnimationDrawable に読み込ませるアニメーション・リストです。
drawable フォルダには、person_01.png ~ person_60.png まで 60 の画像ファイルを配置しています。

animation.xml
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
 android:oneshot="false">
 <item android:drawable="@drawable/person_01" android:duration="50" />
 <item android:drawable="@drawable/person_02" android:duration="50" />
 <item android:drawable="@drawable/person_03" android:duration="50" />
 <item android:drawable="@drawable/person_04" android:duration="50" />

  (途中省略)

 <item android:drawable="@drawable/person_58" android:duration="50" />
 <item android:drawable="@drawable/person_59" android:duration="50" />
 <item android:drawable="@drawable/person_60" android:duration="50" />

</animation-list>


最後は Activity (MapActivity継承) クラスです。

MapTestActivity.java
package jp.kochi.test;

import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;

public class MapTestActivity extends MapActivity {
 /** Called when the activity is first created. */
 AnimationDrawable mAnimationDrawable;
 ImageView mImageView;

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

  MapView mapView = (MapView) findViewById(R.id.mapview);

  mapView.setBuiltInZoomControls(true);

  mImageView = (ImageView) findViewById(R.id.imageview);
  mImageView.setBackgroundResource(R.anim.animation);
  mAnimationDrawable = (AnimationDrawable) mImageView.getBackground();

 }

 @Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);

  mAnimationDrawable.start();
 }

 @Override
 protected boolean isRouteDisplayed() {
  // TODO Auto-generated method stub
  return false;
 }
}


以下は実行画面です。
分かりにくいですが、左上の画像が変化(アニメーション)しています。


エミュレーターで Google マップ表示 (Proxy経由の場合)

Proxy 環境化の PC にて emulator を起動し、Google Map を表示させると、
地図が表示されずに、LogCat 上に以下のエラーが記録されます。

ERROR/ActivityThread(200): Failed to find provider info for com.google.settings
ERROR/MapActivity(222): Couldn't get connection factory client

emulator に対し、Proxy 設定を行えば、解決したのですが、
ググって調べた結果、対応方法が3通りあることが分かったので、以下に整理します。

設定する値は共通です。
設定値
-http-proxy http://<proxyのFQDN又はIPアドレス>:<ポート番号>

emulator の proxy 設定方法。
  1. Default emulator options を設定。
    新規プロジェクト作成時のデフォルトとなる emulator のオプション。
    既存プロジェクトには反映されないので、既存プロジェクトに対しては 2. か 3. の方法を実施。

  2. Additional Emulator Command Line Options を設定
    各プロジェクト毎の個別設定。
    1. を実施していれば、新規プロジェクトは自動設定されます。

  3. emulator コマンドの引数で指定。

詳細は以下の通りです。

1. Default emulator options を設定。

Eclipse にて "Window" --> "Preferences" で Preferences 画面を表示させます。
"Android" --> "Launch" を選択し、以下のように "Default emulator options" に値を設定します。
これで新規プロジェクトが作成されるたびに、この設定が反映されるようになります。



2. Additional Emulator Command Line Options を設定

Eclipse にて "Run" --> "Run Configurations..." 又は "Debug Configurations..." を選択。
左側の該当のプロジェクトを選択し、右側の "Target" タブを選択。
一番下にある "Additional Emulator Command Line Options" に値を設定します。

※ 自分の環境だけも知れませんが、画面を下に広げないと見えない状態でした。



3. emulator コマンドの引数で指定。

コマンド・プロンプトにて、emulator を起動するときに、
-http-proxy http://<proxyのFQDN又はIPアドレス>:<ポート番号> を指定します。

C:\> emulator -avd <AVD名> -http-proxy http://<proxyのFQDN又はIPアドレス>:<ポート番号>

2010年10月27日水曜日

subclipse でチェックアウトすると gen フォルダに関するエラーが出る

subclipse でチェックアウトを行うと、以下のようなエラーが出ます。
Project <プロジェクト名> is missing required source folder: 'gen'
The project cannot be built until build path errors are resolved

これは、Eclipse で以下のように操作すれば解消されます。
  1. Eclipse のメニューより、"Project" --> "Clean..." を選択。
  2. "Clean projects selected below" を選択し、該当のプロジェクトにチェックを入れ、
    "OK" を押します。


Eclipse でヘンテコなエラーが出たら、
Clean をやれば、大抵復旧される気がします ( ̄△ ̄)イイスギ?

2010年10月26日火曜日

subversion への接続を https 化することで proxy 経由でも接続可能になった

前回、外部から proxy 経由で自宅 subversion に接続しようとすると、
うまくできませんでした。

Proxyサーバに阻まれているんだけどどうしよう? を見ると、以下のような記述があります。
"別の戦略としては、SSL上でチェックアウトする、というのもあって、多くのプロキシがこれを許可している。"
ということで、 、Apache を https 化することにしました。

結論としては、SSL にすれば proxy 経由でも正常に subversion に接続することができました!

接続イメージ以下のような感じです。
外部PC --> proxy --> インターネット --> 自宅ルータ --> 自宅PC(Apache+SSL+subversion)

※ 自宅PCの環境は Windows Vista Ultimate SP2 32bit です。

以下に、OpenSSL 付の Apache の導入手順をメモしておきます。

  1. Apache 2.2.17 (OpenSSL付) ダウンロード
    Apache は以下のサイトより、
    httpd-2.2.17-win32-x86-openssl-0.9.8o.msi をダウンロードしました。

    http://httpd.apache.org/download.cgi


  2. 既存の Apache 2.2.16 (OpenSSL無) をアンインストール


  3. Apache 2.2.17 (OpenSSL付) インストール
    httpd-2.2.17-win32-x86-openssl-0.9.8o.msi を実行。

    手順は Apache 2.2.16 (OpenSSL無) と変わりません。
    詳細は Subversion & Apache & subclipse の導入 の "4. Apache のインストール" を参照。

    ※ httpd.conf は 2.2.16 アンインストール後も残っています。
        さらに、2.2.17 インストール後も残っているので、2.2.16 時点のものが、
        そのまま利用できました。


  4. 秘密鍵の作成
    コマンド・プロンプトを管理者として実行し、以下を実行。
    <Apache導入先>\conf\server.key を作成します。

    C:\> cd C:\Program Files\Apache Software Foundation\Apache2.2\bin

    C:\Program Files\Apache Software Foundation\Apache2.2\bin> openssl.exe genrsa -des3 1024 > ..\conf\server.key
    Loading 'screen' into random state - done
    Generating RSA private key, 1024 bit long modulus
    ............++++++
    ..................++++++
    e is 65537 (0x10001)
    Enter pass phrase:
    Verifying - Enter pass phrase:

    C:\Program Files\Apache Software Foundation\Apache2.2\bin>


  5. CSRの作成
    <Apache導入先>\conf\server.csr を作成します。

    C:\Program Files\Apache Software Foundation\Apache2.2\bin> openssl.exe req -config ..\conf\openSSL.cnf -new -key ..\conf\server.key > ..\conf\server.csr
    Enter pass phrase for ..\conf\server.key:
    Loading 'screen' into random state - done
    You are about to be asked to enter information that will be incorporated
    into your certificate request.
    What you are about to enter is what is called a Distinguished Name or a DN.
    There are quite a few fields but you can leave some blank
    For some fields there will be a default value,
    If you enter '.', the field will be left blank.
    -----
    Country Name (2 letter code) [AU]:JP
    State or Province Name (full name) [Some-State]:Fukuoka
    Locality Name (eg, city) []:Fukuoka
    Organization Name (eg, company) [Internet Widgits Pty Ltd]:rtaki
    Organizational Unit Name (eg, section) []:rtaki
    Common Name (eg, YOUR name) []:rtaki
    Email Address []:hoge@123.abc.co.jp

    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []:riverfall
    An optional company name []:rtaki

    C:\Program Files\Apache Software Foundation\Apache2.2\bin>


  6. サーバー証明書の作成
    <Apache導入先>\conf\server.crt を作成します。

    C:\Program Files\Apache Software Foundation\Apache2.2\bin> openssl.exe x509 -in ..\conf\server.csr -days 1000 -req -signkey ..\conf\server.key > ..\conf\server.crt
    Loading 'screen' into random state - done
    Signature ok
    subject=/C=JP/ST=Fukuoka/L=Fukuoka/O=rtaki/OU=rtaki/CN=rtaki/emailAddress=hoge@123.abc.co.jp
    Getting Private key
    Enter pass phrase for ..\conf\server.key:

    C:\Program Files\Apache Software Foundation\Apache2.2\bin>


  7. httpd.conf の編集
    SSLモジュールを有効化するために、httpd.conf にて、以下の行を有効化します。

      LoadModule ssl_module modules/mod_ssl.so


    さらに、SSL設定ファイル(以下の行)を有効化します。

      Include conf/extra/httpd-ssl.conf

    <Apache導入先>\conf\extra\httpd-ssl.conf は編集不要です。
    念のため、SSLCertificateFile と SSLCertificateKeyFile が、
    項番 4. と 6. で作成したファイルになっていることを確認しておきます。

    SSLCertificateFile "C:/Program Files/Apache Software Foundation/Apache2.2/conf/server.crt"
    SSLCertificateKeyFile "C:/Program Files/Apache Software Foundation/Apache2.2/conf/server.key"


  8. Apache 起動エラーの回避
    <Apache導入先>\conf\extra\server.key を server.key_bk にリネームし、以下を実行します。

    C:\Program Files\Apache Software Foundation\Apache2.2\bin> openssl.exe rsa -in ..\conf\server.key_bk -out ..\conf\server.key
    Enter pass phrase for ..\conf\server.key_bk:
    writing RSA key

    C:\Program Files\Apache Software Foundation\Apache2.2\bin>

    これを実行しないと Apache が起動に失敗し、<Apache導入先>\log\error.log に以下のエラーが記録されます。

    SSLPassPhraseDialog builtin is not supported on Win32 (key file C:/Program Files/Apache Software Foundation/Apache2.2/conf/server.key)


  9. 設定の反映
    httpd.conf の修正の修正を反映するために、Apache サービスを再起動します。
    「コントロールパネル」→「管理ツール」→「サービス」より、Apache 2.2 を再起動します。


  10. 接続確認
    ブラウザを起動し、https://localhost/ として、It works! と表示されること確認します。

    その際に、証明書に関する警告が表示されますが、無視して、
    「このサイトの閲覧を続行する (推奨されません)。」を選択します。(IE8の場合)

    IE8 以外でも同様の画面が表示されるので、無視しましょう。
    オレオレ証明書(自分で作った証明書)なので仕方がないです。



subclipse で最初に接続する際も、以下のように同じような警告が表示されます。
Error validating server certificate for https://xxx~
- Unkown certificate issuer
「永久に承諾」としておきましょう。



以上、https版Apacheの完成です。ヾ(〃^∇^)ノわぁい♪

2010年10月18日月曜日

proxy経由だと自宅subversionに接続できないことも

自宅に用意した subversion ですが、
とある外部から接続しようとした場合、proxy 経由だと接続できませんでした。
※ 2010.10.26 追記。https化することで proxy 経由でも接続できるようになりました。
    詳細は、こちら を参照下さい。

subclipse で接続しようとすると以下のようなエラーメッセージが出力されます。

RA layer request failed
svn: OPTIONS of 'http://www.xxx.yyy.zzz/svn/test_proj': 200 OK (http://www.xxx.yyy.zzz)

subversion の FAQ (http://subversion.apache.org/faq.html#proxy) を見ると、
以下の2つを確認せよとのことです。
  1. "%APPDATA%\Subversion\servers ファイルで Proxy 接続先を設定する。

  2. Proxy サーバーで、以下のHTTPメソッドが使用可であることを確認する。
    PROPFIND, REPORT, MERGE, MKACTIVITY, CHECKOUT。

1. を実施した上で、上記エラー・メッセージだったので、
おそらく、Proxy サーバーの設定の問題だと思っています。

それにしても、Eclipse 自体にも Proxy 設定があるので、
subclipse がそちらを使用せず、独自に設定を持っているのは、ちょっと不思議な感じです。。

Proxy サーバーの設定は、変更することも、確認することもできないので、
あきらめることにしました (ノ_・。)

proxy 経由だと、うまくいかないことがあるので要注意です。


2010/10/19追記。

ちなみに、コマンド・プロンプトから、
telnet コマンドで、Proxy が許可する HTTP メソッドを確認できます。

確かに、proxy サーバーから、405 で弾かれています。

青字が入力部分です。

> telnet [proxyサーバー] [proxyポート番号]

OPTIONS http://www.xxx.yyy.zzz/svn/test_proj/ HTTP/1.1
host:www.xxx.yyy.zzz
Authorization: Basic dXNlcjpwYXNzd29yZA==


HTTP/1.1 405 Method Not Allowed.
Allow: HEAD, GET, POST, CONNECT
Cache-Control: no-cache
Pragma: no-cache
Expires: Tue, 19 Oct 2010 09:34:33 GMT
Last-Modified: Tue, 19 Oct 2010 09:34:33 GMT
Content-Length: 1469
Content-Type: text/html
Accept-Ranges: bytes
Connection: close
Date: Tue, 19 Oct 2010 09:34:33 GMT
Server: proxy-server-xyz

~ボディ部省略~


Basic 認証時は、リクエスト・ヘッダーに Authorization を含めます。
値は ユーザー名:パスワード (間にコロン)を Base64 に変換します。

Base64の変換は以下のサイトを使用させて頂きました。

  Base64変換 - http://www.ahref.org/app/base64/base64.cgi

上記例では、user:password を Base64 に変換して、dXNlcjpwYXNzd29yZA== を指定しています。

2010年10月17日日曜日

グローバルIPアドレスを確認し、変更あればメールで通知

Subversion を外部から接続しようとした場合、
固定グローバルIPアドレスを使用しているわけではないので、
変更されてしまうと困ったことになります。

ちなみに、フレッツ光を自宅では使用しているのですが、
グローバルIPアドレスを固定にするには、タダではありませんし、
今のところ、お金を払うほど必要としているわけでもありません。

まあ、そうそう変わることはないと思うのですが、
変わったら、そのことをメールで通知させることはできないか?
ということを考えてみました。

メールで教えてくれれば、その都度、Subversion の接続を変えればいいと
思うわけです。


幸いなことに、内部(自宅内のPC)から接続すれば、グローバルIPアドレスが
何なのか教えてくれるWEBサイトがあります。

ということは、そのようなWEBサイトにアクセスして、
IPアドレスを抽出して、前回確認時と比較して、不一致だったらメール送信、
という処理を作成すればよいわけです。

んでもって、それをタスクで定期的に実行させれば、望み通りとなります。


以上の考えを元に、PowerShell スクリプトを作成してみました。

PowerShell は今回が初めてだったので、結構苦労しました・・・


グローバルIPアドレスを確認するWEBサイトは、以下を使用させて頂くことにしています。

自分のグローバルIPアドレス確認ツール、「IP君」


HTMLのBODY部に依存しているので、
確認に使用するWEBサイトが変われば、PowerShell スクリプト修正が必要です・・・

もし、ご利用される方がいらっしゃいましたら、
「# メール送信アドレス」の $smtp (メール・サーバー)、$to (メール送信先アドレス)、
$from (メール送信元アドレス) を修正してやってください。

get_GlobalIP.ps1
# 接続先URL
$url = "http://www.axisnetworks.biz/tools/gip/"
Write-Output "接続先: $url"

# メール送信先アドレス
$smtp = "xxxxx.ne.jp"
$to   = "xxxx@yyyyy.com"
$from = "xxxx@xxxxx.ne.jp"


# グローバルIPアドレス保存用ファイル
$path = Split-Path $MyInvocation.MyCommand.Path -Parent
$file = Join-Path $path get_GlobalIP.txt

# ファイル存在確認&前回確認時のIPアドレス読込み
If (Test-Path $file) {
  $old_address = Get-Content $file
} else {
  $old_address = "<記録なし>"
}
Write-Output "前回確認済みのグローバルIPアドレス: $old_address"


# GETリクエスト実行
$req = [System.Net.WebRequest]::Create($url)
$req.Method=”GET”
$resp=$req.GetResponse()

# レスポンスの読込み&確認
$encode = $resp.ContentEncoding
$sr = New-Object IO.StreamReader($resp.GetResponseStream(), $resp.ContentEncoding)

while ( ($line = $sr.ReadLine()) -ne $null ) {
  $line = $sr.ReadLine()
  if ($line -like '*<p class="style4" name="ip">*') {
    if ($line -match "(?<address>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)") {
      $new_address = $matches.address
      if ($old_address -eq $new_address) {
        Write-Output "IPアドレス変更なし: $old_address == $new_address"
      } else {
        Write-Output "IPアドレスが変更: $old_address -> $new_address"
        # ファイル上書き
        Write-Output "ファイル更新: $file"
        $matches.address | Out-File -FilePath $file
        
        # メール送信
        Write-Output "メール送信: $to"
        $mail = New-Object Net.Mail.MailMessage($from, $to)
        $mail.Subject = "グローバルIPアドレス変更"
        $mail.Body = "IPアドレスが変更: $old_address -> $new_address"
        $mail.SubjectEncoding = [Text.Encoding]::GetEncoding("ISO-2022-JP")
        $mail.BodyEncoding = [Text.Encoding]::GetEncoding("ISO-2022-JP")
        $sc = New-Object Net.Mail.SmtpClient($smtp)
        $sc.Send($mail)
        $mail.Dispose()
      }
    }
  }
}

$sr.Close()
$resp.Close()


実行は、コマンド・プロンプトで以下のように実行します。

> powershell .\get_GlobalIP.ps1


最後に、powershell.exe、get_GlobalIP.ps1 ともにフルパスを指定して、
タスク登録すれば完成です。(= ̄▽ ̄=)V


ちなみに、これまでに(自分のように)PowerShell を実行したことがなければ、
以下のようなエラーが表示されるかもしれません。

スクリプトの実行がシステムで無効になっているため、ファイル C:\(配置場所)\get_GlobalIP.ps1 を読み込めません。詳細については、「get-help about_signing」と入力してヘルプを参照してください。


上記エラーが出た場合、PowerShell の実行ポリシーを変更する必要があります。
管理者で起動したコマンド・プロンプトにて、以下を実行します。

> powershell Set-ExecutionPolicy RemoteSigned

2010年10月16日土曜日

Subversion & Apache & subclipse の導入

複数の環境でソースの同期を行うことが面倒になってきたので、
Subversion を導入してみることにしました。

環境は Windows Vista Ultimate SP2 32bit と、
Eclipse Galileo 3.5.2 です。

導入設定は、以下の3つ行いました。

  • Subversion - バージョン 1.6.13
    バージョン管理を行うソフトウェア。
  • Apache - バージョン 2.2.16
    subversion を HTTP 経由でアクセスするために使用。
  • subclipse - バージョン 1.6.14
    Eclipse 用のプラグイン。
    Apache 経由で subversion にアクセスさせる。


以下が導入手順です。
  1. Subversion 及び Apache のダウンロード

    Subversion は以下のサイトより、Windows 版のバイナリ・パッケージをダウンロードします。

    http://subversion.apache.org/packages.html

    Windows 版のパッケージは、いくつかあるようですが、
    以下を導入することにしました。

     Win32Svn (32-bit client, server and bindings, MSI and ZIPs; maintained by David Darj)

    上記サイトより、Setup-Subversion-1.6.13.msi をダウンロードしました。


    Apache は以下のサイトより、
    httpd-2.2.16-win32-x86-no_ssl.msi をダウンロードしました。

     http://httpd.apache.org/download.cgi


  2. Subversion のインストール
    Setup-Subversion-1.6.13.msi を実行します。
  Next を押す。

  Next を押す。

  インストールするフォルダを指定。

  Install を押す。

  インストールが開始される。

  完了すると以下のような画面が表示されるので、Finish を押す。


  以上で、subversion のインストールは完了です。


  1. レポジトリ作成
    インストールが完了すると、自動的に環境変数 Path に、
    C:\Program Files\Subversion\bin が追記されます。

    コマンド・プロンプトを起動し、以下のコマンドを実行して、レポジトリを作成します。


    C:\>mkdir C:\svn

    C:\>svnadmin create C:\svn\test_project

    C:\>svn mkdir file://localhost/C:/svn/test_project/trunk -m "trunk作成"

    リビジョン 1 をコミットしました。

    C:\>svn mkdir file://localhost/C:/svn/test_project/tags -m "tags作成"

    リビジョン 2 をコミットしました。

    C:\>svn mkdir file://localhost/C:/svn/test_project/branchs -m "branchs作成"

    リビジョン 3 をコミットしました。

    C:\>

  2. Apache のインストール

    httpd-2.2.16-win32-x86-no_ssl.msi を実行します。
  Next を押す。

  accept を選択し、Next を押す。

  Next を押す。

  ドメイン名、サーバー名を localhost としておきます。
  メールアドレスも入力します。
  Apache をサービスとして稼動させるようにします。

  Next を押す。

  Next を押す。

  Install を押す。

  インストールが開始されます。

  インストールが正常に完了したら Finish を押す。


  以上で、Apache のインストールは完了です。


  1. Apache 接続確認
    ブラウザを起動し、http://localhost/ として、It works! と表示されること確認します。


  2. モジュールのコピー
    C:\Program Files\Subversion\bin\ にある mod_authz_svn.so と mod_dav_svn.soを、
    C:\Program Files\Apache Software Foundation\Apache2.2\modules\ にコピーします。


  3. httpd.conf の修正
    修正前の httpd.conf は念のためコピーして保存しておきます。
    メモ帳を管理者として実行します。

    まずは、以下の2行の先頭にある # を削除し、コメントアウトから有効にします。

      LoadModule dav_svn_module modules/mod_dav_svn.so
      LoadModule authz_svn_module modules/mod_authz_svn.so

    次に、以下の2行を新規に追加します。
    追加する場所は、LoadModule の記述のかたまりの下のあたりで構いません。

      LoadModule dav_svn_module modules/mod_dav_svn.so
      LoadModule authz_svn_module modules/mod_authz_svn.so

    最後に、httpd.conf ファイルの最後に、以下の行を追加します。

      <Location /svn>
        DAV svn
        SVNParentPath "C:\svn"

      </Location>


  4. Apache 再起動
    httpd.conf の修正の修正を反映するために、Apache サービスを再起動します。
    「コントロールパネル」→「管理ツール」→「サービス」より、Apache 2.2 を再起動します。


  5. ブラウザより表示されることを確認
    ブラウザを起動し、以下の URL を入力。

      http://localhost/svn/test_project/

    以下のように、test_project が表示されることを確認します。


  1. Eclipse に Subclipse ブラグインを導入
    Eclipse を起動し、「Help」→「Install New Software...」を選択。
    Add を押し、サイトを追加します。

    Name は subclipse とし、
    Location は http://subclipse.tigris.org/update_1.6.x とします。

  Core SVNKit Library、Optional JNA Library (recommended)、Subclipse の
  3 つともにチェックを入れ、Next を押します。

  Next を押します。

  I accept ~ にチェックを入れ、Finish を押します。

  インストールが開始されます。
  途中、認証に関する警告が表示されますが、OK と答えておきます。
  インストールが完了すると、Eclipse の再起動を促されるので、再起動します。

  1. Subclipse を使用してみる
    「Window」→「Open Perspective」→「Other...」より、
    「SVN リポジトリー・エクスプローラー」を開きます。

  SVN Repositories が表示されるので、右クリックから、
  「新規」→「リポジトリー・ロケーション」を選択します。

  項番 9. でブラウザにて接続確認した URL を入力します。
  http://localhost/svn/test_project/

  無事、レポジトリーに接続できました。


  1. 既存プロジェクトのチェックイン
    「Package Explorer」より既存プロジェクトを右クリックし、
    「Team」→「Share Project..,」より「SVN」を選択することで行えます。


以上、導入までの手順を整理しました。

う~ん、これでソース管理が楽になればいいんだけど。


2010年10月14日木曜日

SDカードの中身を ListView を使って表示&選択 (拡張子フィルター付)

こんな感じのサンプル・アプリを作ってみました。

起動直後
起動直後に、/sdcard 内のディレクトリ及びファイル(拡張子が.xmlのみ)を表示します。

上記画面で、dir1 を選択すると、以下のように、/sdcard/dir1 内が表示されます

1つ下に移動し、ファイル選択
test1_1.xml を選択すると、EditText にフルパス名が自動セットされ、
最下段にある OK ボタンが Enable になります。

/sdcard 直下では表示されませんが、
/sdcard/dir1 など、/sdcard のサブ・ディレクトリでは上位ディレクトリに戻るために、
"...(上へ)" を表示&選択できるようにしています。

ちなみに、SDカードへの書き込みを行う際は、AndroidManifest.xml に、
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
を記述する必要があります。


以下はソースです。

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

<TextView
    android:id="@+id/current_dir"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />

<ListView
    android:id="@+id/select_file"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_weight="2"
/>

<TextView
    android:text="ファイル名:"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />

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

<Button
    android:id="@+id/ok_button"
    android:text="OK"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical"
    />

</LinearLayout>



SdCardDisplayActivity.java
package jp.kochi;

import java.io.File;
import java.io.FileFilter;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class SdCardDisplayActivity extends Activity implements OnItemClickListener {

    private ListView mListView;
    private EditText mEditText;
    private TextView mCurDirTextView;
    private File mFile;
    private String mWarning;
    private String mGoUp;
    private File mSDdir;
    private Button mButton;

      @Override
      public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

          setContentView(R.layout.main);

          mCurDirTextView = (TextView) findViewById(R.id.current_dir);
          mButton = (Button) findViewById(R.id.ok_button);
          mButton.setEnabled(false);

      mWarning = "警告";
      mGoUp    = ".. (上へ)";

          //final File SDdir
          mSDdir = Environment.getExternalStorageDirectory();
      Log.v("DEBUG", "getExternalStorageDirectory() = " + mSDdir.getPath());

          String SDstatus = Environment.getExternalStorageState();
      Log.v("DEBUG", "getExternalStorageState() = " + SDstatus);

      // SDカードがセットされているか確認
      if (SDstatus.equals(Environment.MEDIA_MOUNTED) == false &&
          SDstatus.equals(Environment.MEDIA_MOUNTED_READ_ONLY) == false) {

        AlertDialog.Builder builder = new AlertDialog.Builder(SdCardDisplayActivity.this);

        builder.setTitle(mWarning);
        builder.setMessage("SDカードがセットされていません。");
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener () {

          @Override
          public void onClick(DialogInterface dialog, int which) {
            Log.v("DEBUG", "alerDialog - OnClickListener()");
            SdCardDisplayActivity.this.finish();
          }
        });

        builder.show();

        Log.v("DEBUG", "finish: " + this.getClass().getName());

      }

          mListView = (ListView) findViewById(R.id.select_file);
          mListView.setOnItemClickListener(this);
          //list.setAdapter(new ListAdapter)
          updateList(mSDdir);

          Button select = (Button) findViewById(R.id.ok_button);
          select.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
          Toast.makeText(SdCardDisplayActivity.this, mEditText.getText(), Toast.LENGTH_LONG).show();
          // --------------------------
          // ファイル読込み等の処理を行う
          // --------------------------
        }
      });


          mEditText = (EditText) findViewById(R.id.file_text);
      }

      final private FileFilter filter = new FileFilter() {
      @Override
      public boolean accept(File pathname) {
        if (pathname.isDirectory() == true) {
          return true;
        } else if(pathname.getName().endsWith(".xml") == true){
          return true;
        }
        return false;
      }
    };

      private void updateList(File dir){

        // EditText に選択されたファイル名/フォルダ名をセット
        mCurDirTextView.setText(dir.getAbsolutePath());

        SimpleAdapter adapter = new SimpleAdapter(this, getData(dir.listFiles(filter), dir.getParentFile()),
                  android.R.layout.simple_list_item_1, new String[] { "name" },
                  new int[] { android.R.id.text1 });

        mListView.setAdapter(adapter);
      }

    private List getData(File[] files, File parent) {

      Map<String, Object> temp;
      List<Map> myData = new ArrayList<Map>();

      if (parent != null && mSDdir.getParent().equals(parent.getAbsolutePath()) == false) {
        temp = new HashMap<String, Object>();
        temp.put("name", mGoUp);
        temp.put("file", parent);
        myData.add(temp);
      }

      if (files != null) {
        for(File file : files){
          temp = new HashMap<String, Object>();
          if (file.isDirectory()) {
            temp.put("name", file.getName() + "/");
          } else {
            temp.put("name", file.getName());
          }
          temp.put("file", file);
          myData.add(temp);
        }
      }

      Collections.sort(myData, sDisplayNameComparator);

      return myData;
    }

    private final static Comparator<Map> sDisplayNameComparator = new Comparator<Map>() {
      private final Collator collator = Collator.getInstance();

      public int compare(Map map1, Map map2) {
        return collator.compare(map1.get("name"), map2.get("name"));
      }
    };

    @Override
    public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
      Map map = (Map) arg0.getItemAtPosition(arg2);
      mFile = (File) map.get("file");

      mEditText.setText(mFile.getAbsolutePath());
      if (mFile.isDirectory()) {
        mButton.setEnabled(false);
        updateList(mFile);
      } else {
          // ファイルだったら、ボタンを押せるようにする
          mButton.setEnabled(true);
      }


    }
  } 

2010年10月12日火曜日

android で SAX 使用時の characters() の動きについて

android で XML ファイルを読み込み際に、DOM より SAX が軽量とのことなので、
SAX について挙動を確認するためのサンプル・プログラムを作成してみました。

特に、これまで SAX なんて使ったことがなかった自分には、characters() の動きが、
想定外だったので、確認した内容を記録しておきます。


作成したサンプルですが、画面は以下のように3つの要素から構成されています。


上段の EditText で入力となる XML ファイルを指定し、
「ファイル読み込む」ボタンを押すと処理を開始し、
下段の EditText で出力ファイル名 (入力ファイル名 + .txt ) を表示しています。


以下はソースです。

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<EditText
 android:id="@+id/edittext"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
<Button
 android:id="@+id/button"
 android:text="ファイル読み込む"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    />
<TextView
 android:text="出力ファイル名:"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    />
<EditText
 android:id="@+id/output"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:editable="false"
    />
</LinearLayout> 


TestSaxActivity.java
package jp.kochi;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class TestSaxActivity extends Activity {

 private EditText mEdit;
 private Button mButton;
 private EditText mOutput;
 private BufferedWriter mFileOutput;


    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mEdit   = (EditText) findViewById(R.id.edittext);
        mButton = (Button) findViewById(R.id.button);
        mOutput = (EditText) findViewById(R.id.output);

        mEdit.setText(getFilesDir() + "/");

        mButton.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View v) {
    // ファイル名を取得
    CharSequence name = mEdit.getText();
    File file = new File(name.toString());

    // 出力用 のファイル名をセット
    String output = file.getName() + ".txt";
    mOutput.setText(output);
    try {
     // 出力ファイルオープン
     FileOutputStream out = openFileOutput(output, MODE_PRIVATE);
     mFileOutput = new BufferedWriter(new OutputStreamWriter(out));

     // SAXのよるparse開始
     parseSax(file);

    } catch (FileNotFoundException e) {
     e.printStackTrace();
    } finally {
     if (mFileOutput != null) {
      try {
       mFileOutput.close();
      } catch (IOException e) {
       e.printStackTrace();
      }
     }
    }
   }
  });

    }

    private void parseSax(File file) {

     SAXParserFactory factory = SAXParserFactory.newInstance();
  try {
   SAXParser parser = factory.newSAXParser();
   TestHandler handler = new TestHandler();
   parser.parse(file, handler);
  } catch (ParserConfigurationException e) {
   e.printStackTrace();
  } catch (SAXException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
    }

    private void writeToFile(String text) {

     try {
      mFileOutput.append(text);
  } catch (IOException e) {
   e.printStackTrace();
  }
    }

 private class TestHandler extends DefaultHandler {

  @Override
  public void startDocument() throws SAXException {
   super.startDocument();
   writeToFile("startDocument()\n");
  }

  @Override
  public void startElement(String uri, String localName, String qName,
    Attributes attributes) throws SAXException {
   super.startElement(uri, localName, qName, attributes);
   writeToFile("startElement() - uri            = " + uri + "\n");
   writeToFile("               - localName      = " + localName + "\n");
   for(int i=0; i<attributes.getLength(); i++) {
    writeToFile("               - attr.localName = " + attributes.getLocalName(i) + "\n");
    writeToFile("               - attr.Value     = " + attributes.getValue(i) + "\n");
   }
  }

  @Override
  public void characters(char[] ch, int start, int length)
    throws SAXException {
   super.characters(ch, start, length);

   String value = new String(ch, start, length);
   writeToFile("characters() - ch = \"" + value + "\"\n");
  }

  @Override
  public void endElement(String uri, String localName, String qName)
    throws SAXException {
   super.endElement(uri, localName, qName);
   writeToFile("endElement() - uri       = " + uri + "\n");
   writeToFile("             - localName = " + localName + "\n");
  }

  @Override
  public void endDocument() throws SAXException {
   super.endDocument();
   writeToFile("endDocument()\n");
  }

 }


}


実行例を以下のようになります。


test.xml (入力ファイル)
<?xml version="1.0" encoding="UTF-8" ?>
<test xmlns="http://www.hogehoge.com/hoge/2.0">
<dir id="d1">
  previous <text>Sample text1</text> after
</dir>
</test>

test.xml.txt (出力ファイル)
startDocument()
startElement() - uri            = http://www.hogehoge.com/hoge/2.0
               - localName      = test
characters() - ch = "
"
startElement() - uri            = http://www.hogehoge.com/hoge/2.0
               - localName      = dir
               - attr.localName = id
               - attr.Value     = d1
characters() - ch = "
"
characters() - ch = "  previous "
startElement() - uri            = http://www.hogehoge.com/hoge/2.0
               - localName      = text
characters() - ch = "Sample text1"
endElement() - uri       = http://www.hogehoge.com/hoge/2.0
             - localName = text
characters() - ch = " after"
characters() - ch = "
"
endElement() - uri       = http://www.hogehoge.com/hoge/2.0
             - localName = dir
characters() - ch = "
"
endElement() - uri       = http://www.hogehoge.com/hoge/2.0
             - localName = test
endDocument()

test.xml.txt の内容を見ると分かるのですが、
characters() では、改行や空白が、そのまま拾われるようになっています。

しかも面倒?なことに、1回で全てを取得するわけではなく、
改行の1バイトのみを characters() が返している箇所があります。

これまで SAX  を使ったことがある人には当然なのかもしれませんが、
使ったことがない自分には、ちょっと意外な動きでした。

この点は、要注意かと。

2010年10月7日木曜日

adb logcat (複数デバイスが存在する場合)

USB で実機が接続され、さらにエミュレーターが起動されている状態で、

adb logcat を実行すると、以下のエラーが繰り返し表示されます。


 -waiting for device -
 error: more than one device and emulator


これを回避するには、-s オプションにて、デバイスを指定する必要があります。

シリアル番号は adb devices で確認できます。


以下は実行例です。


 C:\> adb devices      
      … (1)
 List of devices attached
 HT97ALF0xxxx device
 emulator-5554 device


 C:\> adb -s emulator-5554 logcat  … (2)


(1) 実機1つ、エミュレータが1つ起動されている状態です

(2) エミュレーターに対して、adb logcat を実行しています。


毎回、シリアル番号を指定するのは面倒なので、バッチを作成してみました。

logcat_cmd.bat 等、適当な名前で保存して、実行するだけで使用できます。


デバイスが1つしかないときは、無条件に adb logcat を実行し、

デバイスが2つ以上あるときは、リストから選択させるようにして、

adb -s <シリアル番号> logcat を実行するようにしています。


adb logcat を状況に合わせて実行するバッチ
@echo off

REM 前提条件
REM コマンド・プロンプトが以下の2点を満たす状態で開始されること。
REM 「オプション」タブで「現在のコードページが『932』
REM 「フォント」タブで「フォント」が『MS ゴシック』

setlocal enabledelayedexpansion

set CMD=

set /A num = 0
REM xxxx device となっている行のみ表示
for /F "tokens=1-2" %%a in ('adb devices ^| findstr device ^| findstr /V "List of devices attached"') do (
  set /A num = !num! + 1
  echo !num!. %%a
)

REM デバイスなし
if %num% EQU 0 (
  echo デバイスが存在しません。
  pause
  goto :STOP
) else if %num% EQU 1 (
  REM デバイス1つ
  set CMD=adb logcat
  chcp 65001 & !CMD!
  goto :STOP
)

REM デバイス2つ以上
set /p select_num=adb logcat を実行するデバイスを選択して下さい(1-%num%)
set /A num = 0
for /F "tokens=1-2" %%a in ('adb devices ^| findstr [a-zA-Z] ^| findstr /V "^List of devices"') do (
  set /A num = !num! + 1
  if !num! EQU %select_num% (
  set CMD=adb -s %%a logcat
  chcp 65001 & !CMD!
  )
)

:STOP

endlocal

exit /b 0

2010年10月5日火曜日

adb logcat で UTF-8 を文字化けせずに見る方法

先日、開発していて気がついたのですが、

eclipse 上の Logcat では、UTF-8 が文字化けして表示されます。



う~ん、と思っていたところ、コマンド・プロンプトで、

adb logcat とコマンドを実行する方法があることも分かりましたが、

これでも文字化けしてしまいます。

出力例
 V/AllMapInfo(  214): name        = 2番札所ã�¸ã�®é�“
 V/AllMapInfo(  214): route.size  = 14
 V/AllMapInfo(  214): rootnum     = 2
 V/AllMapInfo(  214): name        = 3番札所ã�¸ã�®é�“
 V/AllMapInfo(  214): route.size  = 45
 V/AllMapInfo(  214): rootnum     = 3
 V/AllMapInfo(  214): name        = 4番札所ã�¸ã�®é�“


が! しかし! コマンドを実行する方法では、

一手間加えてあげれば、きちんと表示されることが分かりました ヾ(o・д・)ノ

ググって調べると、レジストリの TrueTypeFont を新規追加しないと
いけなさそうなんですが、自分の Vista の環境で試したところ、
やらなくても、うまくいきました。

  1. レジストリの設定

    2010/10/6 訂正。再度、試行錯誤してみた結果、これは実施しなくてもよさそうです。
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont にて、名前 0. (ゼロとドット)、種類 REG_SZ、値 MS Gothic を設定。

  1. OS再起動
    2010/10/6 訂正。1. のレジストリ変更がなければ、これも実施不要です。

  2. コマンド・プロンプト起動&事前確認
    コマンド・プロンプトを起動し、タイトル・バーを右クリックからプロパティを選択し、
    フォントが MS ゴシック になっていることを確認。
    なってなければ、MS ゴシック に変更する。

    さらに、コマンド・プロンプト上で、chcp を実行し、
    現在のコード ページが 932 (Shift-JIS) であることを確認。
    932 でない場合は、chcp 932 で変更する。
  1. 文字コード UTF8 に変更し、adb logcat を実行

    chcp 65001 を実行し、コードページを UTF8 に変更。
    adb logcat を実行すれば、UTF-8 が表示されるようになります。

    実行例
     > chcp 65001
     > adb logcat
     (省略)
     V/DEBUG ( 213): Map name = お遍路
     D/dalvikvm( 213): GC freed 6302 objects / 327536 bytes in 115ms
     V/AllMapInfo( 213): mapid = 0
     V/AllMapInfo( 213): name = お遍路
     (省略)

無事、UTF-8 が表示されるようになりました。