トップ 最新 追記

Ussy Diary


2010-01-05

HTTP Client 4.0 でリダイレクトしないようにする

Apache Commons HTTP Client のバージョン 3.x には HttpMethod#setFollowRedirects があったのですが、 4.x から綺麗に取り除かれています。

4.0 では以下のように HttpParams に設定してあげるとリダイレクトしなくなりました。

HttpParams params = new BasicHttpParams();
params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);

ログイン画面にリダイレクトされたときに、リダイレクト先のコンテンツを取得されるのを抑止し、 302 コードが欲しかったのです。

ドキュメント

Chapter 5. HTTP client service

Tags: Java

2010-01-09

WebView と assets 連携

Android には WebKit ブラウザが内蔵されていて、 SDK にはそのエンジンを利用した WebView というコンポーネントが提供されています。

使い方は layout に WebView コンポーネントを配置して Activity から WebView インスタンスを取得し、以下のメソッドを呼び出すことで描画が行えます。

  1. WebView#loadUrl
  2. WebView#loadData
  3. WebView#loadDataWithBaseURL

loadUrl は URL を指定し、 loadData は html 文字列から描画を行うものですが、今回は loadDataWithBaseURL を使いました。というのもローカルに css ファイルを作成して、 html から参照させたかったからです。配置

[追記]

loadUrl でも assets 連携が行えました。

webView.loadUrl("file:///android_asset/index.html");

[/追記]

まずリソースファイルとなる html ファイルおよび css ファイルを作成します。アプリケーションのルートディレクトリに assets ディレクトリを作成し、その直下に対象のファイルを置きます。

あとやることは Activity から WebView インスタンスを取得し、メソッドで呼び出すだけです。

webView.loadDataWithBaseURL("file:///android_asset/", html, "text/html", "utf-8", null);

第一引数に file:///android_asset を指定することで、アプリケーションルートに作成した assets ディレクトリにマッピングが行われます。最後の引数は baseURL の取得がうまく行えなかったときのエラーページを指定するらしいです。今回は内部ファイルにアクセスするので null にしました。

これで assets ディレクトリを基底ディレクトリとし、 html から assets ディレクトリの中にアクセスできるようになりました。

index.html

<link rel="stylesheet" type="text/css" href="./default.css" />

それから assets ディレクトリの資源に直接アクセスしたい場合は、 Context#getAssets (Activity) から AssetManager を取得し、 AssetManager#open メソッドから可能です。

今回は html ファイルをローカルに置いて、テンプレートの中身を書き換える形にしてみました。以下コードです。

Java

public class WebViewActivity extends Activity {

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

        String template = getTemplateHtml("index.html");
        String html = template.replaceAll("%s", "<h1>Hello!</h1>");

        WebView webView = (WebView) findViewById(R.id.webView);
        webView.loadDataWithBaseURL("file:///android_asset/", html, "text/html", "utf-8", null);
    }

    private String getTemplateHtml(String path) {
        InputStream in = null;
        try {
            in = getAssets().open(path);
            byte[] buffer = IOUtils.read(in); // 自作ユーティリティ
            return new String(buffer, "utf-8");
         } catch (IOException e) {
             throw new RuntimeException(e);
         } finally {
             IOUtils.close(in);
         }
    }
}

HTML

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
          "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta http-equiv="content-script-type" content="text/javascript" />
    <meta http-equiv="content-style-type" content="text/css" />
    <link rel="stylesheet" type="text/css" href="./default.css" />
  </head>
  <body>%s</body>
</html>

CSS

body {
  margin: 0;
  padding: 0;
  background-color: black;
  color: white;
}

WebView は JavaScript との連携も用意されていて、色々できそうです。時間を取って追ってみたいコンポーネントでした。

Tags: Android

2010-01-10

AIDL インターフェース引数に Parcelable 実装クラスを指定する

