2010年 02月 23日

todo

jsdeferred の recipe をかく

gerry++

2010年 02月 21日

JavaScript、仮引数で宣言した変数へ代入したあとの arguments の挙動

さて問題です。以下のコードで alert されるのは何でしょう!!

(function (x) {
  x = 2;
  alert(arguments[0]);
})(1);


答えはやってみてください。ビビりました。どうやら arguments オブジェクトは、変数の参照 (値の参照ではなく) を持っています。

ECMAScript 3rd Edition 日本語訳から根拠を探すと、ちゃんと書いてありました。

  • 0 以上 length プロパティの値未満の整数 arg それぞれについて、属性 { DontEnum } のプロパティ ToString(arg) が作成される。このプロパティの初期値は対応するパラメータの呼出側に供給される実際の値である。最初の実際のパラメータ値が arg = 0、2 番目は arg = 1, 以下同様である。arg が Function オブジェクトの仮引数の数より小さい場合、このプロパティは Activation オブジェクトの対応するプロパティとその値を共有する。このことは、このプロパティの変更が Activation オブジェクトの対応するプロパティを変更すること、そしてその逆を意味する。
http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/10_Execution_Contexts.html#section-10.1.8

ちゃんと説明しとくと、この動作の奇妙さは

var obj = new Object();

obj[0] = 1;
obj.foo = obj[0];

obj[0] = 2;
alert(obj.foo); //=> 1

var baz = new Object();
baz.foo = obj[0];

obj[0] = 3;
alert(baz.foo); //=> 2

というあたり前 (に感じる) 挙動崩れるところです。普通、オブジェクトの別プロパティに代入したら、他のプロパティの値も同時に変わることなんてことはありえないし、別オブジェクトのプロパティが変わるなんてもってのほかですが、Activation オブジェクトの引数関係のプロパティだけは違う、ということです。

Activation オブジェクトはそもそも何かというと、変数を保持するオブジェクトです。この、隠れたオブジェクトを見えるようにしたコードを書くと、冒頭のコードは

(function (x) {
  // // Variable Instantiation
  // var a = new Activation(); 
  // a.arguments = { };
  // a.arguments[0] = 1;
  // a["x"] = a.arguments[0];
  // // End

  // a["x"] = 2;
  x = 2;

  // alert(a.arguments[0]);
  alert(arguments[0]);
})(1);

のように (実際は Activation オブジェクトにはアクセスできない) なり、そもそも仮引数 x が保持されているオブジェクトと、渡された引数第一番目の arguments[0] は別々のオブジェクトなのにも関わらず、値が共有されて相互作用をします。

これは「変数」を保持している「オブジェクト」が存在するという仕様上の説明からすると、一貫性がない不可解な動作です。

JSDeferred Deferred.chain

http://lab.hisasann.com/addCommand/index.html を見ていて、next().next() 以外に、こういう風に書けても別にいいよなぁと思ったので実装を書いてみた。next().next() はめんどい割とめんどいし

git master HEAD には今のところ Deferred.chain() として入ってる。これと Deferred.connect() の変数バインドを組み合せて、addCommand.js のデモ相当をしてみた。

それっぽいとこを抜きだすと

Deferred.define();

var img1 = $("#img1"),
	img2 = $("#img2"),
	img3 = $("#img3");
(function() {
	var callee  = arguments.callee;

	var animate = function (target, prop, speed, easing) {
		return Deferred.connect(target, "animate", { args: [ prop, speed, easing ] });
	};

	Deferred.chain(
		animate(img1, {
			opacity: 0, top: 90 + "px", left: 260 + "px"
			}, 1000, "easeInOutCirc"),
		animate(img2, {
			opacity: 0, top: 170 + "px", left: 450 + "px"
			}, 1000, "easeInOutCirc"),
		animate(img3, {
			opacity: 0, top: 260 + "px", left: 620 + "px"
			}, 1000, "easeInOutCirc"),
		[
			animate(img1, {
				opacity: 1, top: 0 + "px", left: 0 + "px"
				}, 1000, "easeInBack"),
			animate(img2, {
				opacity: 1, top: 0 + "px", left: 0 + "px"
				}, 1000, "easeInBack"),
			animate(img3, {
				opacity: 1, top: 0 + "px", left: 0 + "px"
				}, 1000, "easeInBack")
		],
		function () {
			return wait(0.5).next(callee);
		},
		function error (e) {
			alert(e);
		}
	);
})();

となっていて、デモのコードとほぼ同じになる。エラーをキャッチする関数には function error() としておくルールがある。

キモは animate() を実行した時点ではアニメーションは実行されず、アニメーションを実行する関数を返すだけのところです。(Deferred.connect は関数を返す関数です)

便利なので入れたい気もするけど、うまく書かないと混乱するのでむずかしい。

2010年 02月 17日

