Android 1.5でTabHostを使った場合、StackOverflowErrorが発生する

このエントリーをはてなブックマーク
Share
2010.07.30    android, java, 馬場   タグ: , , , , —    baba   

Androidで便利な、タブ表示。

タブ表示

タブ表示

これは、TabActivity, TabHost, TabSpecを使って簡単に実現できます。

src/MainActivity.java

package jp.bpsinc.android.tabtest;

import android.app.TabActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;

public class MainActivity extends TabActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    TabHost tabHost = getTabHost();
    LayoutInflater.from(this).inflate(R.layout.main, tabHost.getTabContentView(), true);

    //タブを作る
    TabSpec tab1 = tabHost.newTabSpec("tab1");
    tab1.setIndicator("tab1");
    tab1.setContent(R.id.tab1);

    TabSpec tab2 = tabHost.newTabSpec("tab2");
    tab2.setIndicator("tab2");
    tab2.setContent(R.id.tab2);

    tabHost.addTab(tab1);
    tabHost.addTab(tab2);
  }
}

res/layout/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">

  <TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="TAB1" android:id="@+id/tab1" />
  <TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="TAB2" android:id="@+id/tab2" />
</FrameLayout>

しかし、サンプルでなく複雑なアプリを作ると、Android 1.5 (SDK Version 3) の時だけ、StackOverflowErrorが発生するという現象に見舞われることがあります。

body : java.lang.StackOverflowError
at android.text.TextUtils#getChars:69
at android.graphics.Canvas#drawText:1278
at android.text.Layout#draw:337
at android.widget.TextView#onDraw:3921
at android.view.View#draw:5838
at android.view.ViewGroup#drawChild:1486
at android.view.ViewGroup#dispatchDraw:1228
at android.view.ViewGroup#drawChild:1484
at android.view.ViewGroup#dispatchDraw:1228
at android.view.ViewGroup#drawChild:1484
at android.view.ViewGroup#dispatchDraw:1228
at android.view.ViewGroup#drawChild:1484
at android.view.ViewGroup#dispatchDraw:1228
at android.widget.AbsListView#dispatchDraw:1319
at android.widget.ListView#dispatchDraw:2820
at android.view.View#draw:5944
….

こちらのGoogleGroupディスカッションでも触れられているようですが、Android 1.5では、タブの中に多重にネストしたコンテンツがあると、StackOverflowErrorが発生します。

手元のエミュレータで試したところ、12回ネストしたところで、エラーになりました。

<?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">

  <TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="TAB1" android:id="@+id/tab1" />

  <FrameLayout android:id="@+id/tab2" android:layout_width="fill_parent" android:layout_height="fill_parent">
    <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
      <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
        <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
          <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
            <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
              <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
                <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
                  <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
                    <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
                      <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
                        <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
                          <TextView android:text="TAB2" android:layout_width="wrap_content" android:layout_height="wrap_content" />
                        </LinearLayout>
                      </LinearLayout>
                    </LinearLayout>
                  </LinearLayout>
                </LinearLayout>
              </LinearLayout>
            </LinearLayout>
          </LinearLayout>
        </LinearLayout>
      </LinearLayout>
    </LinearLayout>
  </FrameLayout>
</FrameLayout>

XMLをパーツに分けて書いていると、うっかり12回を超えてしまうことがあるので、注意が必要です。
解決策としては、ネスト回数を減らすか、Android 1.5を対象から外すという後ろ向きなものが最適ですね。

AndroidにAdMob広告を配置する入門

このエントリーをはてなブックマーク
Share
2010.07.14    android, java, 馬場   タグ: , , , , —    baba   

インプレッション表示の収入がないとか、クリック単価が低いとか、なかなか広告が出ないとか、アプリ・Web開発者側からするとお金にしづらいAdMobではありますが、すごく使いやすいので、使っている方も多いと思います。

Androidに完全対応した(少ない手間で組み込める)数少ない広告システムということもあり、色々お世話になります。