Android で画面操作を行っているときに電話がかかってくると、処理が停止されてしまいます。しかし停止させず処理を継続させたい場合にはサービスという概念が用意されており、これを利用することで処理をバックエンドで続けてくれるとのこと。

実装方法はいくつかあるのですが、今回は AIDL ファイルに Java インターフェースを定義して、コールバックを受け取れるバインダという種類を選択してみました。なお AIDL ファイルに記述したインターフェースは ADT によって、インターフェースを実装したクラスが自動的に生成されます。

まず AIDL ファイルに定義するインターフェースの引数、戻り値に指定できるものとして以下の制約があるとのことです。

  • プリミティブ型
  • String, List, Map, CharSequence, AIDL ファイルに定義したインターフェース
  • Parcelable インターフェースを実装したクラス

新しく Parcelable インターフェースというものが出てきたのですが、すごく適当にいうと Serializable の Android 用みたいなものです。 Java の Serializable は冗長なので、 Android ではリモート通信間に使うものには Parcelable インターフェースを実装するルールのようです。

で、今回 Parcelable インターフェースを実装した ValueObject を渡そうとしたところ意外にハマってしまったのでメモしておきます。

example.dto.Hoge クラスが Parcelable インタフェースを実装しているものとします。(インターフェース実装は省略します)

Hoge クラスと同じ example.dto パッケージに Hoge.aidl ファイルを作成します。

Hoge.aidl

package example.dto;

parcelable Hoge;

ここでは Hoge クラスが parcelable であることを宣言します。

続いてコールバックを受け取るリスナを example.service パッケージに定義します。

HogeCallbackListener.aidl

package example.service;

import example.dto.Hoge;

interface HogeCallbackListener {

    void receive(in Hoge hoge);
}

ここでは先ほど Hoge.aidl で定義した Hoge クラスを import し、インターフェースの引数に in を指定しました。なお public をつけるとおこられます。これで利用するための準備は整いました。

この後に AndroidManifest ファイルに Service を定義したり、 Activity や Service にコードを記述したりと面倒な作業が待っています。

参考

http://developer.android.com/intl/ja/guide/developing/tools/aidl.html

Tags: Android

2010-01-17

Sequel でマイグレーション

Ruby にある軽量 ORM Sequel を簡単に触ったことはあるのですが、マイグレーションもできるということで試してみました。

インストール

$ sudo gem install rake
$ sudo gem install sequel
$ sudo gem install sqlite3-ruby

構成

+app_root/
 +Rakefile
 +db/
  +migrate/
   +001_migrate.rb
   +002_migrate.rb

001_migrate.rb

#!/usr/bin/env ruby

Class.new(Sequel::Migration) do
  def up
    create_table :users do
      primary_key :id
      varchar :name, :size => 32, :unique => true
      varchar :salt, :size => 32, :null => false
      varchar :hashed_password, :size => 64, :null => false
      varchar :mail_address, :size => 128, :null => false
      timestamp :created_at
      timestamp :updated_at
    end

    add_index :users, :name
  end

  def down
    drop_index :users, :name
    drop_table :users
  end

end

002_migrate.rb

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

Class.new(Sequel::Migration) do
  def up
    create_table :tags do
      primary_key :id
      varchar :name, :size => 32
    end

    from(:tags).insert(:name => 'あとでやる')
    from(:tags).insert(:name => '明日から本気出す')
  end

  def down
    drop_table :tags
  end

end

クラスを匿名で生成していますが、意味づけを行う場合は名前をきちんとつけたほうが良いかもしれません。

Rake は引数を渡すと ENV 変数に書き込まれるので、これでバージョンを指定できます。

Rakefile

#!/usr/bin/env ruby

require 'rubygems'
require 'rake'
require 'sequel'
require 'sequel/extensions/migration'

namespace :db do
  desc "migrate database"
  task :migrate do
    target = target.to_i if (target = ENV['target'])
    current = current.to_i if (current = ENV['current'])

    DB = Sequel.connect("sqlite://db/app.sqlite3")
    Sequel::Migrator.apply(DB, './db/migrate', target, current)
  end