5つ以上スターを連打するとグリーンスターつけるように変える Greasemonkey script

5つ以上連打しようとすると勝手にグリーンスターを消費するようになるグリモンです。

グリーンスターに切り替わるとつく速度が落ちますが、連打したぶんはちゃんとつくので待っていてください (色つきスターはリクエストが増えるみたいなので純粋につけるのに時間がかかってます)

最近 Java の Executors にラブなので SingleThreadExecutor みたいなやつをつくってやってます。

2010年 02月 10日

Android Emulator で DNS がひけない場合

エミュレータを

emulator  -dns-server 8.8.8.8  -avd android1.6   

みたいに起動。プライベート IP を指定するとアクセスできなかったりするので Google DNS をつかうのが楽

さらに、自作のアプリケーションの場合、

    <uses-permission android:name="android.permission.INTERNET" />

が入っていることを確認する。

Android でアプリケーションが強制終了したとき、エラーレポートを送るようにする

フォトライフアプリは例外発生時にバグレポを送れるようになってます。

バグレポの仕組みは、http://www.adamrocker.com/blog/288/bug-report-system-for-android.html で述べられているようなやり方で、フォトライフアプリではサーバサイドなしにしたりとか簡略化して実装をしています。

開発中やらデバッグ中も、ケーブルレスでエラースタックトレースが見れるようになるのでとても便利です。

100行程度なので、めんどくさいのでコード貼っておくと

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Build;

class ErrorReporter implements UncaughtExceptionHandler {
    private static Context sContext = null;
    private static PackageInfo sPackageInfo = null;
    private static ActivityManager.MemoryInfo sMemoryInfo = new ActivityManager.MemoryInfo();

    private static final String BUG_FILE = "BUG";
    private static final String MAIL_TO  = "mailto:cho45@lowreal.net";
    private static final UncaughtExceptionHandler sDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();

    public static void setup(Context context) {
        context = context.getApplicationContext();

        try {
            sPackageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }

        sContext = context;

        Thread.setDefaultUncaughtExceptionHandler(new ErrorReporter());
    }