今回は、AdMobの登録~Androidで広告を表示してみるまでの簡単な手順と、注意点です。

登録

http://jp.admob.com/から登録します。

アプリケーションを追加

ログインしたら上部タブから「マーケットプレイス」を選択します。
「キャンペーン」は広告主用のものなので、開発者は「サイト及びアプリケーション」を開きます。

「サイト・アプリケーションを追加」ボタンをクリックし、「Android」ボタンをクリックすると、アプリ情報入力画面になります。
ここに入力した内容に間違いがあっても、広告が表示されない、ということはありませんが、なるべく正直に入力しましょう。

SDKのダウンロード

アプリの登録を完了すると、SDKとマニュアルPDFがダウンロードできるようになります。
SDKはダウンロード・解凍して、適当な場所に置いておきます。同フォルダにAdMobSDKのリファレンス(JavaDoc)もあるので、是非参考にしてください。

これ以降の手順は、PDFに従うのが一番です!!

広告が表示されないとき

ということで、解説を途中で放棄して、「PDF通りにやったのに広告が表示されない」という場合のチェックポイントです。
基本的に、ManifestにパブリッシャーIDを入れて、attrs.xml、表示したいViewのXMLを書けばOKなのですが、以下の場合、広告が表示されません。

・ネットワーク接続できない
この場合、広告は取得できず、何も表示されません。

・表示領域が狭い
AdViewのサイズは、横幅320px以上である必要があります。それより小さいと、何も出てきません。

・ログに「no ads are available」と出る場合
広告がありません。AdMobは広告主に比べて開発者が多いのか、広告が見つからないことが良くあります。
この場合、Webの管理画面から「自社広告」を入れておけば、何も見つからないときに自社広告を表示することができます。

・単に遅い
Viewが表示されてから広告が表示されるまで、数秒かかるのが普通です。
上部に入れる場合は、48pxくらいの高さを確保しておいた方が、レイアウトが変わらなくて良いかもしれません。

動的にキーワードフィルタする

Web管理画面から、表示する広告のジャンル、キーワードフィルタ等を設定することができます。
しかし、アプリのコードから指定すれば、現在表示しているコンテンツにマッチした広告を表示することも可能です。

以下のようなコードで可能です。

AdView ad = (AdView)findViewById(R.id.ad);
ad.setKeywords("music");

とても多機能なAdMob、うまく使いこなしたいですね。

Androidの多言語対応strings.xmlを効率的に管理する

このエントリーをはてなブックマーク
Share

Androidは多言語対応が簡単で、

values/strings.xml
values-ja/string.xml

などの言語ファイルを作るだけで、システムロケールにあわせて自動的に多言語対応されます。

しかし、開発しながらstrings.xmlを書いていくと、複数言語を管理するのはかなり大変。
また、開発者以外の翻訳者にXMLを書かせるのも酷な話です。

ということで、万能ツールExcel君の出番です。

簡単なVBAスクリプトで、Excel管理している言語ファイルから、strings.xmlを出力する機能を作りました。
(たぶん似たようなことをやっている方は多いですよね・・・)

言語ファイル

言語ファイル

これを使えば、

・日本語にはあるのに英語には無い!などの、strings.xmlの記述ミスが無くなります
・Excelなので、XMLを分からない人にも翻訳をお願いできます
・一括生成で、スピーディーに言語のアップデートができます

作ってみたばかりで、不具合等あるかもしれませんが、是非お試し下さい。
フィードバック頂ければ幸いです。

ダウンロード

※VBAマクロなので、Excel設定でマクロを許可して下さい
※Excel 2007で作成しています
※UTF-8対応のため、UTF-8ファイル作成 for VBAのライブラリを使わせて頂きました。作者様に感謝致します。

RailsのHash.from_xmlに注意

このエントリーをはてなブックマーク
Share
2010.05.25    Ruby, Ruby on Rails, 馬場   タグ: —    baba   

RailsのActiveSupportは大変便利で、生Ruby使うときも

irb -r rubygems -r active_support