end

最新版 (version 2) にする

$ rake db:migrate

バージョンを 1 に戻す

$ rake db:migrate target=3

わざわざ rake しなくても、コマンドから migrate できます。 -m でマイグレーションファイルを格納しているディレクトリを指定、 -M でバージョンを指定します。

$ sequel -m ./db/migrate sqlite://db/keyself.db -M 1

マイグレーションとは関係ありませんが、 model をつくる場合は最低限なものであればこれだけです。

user.rb

class User < Sequel::Model; end

users テーブルと関連づけられ、様々な操作が行えます。手軽にデータベースを操作したい場合に使っていきたいと思う ORM です。

Tags: ORM Ruby

2010-01-21

Sinatra(Rack) のセッションを無効にする

まずサーバーセッションを有効にするには起動ファイルである config.ru で use します。

use Rack::Session::Pool

引数にオプションをつけてセッションの有効期限を変えたりします。

あとはクライアントから呼び出されたアクション(コントローラー)で session_options に :drop オプションを渡してあげると削除されます。

get '/' do
  (session[:count] = (session[:count] || 0) + 1).to_s
end

get '/clear' do
  request.session_options[:drop] = true
end

http://localhost:4567/ をたたくとリクエストした数だけカウント数が画面に表示され、 /clear を叩いてから再度 / を叩くと、セッション ID が新たに割り当てられ 1 に戻ります。 :renew を渡すと新たなセッション ID を割り振りつつ、前の情報をマージしてくれます。

session.clear は単純に中身の Hash オブジェクトをクリアするだけで、セッション ID はそのままでした。


2010-01-23

Encoding::UndefinedConversionError

Ruby 1.9 上の Sequel で sqlite3 からマルチバイト文字列を含んだレコードを取得し to_json しようとすると

Encoding::UndefinedConversionError: "\xE2" from ASCII-8BIT to UTF-8

というエラーが出力されます。

String#force_encoding で、いちいち utf-8 しないといけないのかと思ったところ、きちんと Sequel の plugin にエンコーディングを変換できるものが提供されていました。

class User < Sequel::Model
  plugin :force_encoding, 'utf-8'
end
Tags: Ruby Sequel

Sequel で保存時にタイムスタンプをつける

カラム名を created_at, updated_at にしておくと、 save メソッドを呼び出したときに自動的に作成、更新時間を更新してくれるみたいです。カラム型は timestamp で確認しています。

さらに追加時にも更新時間を入れたい場合には timestamps プラグインを利用して、オプションを渡してあげるとよいです。

DB = Sequel.connect("sqlite://test.db")

class User < Sequel::Model
  plugin :timestamps, :update_on_create => true
end

DB.transaction do
  User.new(:name => 'aaa').save
  User.new(:name => 'bbb').save
end

既存システムで、すでに別のカラム名が使われている場合には下のオプションを渡してあげると変更できます。

plugin :timestamps, :create => 'created_on', :update => 'updated_on'

Module: Sequel::Plugins::Timestamps [Sequel: The Database Toolkit for Ruby]

モデルすべてに適用したい場合は Sequel::Model で定義してあげるとよいみたいです。

class Sequel::Model
   plugin :timestamps, :update_on_create => true, :create => 'created_on', :update => 'updated_on'
end

class User < Sequel::Model; end
Tags: Ruby Sequel

2010-01-27

Android 端末のディスプレイサイズを取得する

Activity#onCreate

WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Log.d("display", "w:" + display.getWidth());
Log.d("display", "h:" + display.getHeight());

実行結果

w:320
h:480

onCreate 時には、まだレイアウトの初期化が行われないため View の width/height からは 0 が返ってきます。調べてみると WindowManager を経由して取得した Display クラスから onCreate 時に取得できました。

こちらは View の onSizeChanged イベントから取得する方法です。

throw Life - Viewの幅と高さを取得する方法を考える

Tags: Android