    public static void bugreport(final Activity activity) {
        File bugfile = activity.getFileStreamPath(BUG_FILE);
        if (!bugfile.exists()) return;

        File dstfile = activity.getFileStreamPath(BUG_FILE + ".txt");
        bugfile.renameTo(dstfile);

        final StringBuilder body = new StringBuilder();
        String firstLine = null;
        try {
            BufferedReader br = new BufferedReader(new FileReader(dstfile));
            String line;
            while ((line = br.readLine()) != null) {
                if (firstLine == null) {
                    firstLine = line;
                } else {
                    body.append(line).append("\n");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        final String subject = firstLine;
        new AlertDialog.Builder(activity)
            .setIcon(R.drawable.icon)
            .setTitle(R.string.bug_report)
            .setMessage(R.string.bug_report_message)
            .setPositiveButton(R.string.bug_report_positive_button, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    activity.startActivity(
                        new Intent(Intent.ACTION_SENDTO, Uri.parse(MAIL_TO))
                            .putExtra(Intent.EXTRA_SUBJECT, subject)
                            .putExtra(Intent.EXTRA_TEXT, body.toString())
                    );
                }
            })
            .setNegativeButton(R.string.bug_report_negative_button, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                }
            })
            .show();
    }


    public void uncaughtException(Thread thread, Throwable error) {
        error.printStackTrace();

        try {
            PrintWriter writer = new PrintWriter(sContext.openFileOutput(BUG_FILE, Context.MODE_WORLD_READABLE));
            if (sPackageInfo != null) {
                writer.printf("[BUG][%s] versionName:%s, versionCode:%d\n", sPackageInfo.packageName, sPackageInfo.versionName, sPackageInfo.versionCode);
            } else {
                writer.printf("[BUG][Unkown]\n");
            }
            try {
                writer.printf("Runtime Memory: total: %dKB, free: %dKB, used: %dKB\n",
                    Runtime.getRuntime().totalMemory() / 1024,
                    Runtime.getRuntime().freeMemory() / 1024,
                    (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024
                );
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                ((ActivityManager)sContext.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(sMemoryInfo);
                writer.printf("availMem: %dKB, lowMemory: %b\n", sMemoryInfo.availMem / 1024, sMemoryInfo.lowMemory);
            } catch (Exception e) {
                e.printStackTrace();
            }
            writer.printf("DEVICE: %s\n", Build.DEVICE);
            writer.printf("MODEL: %s\n", Build.MODEL);
            writer.printf("VERSION.SDK: %s\n", Build.VERSION.SDK);
            writer.println("");
            error.printStackTrace(writer);
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        sDefaultHandler.uncaughtException(thread, error);
    }
}

てな感じで

  • R.drawable.icon
  • R.string.bug_report
  • R.string.bug_report_message
  • R.string.bug_report_positive_button
  • R.string.bug_report_negative_button

あたりをリソースに使いつつ、各 Activity の onCreate で

ErrorReporter.setup(this);

を早い段階で呼び、

if (mPref.getBoolean(Setting.SEND_BUG, true)) ErrorReporter.bugreport(FooActivity.this);

を適当に、ダイアログを表示する準備ができたときに呼んでやっています。

ErrorReporter.bugreport は前回アプリケーションが強制終了した痕跡があるなら、ダイアログを表示してユーザにレポートの送信を求めます。やってることは単にメール Intent を投げてるだけなので、特にパーミッションもいりません。

2010年 02月 09日

Android リファレンスを Chemr で検索

SDK が手元にあると思うのでそれでインデックスさせたのをつかってる。CSS は付属のやつを生かす方向で、一部の要素を非表示に……

http://gist.github.com/299044

2010年 02月 06日

Eclipse + Vim 開発環境構築

Android SDK は Eclipse 前提でだいたい話が進むので、かたくなに Eclipse を使わないようにしているといらない苦労を負いすぎます (リソース管理とか)。ので、これを期に Eclipse と連携させて Vim を使う環境をつくりました。

Eclipse をコンパイル、実行環境にし、編集を Vim にするために

  • Eclim をいれる http://eclim.org/
  • eclimd を起動 (Window -> Show View -> eclimd or /Application/Eclipse/eclimd) して :PingEclim :EclimValidate がうまくいくか確かめる。ぼくは大丈夫だった。ダメなら FAQ
  • ワークスペースの設定をする
    • Eclipse デフォルトなら変えなくてもいいっぽいけど、僕の場合 ~/project なので
  • eclimd はメモリ増やして起動する
  • :ProjectInfo がうまくいくかを確認する。できなければ FAQ を見る

AutoComplPop の設定

.vimrc に追記

let g:AutoComplPop_Behavior = {
      \   'java' : [
      \     {
      \       'command'  : "\<C-n>",
      \       'pattern'  : '\k\k$',
      \       'excluded' : '^$',
      \       'repeat'   : 0,
      \     },
      \     {
      \       'command'  : "\<C-x>\<C-u>",
      \       'pattern'  : '\k\k$',
      \       'excluded' : '^$',
      \       'repeat'   : 0,
      \     },
      \     {
      \       'command'  : "\<C-x>\<C-f>",
      \       'pattern'  : (has('win32') || has('win64') ? '\f[/\\]\f*$' : '\f[/]\f*$'),
      \       'excluded' : '[*/\\][/\\]\f*$\|[^[:print:]]\f*$',
      \       'repeat'   : 1,
      \     }
      \   ]
      \ }

キーワードマッチしなければ eclipse に投げる (めっちゃ重い)。

デバッグ

保存すると自動で :Validate が走る。:lope で確認できる。+sign つきで vim がコンパイルされているならいい感じにでてくる!

:Validate が走るタイミングが保存よりも早いときがあってちょっと変

import 分関係

http://eclim.org/vim/java/import.html

  • :JavaImportMissing
  • :JavaImportClean

override しまくる

:JavaImpl すると override できるメソッド一覧がでてくるので、適当に RET おすと テンプレートが挿入される。便利なんだけど、Vim の undo が効かなくなってしまうのであんまり使ってない (表示させてコピペ、っていう使いかたをしてる)

データクラス

ハッシュを自由に作れないのが LL に比べて極めて不便なので、つどデータクラスを作るわけですが、getter setter 作るのが死ぬほどめんどい。

  • :JavaGetSet

Android アプリケーションのヒープメモリを観察する

メモリが限られていると頻繁に OOM エラーがでますが、OOM エラーは出さないことが大前提なので、頑張って何が容量食ってるか調べないといけません。まぁ、コード的に勘が効く部分は良いのですが、ライブラリに隠蔽されていたり、普段 LL 使ってると見落したりする部分があるのでちゃんと計測したいもんです。

http://www.eclipse.org/mat/ をいれる。

エミュレータでアプリケーションを起動して、ある程度動かしてから

↑ のボタンを押す

メモリーリークしているっぽいものまで解析してくれる。すごい

Histogram -> ソート -> 右クリック -> List objects でどんなオブジェクトがみれるっぽい。List objects の outgoing incoming はよくわからない。

開発中これを使ったら、画像のURLを保持する的な部分で随分メモリを食っていることが分ったので、はてな記法のみを保持してURLとかは必要なときだけ更めて生成 (パフォーマンスは劣化しますがOOMでるよりはマシです) するようにしたらだいぶ改善しました。

あと、スレッドの一覧を見ながら動かして想定通りのスレッド数かを観察するのも大事だと思いました。HttpClient はインスタンスごとにプールスレッドを持つので注意が必要でした (若干反則な気がしますが static に確保するようにしました)