をデフォルトにしたくなります。

# 個人的には .blank? が一番便利だと思います。

ところで、Hash.from_xmlを使うとお手軽にXMLをパースできますが、若干癖があるので注意が必要です。
・子要素も属性も同じように扱われる
・同じ名前の要素が複数あると自動で配列になる
・typeという名前の属性は、無視されることがある
・ハイフンはアンダースコアに置換される

たとえば、user-listの中にuserが複数ある場合、

{"user_list" => {"user" => ["yamada", "tanaka"]}}

のように変換されるため、扱いやすいのですが、userがたまたま1件だと、

{"user_list" => {"user" => "yamada"}}

のようになり、userが配列と期待しているプログラムは動かなくなります。
特に、検索系のXMLを使う際は注意が必要です。

また、属性は子要素のように使えますが、属性が1つだけだったり、子要素が文字列の場合、扱いが変わったり消えてしまうことがあるので、注意しましょう。

以下に例を挙げます。

<a><b size="123">456</b></a>
=> {"a" => {"b" => "456"}}

<a><b size="123" /></a>
=> {"a" => {"b" => "123"}}

<a><b type="123" /></a>
=> {"a" => {"b" => nil}}

<a><b size="123"><c>456</c></b></a>
=> {"a" => {"b" => {"size" => "123", "c" => "456"}}}

<a><b type="123"><c>456</c></b></a>
=> {"a" => {"b" => {"c" => "456", "type" => "123"}}}

複雑なXMLをパースする際は、ちゃんとnokogiriなどのライブラリを使うと良さそうですね。

EthnaでXML-RPCの引数を連想配列にする

このエントリーをはてなブックマーク
Share
2009.03.04    PHP, Web, ネットワーク, 馬場   タグ: , —    baba   

Ethnaは標準でXML-RPCに対応しているので、
# ethna add-action-xmlrpc hoge
とやるだけで app/action-xmlrpc にActionのテンプレートが作れますし、クライアントからHogeメソッドをコールすると、自動的にHogeアクションが実行されます。
(エントリポイントはindex.phpではなくxmlrpc.phpになります)

しかし、デフォルトでは引数がActionFormのform定義と順番に1対1で対応するため、引数が多くなると使いづらくなります。

ここは、PHPらしく引数名をキーにした連想配列にしたいところ。
(プロジェクト名)_Controller に以下のメソッドを定義(オーバーライド)することで、XMLRPCの第一引数を連想配列として扱い、対応する名前のformに値がセットされるようになります。

function trigger_XMLRPC($method, $param)
{
    // アクション定義の取得
    $action_obj =& $this->_getAction($method);
    if (is_null($action_obj)) {
        return Ethna::raiseError("undefined xmlrpc method [%s]", E_APP_UNDEFINED_ACTION, $method);
    }

    // オブジェクト生成
    $backend =& $this->getBackend();

    $form_name = $this->getActionFormName($method);
    $this->action_form =& new $form_name($this);
    $def = $this->action_form->getDef();
    
    /***** ここから変更 *****/
    if (isset($param[0]) && is_array($param[0]))
    {
        foreach ($param[0] as $key => $value)
        {
            if (isset($def[$key]))
            {
                $this->action_form->set($key, $value);
            }
        }
    }
    /***** ここまで変更 *****/

    // バックエンド処理実行
    $backend->setActionForm($this->action_form);

    $session =& $this->getSession();
    $session->restore();
    $r = $backend->perform($method);

    return $r;
}

これで、クライアント側では引数の順番を気にせずに済みます。

XML-RPC

このエントリーをはてなブックマーク
Share
2009.02.27    Linux, PHP, Web, 馬場   タグ: , , —    baba   

PHPでXML-RPCを使った記録です。
PEAR::XML_RPC2を使うことにしました。PHP5専用で使いやすそうです。

  • PHP5以上
  • cURLのエクステンション

が必須です。

クライアント側では、

