Techracho

MusicFly v2.2.3リリース XperiaMini対応

このエントリーをはてなブックマーク Share
2010.07.30    MusicFly, android, java, プレスリリース, 芝原   タグ: , , , , , —    shibachan   

弊社BPSで開発しておりますAndroid端末向け音楽試聴アプリMusicFlyを先ほど1週間ぶりにアップデートしました。

今回のアップデートにより

XperiaMiniに代表される低解像度端末や、反対の高解像度端末などへの対応を果たし

全スクリーンサイズ対応のアプリケーションとなりました。

技術者向けの参考情報

また、以前より原因が解明できずにいたバグのいくつか解決することができました。

そのうちの一つがAndroid1.5でのみタブを利用した画面で強制終了してしまうクリティカルなバグであり、

今回無事解決しました。技術者向けの詳しい話はこちらで。

該当端末を利用している方にはご不便おかけしました。

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

このエントリーをはてなブックマーク Share
    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を対象から外すという後ろ向きなものが最適ですね。

IEでJavaScriptからButtonのtypeを変更できない

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

JavaScriptで動的にエレメントを作成するときは、

var div = document.createElement('div');

のようにやります。

たとえば、ボタンを作ってそのボタンにクリックイベントを追加、などの良くある処理は、jQueryを使って、

var button = $(document.createElement('button'))
.attr('type', 'button').text('ぼたん');
button.click(function() {
    alert('hello');
});
$('#hoge').next(button);

のようにやると思います。typeを設定しているのは、デフォルトだとsubmit扱いになってしまうからです。

しかしこれ、IEだとエラーになります。

type property can’t be changed

buttonのtypeはなぜか読み取り専用プロパティらしい・・・

ということで、ちょっとダサイですが、以下のようにやって解決。

var button = $('<button type="button">ぼたん</button>');

以上、ちょっとした注意点でした。

PythonのSQLiteで検索しようとしたらIncorrect number of bindings supplied. The current statement uses 1, 10 supplied.とか言われた

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

最近の言語はSQLite3のライブラリが入っていることが多いですね。

Python 2.6でも簡単で、

import sqlite3
db = sqlite3.connect('data.db')
db.execute('create table users (id integer primary key, name text, age integer)')
db.execute('insert into users (name, age) values (?, ?)', ('yamada', 21))

のように簡単に扱えます。

ところが、使い方を間違えると

Incorrect number of bindings supplied. The current statement uses 1, 10 supplied.

のようなエラーが出てしまうことがあります。

原因は単純。
executeの第2引数はタプルにしないといけません。

項目が1個のとき、()で囲んだだけだとダメですね・・・

#ダメな例
db.execute('select * from users where name = ?', ('yamada'))  

#良い例
db.execute('select * from users where name = ?', ('yamada',))

基本的ですが、普段別の言語を書いていると気づくのが遅れます。

Twitter OAuthのrequest_tokenするところで、CallbackURLを指定したときだけ401 Unauthorizedになる場合

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

Twitter APIで認証ページに遷移させるときは、http://twitter.com/oauth/request_token にリクエストトークンを発行依頼します。
しかし、設定を間違えるとここで401 Unauthorizedが発生してしまうことがあります。

Tweepyの場合、以下のようなコードになると思います。

consumer_token = "xxxxxxxxxx"
consumer_secret = "xxxxxxxxxxx"
callback = "http://www.bpsinc.jp/"
auth = tweepy.OAuthHandler(consumer_token, consumer_secret, callback)
redirect_url = auth.get_authorization_url()

今回は、auth.get_authorization_url()のところで、401 Unauthorizedが発生しました。

OAuthHandlerの第3引数、callbackを無しにすると、うまく動きます。

なぜかと思ったら・・・

ここが空だった

ここが空だった

Twitterのページでアプリを登録する際に、「ブラウザアプリケーション」を選んで、Callback URLを入力し忘れると、自動的にクライアントアプリケーションになります

クライアントアプリケーションでCallbackは使えないため、401エラーが返っていたようです(400 Bad Requestを返して欲しかったな・・・)。

また、登録したアプリがBANされた場合にも、同じ現象が発生するようなのでご注意下さい。

OAuthライブラリが謎の停止

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

CakePHPでOAuthを使うのに、このライブラリが有名だと思います。