require_once './XML/RPC2/Client.php';
$options = array('debug' => true);
$client = XML_RPC2_Client::create('http://www.bpsinc.jp/xmlrpc.php', $options); //URLはダミー
try {
    $result = $client->Version(); //関数名はサーバ側で定義したもの
    print_r($result);
} catch (XML_RPC2_FaultException $e) {
    var_dump($e);
} catch (Exception $e) {
    var_dump($e);
}

のように使えます。

しかし、これだとなぜか結果が全部NULLになることがあります。
debugメッセージを見ると、Server Responseは正常なのに、Decoded ResultがNULLになっているように見えます。

この問題、Server側かHttpRequestの実装の問題だと思うのですが、Server ResponseのBodyの最初に改行が入っているのが原因でした。
"\n<?xml version="1.0"?><methodResponse> …"

PEAR/XML/RPC2/Backend/Xmlrpcext/Client.php の112行目付近で、
$result = xmlrpc_decode($body, $this->encoding);
となっているところを、
$result = xmlrpc_decode(trim($body), $this->encoding);
とすれば直ります。(マルチバイト対応のtrimを書いた方が良いかもしれません)
※XMLRPCEXT拡張モジュールが入っていない場合は、PEAR/XML/RPC2/Backend/PHP/Client.phpになります。

ライブラリを書き換えるのは強引ですが、xmlrpc_decode関数自体がEXPERIMENTALのままですし、あとはHttpRequestやサーバ側を変更しなければ行けないので、スマートな解決方法は思いつきません。
とりあえずresultがnullになる問題は回避できました。

オーバーライドでちょっとはまりました

このエントリーをはてなブックマーク
Share

C#で System.Xml.XmlDocument を使っています。
XmlDocument xmlDocument = new XmlDocument();

プレフィックス付きのノードを作るとき、たとえば


<s:root xmlns:s="urn:hogehoge-hoge" >
    <s:child />
</s:root>

というXMLを作りたいとして、

XmlNode root = xmlDocument.CreateNode(XmlNodeType.Element, "s", "root", "hogehoge-hoge");
XmlNode child = xmlDocument.CreateNode(XmlNodeType.Element, "s", "child", "hogehoge-hoge");
root.AppendChild(child);

とすると動きますが、毎回NamespaceURIを指定するのは変だと思います。
しかし、最後の引数を指定しないと、prefixが出力されません。

また、
xmlDocument.Save("filename");
でファイルに保存するのは1行ですが、string型で受け取ろうと思うと、

StringWriter writer = new StringWriter();
xmlDocument.Save(writer);
string output = writer.ToString();

としないといけません。

色々不便なので、XmlDocumentを継承したExtendedXmlDocumentを作りました。命名がSXGA並にださいですが。。
このクラス内部では、prefixとnamespaceURIのHashtableを持っていて、一度指定すると以降はnamespaceURIを指定しなくても対応するものを用意してくれます。
また、ToString() をオーバーライドしてあるので、1行で文字列を受け取れます。

・・・と、ここまでは良かったんですが(前置きが長い)、
その後調子に乗ってCreateAttribute()をオーバーライドしたらはまりました。

public override XmlAttribute CreateAttribute(string prefix, string name, string value);
を作って、内部で
base.CreateAttribute(name);
を呼ぶと、StackOverflowExceptionが発生します。

どうやら、XmlDocument.CreateAttribute(string) 内部で CreateAttribute(string, string, string) を呼んでいたみたいです。
C#のオーバーライドは動的にメソッドを呼んでくれるので、派生クラスのCreateAttribute(string, string, string) が呼ばれてしまって、その内部で CreateAttribute(string) を呼ぶと無限ループ、と。

.NET Frameworkの内部コードが見られれば一瞬で解決した問題なんですが。
オーバーライドは慎重に使わないとバグの元ですね。オーバーロードと組み合わさるとたまに危険。気をつけます。

COPYRIGHT [C] 2009 BEYOND PERSPECTIVE SOLUTIONS LTD. ALL RIGHTS RESERVED.