http://code.42dh.com/oauth/

MITライセンスなのが良いですよね。

ところが、これを使ったCakePHPのシステムで、debug=2の時は動くのに、debug=0にすると動かないという謎の問題に出くわしました。

止まる場所は、認証ページへのリダイレクトに使う、

$consumer->getRequestToken('http://twitter.com/oauth/request_token', $callback);

のあたりです。

ソースを追ってみたところ、OAuth.phpの以下の場所で止まっていました。

public function get_normalized_http_url() {
  $parts = parse_url($this->http_url);

  $port = @$parts['port']; // ←ここ!!
  $scheme = $parts['scheme'];
  $host = $parts['host'];
  $path = @$parts['path'];

  $port or $port = ($scheme == 'https') ? '443' : '80';

  if (($scheme == 'https' && $port != '443')
      || ($scheme == 'http' && $port != '80')) {
    $host = "$host:$port";
  }
  return "$scheme://$host$path";
}

いや、確かに、portというキーは無かったんですけど、
そのための@演算子だし、そもそもこんなのでFatal Errorになるはずもなく。

要するに、PHPエンジンの方のバグなのか、謎ですね・・・

解決策は以下。

public function get_normalized_http_url() {
  $parts = parse_url($this->http_url);

  //== add this ==
  if (!array_key_exists('port', $parts)) {
      $parts['port'] = (isset($parts['scheme']) && $parts['scheme'] == 'https') ? '443' : '80';
  }
  //== add this ==

  $port = @$parts['port'];
  $scheme = $parts['scheme'];
  $host = $parts['host'];
  $path = @$parts['path'];

  $port or $port = ($scheme == 'https') ? '443' : '80';

  if (($scheme == 'https' && $port != '443')
      || ($scheme == 'http' && $port != '80')) {
    $host = "$host:$port";
  }
  return "$scheme://$host$path";
}

Xperiaの解像度が320*569になる

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

Xperiaの解像度は480*854です。

しかし、以下のコードで解像度を取得すると、320*569と返ってくることがあります。

Display disp = getWindowManager().getDefaultDisplay();
int dispWidth = disp.getWidth();
int dispHeight = disp.getHeight();

こんな時の検索ワードは、ずばり「Xperia 569 320」ですね。

発売直後に既に回答が議論されていました。

http://groups.google.co.jp/group/android-group-japan/browse_thread/thread/5fad044c9b0429dd

要するに、minSdkVersion=”4″にしろということですね。

まあ、Android 1.5の機種はほぼ絶滅なので、特に問題はないんですが、なんだこれ・・・・

jQuery.formとJSONView

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

JSONViewは、Firefoxで application/json なコンテンツを綺麗に表示するプラグインです。
とても便利ですね。

ところが、jQuery.formプラグインなどと組み合わせると、不具合を起こすことがあるので注意が必要です。

一般的なajaxでは、

$.ajax({
  url: 'hogehgoe',
  success: function(data, dataType) {
  }
});

といったやりかたをしますが、このときは特に問題ありません。

しかし、jQuery.formで $(’form’).ajaxSubmit() を使う場合、内部的には$.ajaxは使用されていません。
XMLHttpRequestでは画像データ等のmultipart/form-dataを送信できないため、ダミーのiframeを作って、そこをターゲットにformの送信を実行しています。

//jquery.form.jsの内部コードのイメージ
var iframe = '<iframe id="hoge" onload="irame-onload"></iframe>';
$('form').attr('target', 'hoge');
$('form').submit();

これで、ajaxSubmitをすると、結果がiframeに入ります。
iframeのonloadイベントで、内部のinnerHTMLを見て、結果を呼び出し元に返す処理を行っています。

ここで、iframeの中身がどんな値になるかは、ブラウザの実装依存です。
一般に、プレインテキストやJSON形式が渡ってきたとしても、DOMツリーを構築するためにbodyタグまでは作ってしまうようです。

良くあるパターン

<html>
<head></head>
<body>{"result":"これが結果です"}</body>
</html>

jquery.formプラグインは、ブラウザがbodyタグの中に直接値を入れるか、またはpreやtextareaに値を入れることを期待しています。

しかし、JSONViewを使うと、以下のような綺麗なHTMLを勝手に作ってくれてしまいます。

<html>
<head></head>
<body>
<doctype html="">
  <div id="json">
    {
    <ul class="obj collapsible">
      <li><span class="prop">result</span>: <span class="num">0</span></li>
    </ul>
    }
  </div>
</doctype>
</body>

人間様が見るには良いですが、これではjquery.formプラグインが認識できません。
そんなわけで、doctype html… といった値を丸ごとJSONパースしようとして、エラーになってしまいます。

解決策は、Content-Typeを変えるか、jquery.formを改造するくらいしかありません。

Content-Typeの変更が、一番みんなハッピーになれるはずです。

これに気づかず、JSONViewを入れると動かなくなるWebサイトがあるかもしれませんね。

AppWidgetManager.updateAppWidgetの注意

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

Androidのホームスクリーンウィジェットを更新するには、AppWidgetManager.updateAppWidget()を使います。

RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.provider);
remoteViews.setTextViewText(R.id.Text01, "hello");

AppWidgetManager manager = AppWidgetManager.getInstance(context);
manager.updateAppWidget(new ComponentName(context, MyProvider.class), remoteViews);

これを連続で呼ぶときには、少し注意が必要です。

たとえば、ボタンのクリック操作にPendingIntentを割り当てるような処理は初期化時のみに行い、ウィジェットを使用中には表示の更新のみを行うような場合、以下のようにやりたくなると思います。

private void init(Context context) {
	RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.provider);
	remoteViews.setTextViewText(R.id.Text01, "");

	PendingIntent clickIntent = PendingIntent.getBroadcast(context, 0, new Intent("MYACTION"), 0);
	remoteViews.setOnClickPendingIntent(R.id.Button01, clickIntent);

	AppWidgetManager manager = AppWidgetManager.getInstance(context);
	manager.updateAppWidget(new ComponentName(context, MyProvider.class), remoteViews);
}

private void update(Context context, String message) {
	RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.provider);
	remoteViews.setTextViewText(R.id.Text01, message);

	AppWidgetManager manager = AppWidgetManager.getInstance(context);
	manager.updateAppWidget(new ComponentName(context, MyProvider.class), remoteViews);
}

このコードは基本的に動くのですが、initの直後にupdateを呼ぶと、以下の問題が発生することがあります。

「同じウィジェットを2つ以上設置したとき、2つめのウィジェットのボタンが反応しない」

init→updateの間にスリープを入れれば動きます。
どうやら、updateAppWidgetが完了しないうちに次の処理をして、ボタンへのpendingIntentの反映が失敗してしまうような動作です。

解決策としては、毎回ボタンの動作を設定する(updateを廃止してinitにmessageを渡せるようにする)か、initの後にスリープを入れるくらいしか、今のところ見つけていません。

なんで2つあったときだけだめなのか、原因を調べてみないといけませんね・・・
何かポカをしていたら教えてください。

Railsでdevelopmentモードでもエラー情報が出ない

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

Railsではエラーが発生した際、developmentモードではstacktraceなどが詳細に表示され、productionモードでは詳細が全部隠れて表示されます。

プロダクションモード

プロダクションモード

デベロップメントモード

デベロップメントモード

しかし、developmentモードで動作しているはずなのに、詳細なエラーが出ないことがあります。

DBへの接続でエラーになった際は、productionと同じ画面が出たことがありました。

他にありがちなのは、エラー詳細画面をrenderする際のエラーです。

今回は、エラーログに、以下のようなエラーが出ていました。

ActionView::TemplateError (wrong number of arguments (1 for 2)) in C:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.3.5/lib/action_controller/templates/rescues/_request_and_response.erb:

-e:2:in `load’
-e:2

Rendered rescues/_trace (42.0ms)
/!\ FAILSAFE /!\ Fri May 28 11:45:52 +0900 2010
Status: 500 Internal Server Error

エラー画面は
/lib/action_controller/templates/rescues/_request_and_response.erb:
ですが、このテンプレートの中では、debugなどの関数を使っています。

今回の原因は、自前で「引数2個の」debug関数を作っていたことでした。
上記エラーからそれが読み取れます。

関数の名前を変えたら、ちゃんとした画面が出ました。
当然、直ってもエラー画面なんですけどね・・・

ありがちな名前を付けないように気をつけようというお話です。
フレームワークが変な名前を予約しないで欲しい・・・ typeとか。

古い投稿 »

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