<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>ぐま あーかいぶ</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/" />
    <link rel="self" type="application/atom+xml" href="https://archive.guma.jp/atom.xml" />
    <id>tag:archive.guma.jp,2017-01-07:/2</id>
    <updated>2017-01-07T16:08:59Z</updated>
    <subtitle>気合いを侵略するサイト</subtitle>
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type Pro 6.3.2</generator>

<entry>
    <title>Android で Twitter4J から Streaming API 使う場合</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2012/03/android-twitter4j-streaming-api.html" />
    <id>tag:archive.guma.jp,2012://2.33</id>

    <published>2012-03-15T07:26:00Z</published>
    <updated>2017-01-07T16:08:59Z</updated>

    <summary>デフォルトのまま使うのは無駄な処理が多く制御もしづらいので、よっぽど単純なテスト...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Android" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Java" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Twitter" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Twitter4J" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>デフォルトのまま使うのは無駄な処理が多く制御もしづらいので、よっぽど単純なテスト目的を除いてやめましょう。</p>

<p>Twitter4J は Streaming API で受け取ったデータをマルチスレッドで処理するために内部で独自のスレッド管理をおこなってますが、これは当然というかほぼ T4J 専用に作られていて非常に応用が利きません。たとえば作られたスレッドが余っていても T4J 専用なので自分のアプリから使うことが出来なかったりします。Android でこの無駄はバッテリーにとても厳しい。</p>

<p>また Java 1.4 との互換性にも縛られているせいで「車輪の再発明」を強いられている感もあり、Java 1.5 環境の Android ならもっとかんたんで便利なものが手軽に利用できるのでそっちにしたほうがいい、というのもあります。</p>
]]>
        <![CDATA[<p>具体的には <a href="https://github.com/twitter/twitter4j/blob/master/twitter4j-core/src/main/java/twitter4j/internal/async/Dispatcher.java">twitter4j.internal.async.Dispatcher</a> インターフェースを実装したクラスを作って、そのクラス名を twitter4j.Configuration クラスのインスタンスに持たせるだけです。</p>

<p>と聞くとなんだかめんどくさそうですけど、Dispatcher の実装ってこんなのでいいんです。</p>

<div data-gist="2042615"></div>

<p>これはチャーハン諸島で使ってるものですが、T4J デフォルトの <a href="https://github.com/twitter/twitter4j/blob/master/twitter4j-core/src/main/java/twitter4j/internal/async/DispatcherImpl.java">DispatcherImpl.java</a> と比べたらかんたんすぎて逆に不安になるレベル。 <br />
実際のチャーハン諸島では ThreadFatctory もオリジナルのものを使ってるので若干違うんですけど本質的には変わらないです。</p>

<p><a href="http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html">ExecutorService</a> と <a href="http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html">Executors</a> 使ってるおかげで「指定した個数のスレッド固定」とか「必要に応じて生成したスレッドをプールして再利用」とかアプリの実装に合わせて挙動も変えられるのでなんとなく嬉しいかもしれません。</p>

<p>これのクラス名を ConfigurationBuilder#setDispatcherImpl(String) 使ったりした Configutaion インスタンスを TwitterStreamFactory に渡せば勝手に使ってくれます。</p>

<p>これだとシンプルすぎてオリジナルの DispatcherImpl との優劣があんまりないんですが、ExecutorService の変数をクラスの static メンバーにして各インスタンスで共有するようにすれば余ったワーカースレッドにほかの処理をさせることもできて効率的です。 <br />
AsyncTask と共有できればより効率的なんですが、こっちも自分専用の ExecutorService を使うようになってるのでそうはいかないので、やるなら AsyncTask を使った Dispatcher インターフェースの実装とかになるんでしょうか。デメリットのほうが多そう。</p>

<p>それはともかく、Twitter4J はちょっとした利用でもオリジナルのクラス使うよりインターフェース実装したりクラス継承したりしたほうが使いやすく、かつ大して手間も変わらなかったりします。少なくとも twitter4j.ConfigurationBuilder 使うより ConfigurationBase とか継承して必要な getter だけオーバーライドしたほうが絶対ラクだしソースも分離できて読みやすくなる。と思う。</p>
]]>
    </content>
</entry>

<entry>
    <title>Android の Support Package r6</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2011/12/android-support-package-r6.html" />
    <id>tag:archive.guma.jp,2011://2.32</id>

    <published>2011-12-23T05:10:40Z</published>
    <updated>2017-01-07T16:10:21Z</updated>

    <summary>Android の下位互換向け純正ライブラリであるところの Support Pa...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Android" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Java" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="android" label="Android" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="pagertitlestrip" label="PagerTitleStrip" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="viewpager" label="ViewPager" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>Android の下位互換向け純正ライブラリであるところの Support Package が r6 にアップデートされてました。</p>

<blockquote>
  <p>Support Package | Android Developers <br />
<a href="http://developer.android.com/intl/ja/sdk/compatibility-library.html">http://developer.android.com/intl/ja/sdk/compatibility-library.html</a></p>
</blockquote>

<p>これは Android 3.0 で追加された Fragment とかの便利なクラスを Android 1.6 以降でも使えるようになったりする大変ありがたいライブラリですが、今回のアップデートで <a href="http://developer.android.com/reference/android/support/v4/view/PagerTitleStrip.html">PagerTitleStrip</a> が追加されていたので試してみました。</p>
]]>
        <![CDATA[<p><a href="/assets_c/2011/12/device-2011-12-23-133825-27.html" onclick="window.open('https://archive.guma.jp/assets_c/2011/12/device-2011-12-23-133825-27.html','popup','width=480,height=800,scrollbars=no,resizable=no,toolbar=no,directories=no,location=no,menubar=no,status=no,left=0,top=0'); return false"><img src="/assets_c/2011/12/device-2011-12-23-133825-thumb-320x533-27.png" width="320" height="533" alt="device-2011-12-23-133825.png" class="mt-image-none" style="" /></a></p>

<p>地味でぱっと見よくわからないスクリーンショットですけど、page 1 とかの部分がページタイトル、その下に各ページが表示されててこれが左右のフリックで切り替えられます。 <br />
要するに Google+ の Android 用アプリ的な UI ですね。</p>

<p>あれ使いやすくて恰好いいから使いたかったけどそのためには自分でいろいろ書かないといけなくてめんどくさかったのが、Support Package ひとつで解決するとか素晴らしいです。 <br />
各ページには ListFragment を使ってるので、Activity もたったひとつだけで済むし。</p>

<p>唯一の違いは「ページタイトルをタップしてもページ切り替えられない」ってとこ。 <br />
ページタイトルの表示は、今回のアップデートで追加された PagerTitleStrip がやってるんですけど、こいつが画面のタッチイベントに反応するよう作られていないので、g+ アプリと同じ挙動を真似るには自前でコード追加しないとですね。</p>

<p>このサンプルのコードは下に。公式サイトで配布されてる android-support-v4.jar をビルドパスに追加すれば、たったこれだけのコードで立派に動きます。</p>

<div data-gist="1513143"></div>

<p>このライブラリは Android 3.0 以降で追加された新要素をわりとよく拾ってくれてて、しかも Android 1.6 から対応してる便利で使い出のあるライブラリだと思うんですけど、なんかあんまり使われてる例を見ないのが不思議で。 <br />
まだ Web 上に日本語のサンプルとか少ないからなんだろうけど、ライブラリのソースも公開されてるからこれ以上どんなヒントが必要なんだ的な。</p>
]]>
    </content>
</entry>

<entry>
    <title>カップラーメン大陸でうまく認証できない場合は</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2011/11/cup-ramen-twitter.html" />
    <id>tag:archive.guma.jp,2011://2.31</id>

    <published>2011-11-06T12:54:09Z</published>
    <updated>2016-04-17T02:01:16Z</updated>

    <summary>iPhone 用の有料 Twitter アプリの中でもなぜかかなりの高人気を誇る...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Twitter" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="twitter" label="Twitter" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="ラーメン大陸" label="ラーメン大陸" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>iPhone 用の有料 Twitter アプリの中でもなぜかかなりの高人気を誇る Twitter クライアント「カップラーメン大陸」で、アカウント認証がうまくいかないというケースがぽつぽつ見受けられるようで。</p>

<p>せっかくお金払って買ったのにアカウント認証できなくてまったく使えない！何このゴミクズ！カップヌードルのほうがまだ安いしお腹もふくれるからマシだわ！などとブチギレしてる人がいるとかいないとかいう話ですが、原因はたいてい「本人のアカウント設定」にあるようです。</p>

<p>さてその解決策ですが。 <br />
Twitter のアカウント設定ページ <a href="https://twitter.com/settings/account">https://twitter.com/settings/account</a> の下のほうに「HTTPS を常時使用する」というチェックボックスがあります。</p>

<p><a href="//archive.guma.jp/assets_c/2011/11/2011-11-06%2021.41.00-24.html" onclick="window.open('//archive.guma.jp/assets_c/2011/11/2011-11-06%2021.41.00-24.html','popup','width=642,height=270,scrollbars=no,resizable=no,toolbar=no,directories=no,location=no,menubar=no,status=no,left=0,top=0'); return false"><img src="//archive.guma.jp/assets_c/2011/11/2011-11-06%2021.41.00-thumb-320x134-24.png" width="320" height="134" alt="2011-11-06 21.41.00.png" class="mt-image-none" style="" /></a></p>

<p>このチェックがもし入っていたら、チェックを外して「保存」ボタンを押します。ここで確認のため Twitter のパスワード入力ダイアログが表示されますから、素直にパスワード入力して完了です。</p>

<p>そして改めてカップラーメン大陸を起動し、アカウント認証を試してみましょう。</p>
]]>
        
    </content>
</entry>

<entry>
    <title>時代は AsyncTask より AsyncTaskLoader</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2011/11/-asynctask-asynctaskloader.html" />
    <id>tag:archive.guma.jp,2011://2.30</id>

    <published>2011-11-02T08:24:00Z</published>
    <updated>2021-03-22T07:16:14Z</updated>

    <summary>時代は AsyncTask より AsyncTaskLoader Android...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Android" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Java" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="android" label="Android" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="asynctask" label="AsyncTask" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="asynctaskloader" label="AsyncTaskLoader" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="java" label="Java" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>時代は AsyncTask より AsyncTaskLoader</p>

<p>Android 4.0、通称 Ice Cream sandwich というスマートフォンもタブレット端末もカバーする新しい OS がもうすぐデビューするとかいう時期なので、Android プログラミングもそれの普及をにらんだ実装に切り替えていくべき。</p>

<p>まずは、きっと Activity 上での非同期処理に多用されているであろう AsyncTask を、Android 3.0 以降で追加された AsyncTaskLoader へ乗り換えるところから始めるのもいいんじゃないかと思ってちょっと書いてみます。</p>

<p>あ、これは Activity での非同期処理について、という前提での内容になりますので、たとえば Service の中で非同期処理したい場合はどうすれば的な質問には役に立たないと思います。</p>
]]>
        <![CDATA[<p>いくら 4.0 がリリースされたとはいえ、世の中はまだまだ 2.3 以前が幅を利かせている時代。 <br />
追加されたり便利に改善されたりした API とか使いたくても使えないというジレンマを解消するために、Google は互換ライブラリをリリースしています。</p>

<blockquote>
  <p>Support Package | Android Developers <br />
<a href="http://developer.android.com/intl/ja/sdk/compatibility-library.html">http://developer.android.com/intl/ja/sdk/compatibility-library.html</a></p>
</blockquote>

<p>これを使えば Android 1.6 以降なら新しい API の一部を使えるようになったりします。超便利。</p>

<p>というわけで、まずはこの互換ライブラリを Java のビルドパスに追加しましょう。考えるよりも先に。</p>

<p>そして AsyncTaskLoader ですが、まずこんな AsyncTask があるものとします。</p>

<pre><code class="language-java">import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import org.json.JSONException;
import org.json.JSONObject;

import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;

public class AsyncFetchJSONTask extends AsyncTask&lt;String, Void, JSONObject> {

    private static final String TAG = AsyncFetchJSONTask.class.getSimpleName();

    @Override
    protected JSONObject doInBackground(String... params) {
        if (params == null || params.length < 1) {
            return null;
        }

        URL url;
        try {
            url = new URL(params[0]);
        } catch (MalformedURLException e) {
            Log.e(TAG, "invalid URL : " + params[0], e);
            return null;
        }

        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Connection", "close");
            conn.setFixedLengthStreamingMode(0);

            conn.connect();

            int code = conn.getResponseCode();
            Log.d(TAG, "Responce code : " + code);

            if (code != 200) {
                Log.e(TAG, "HTTP GET Error : code=" + code);
                return null;
            }

            return new JSONObject(readContent(conn));
        } catch (IOException e) {
            Log.e(TAG, "Failed to get content : " + url, e);
            return null;
        } catch (JSONException e) {
            Log.e(TAG, "invalid JSON String", e);
            return null;
        } finally {
            if (conn != null) {
                try {
                    conn.disconnect();
                } catch (Exception ignore) {
                }
            }
        }
    }

    private String readContent(HttpURLConnection conn) throws IOException {
        String charsetName;

        String contentType = conn.getContentType();
        if (! TextUtils.isEmpty(contentType)) {
            int idx = contentType.indexOf("charset=");
            if (idx != -1) {
                charsetName = contentType.substring(idx + "charset=".length());
            } else {
                charsetName = "UTF-8";
            }
        } else {
            charsetName = "UTF-8";
        }

        InputStream is = new BufferedInputStream(conn.getInputStream());

        int length = conn.getContentLength();
        ByteArrayOutputStream os = length > 0 ? new ByteArrayOutputStream(length) : new ByteArrayOutputStream();

        byte[] buff = new byte[10240];
        int readLen;
        while ((readLen = is.read(buff)) != -1) {
            if (readLen > 0) {
                os.write(buff, 0, readLen);
            }
        }

        return new String(os.toByteArray(), charsetName);
    }

    @Override
    protected void onPostExecute(JSONObject result) {
        // TODO result を使って何かする
    }

}
</code></pre>

<p>実にひねりのないつまらないコードですがそれはともかく、指定した URL から取得できる JSON 文字列を <code>org.json.JSONObject</code> に変換したものが得られる <code>AsyncTask</code> クラスの実装です。</p>

<p>こんな感じで使ったりするんじゃないですかね。</p>

<pre><code class="language-java">
AsyncFetchJSONTask task = new AsyncFetchJSONTask();
task.execute("https://api.twitter.com/1/statuses/user_timeline.json?screen_name=twj_dev");
</code></pre>

<p>で、これを <code>AsyncTaskLoader</code> に置き換えるとこんな感じになります。</p>

<pre><code class="language-java">import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import android.text.TextUtils;
import android.util.Log;

public class AsyncFetchJSONLoader extends AsyncTaskLoader&lt;JSONObject> {

    private static final String TAG = AsyncFetchJSONLoader.class.getSimpleName();

    private final String urlStr;
    private JSONObject result;

    public AsyncFetchJSONLoader(Context context, String urlStr) {
        super(context);
        this.urlStr = urlStr;
    }

    @Override
    public JSONObject loadInBackground() {
        URL url;
        try {
            url = new URL(this.urlStr);
        } catch (MalformedURLException e) {
            Log.e(TAG, "invalid URL : " + this.urlStr, e);
            return null;
        }

        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Connection", "close");
            conn.setFixedLengthStreamingMode(0);

            conn.connect();

            int code = conn.getResponseCode();
            Log.d(TAG, "Responce code : " + code);

            if (code != 200) {
                Log.e(TAG, "HTTP GET Error : code=" + code);
                return null;
            }

            String content = readContent(conn);

            return TextUtils.isEmpty(content) ? null : new JSONObject(content);
        } catch (IOException e) {
            Log.e(TAG, "Failed to get content : " + url, e);
            return null;
        } catch (JSONException e) {
            Log.e(TAG, "invalid JSON String", e);
            return null;
        } finally {
            if (conn != null) {
                try {
                    conn.disconnect();
                } catch (Exception ignore) {
                }
            }
        }
    }

    private String readContent(HttpURLConnection conn) throws IOException {
        String charsetName;

        String contentType = conn.getContentType();
        if (! TextUtils.isEmpty(contentType)) {
            int idx = contentType.indexOf("charset=");
            if (idx != -1) {
                charsetName = contentType.substring(idx + "charset=".length());
            } else {
                charsetName = "UTF-8";
            }
        } else {
            charsetName = "UTF-8";
        }

        InputStream is = new BufferedInputStream(conn.getInputStream());

        int length = conn.getContentLength();
        ByteArrayOutputStream os = length > 0 ? new ByteArrayOutputStream(length) : new ByteArrayOutputStream();

        byte[] buff = new byte[10240];
        int readLen;
        while ((readLen = is.read(buff)) != -1) {
            if (isReset()) {
                return null;
            }

            if (readLen > 0) {
                os.write(buff, 0, readLen);
            }
        }

        return new String(os.toByteArray(), charsetName);
    }

    @Override
    public void deliverResult(JSONObject data) {
        if (isReset()) {
            if (this.result != null) {
                this.result = null;
            }
            return;
        }

        this.result = data;

        if (isStarted()) {
            super.deliverResult(data);
        }
    }

    @Override
    protected void onStartLoading() {
        if (this.result != null) {
            deliverResult(this.result);
        }
        if (takeContentChanged() || this.result == null) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
        super.onStopLoading();
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();
        onStopLoading();
    }

    public String getUrlStr() {
        return urlStr;
    }

    @Override
    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
        super.dump(prefix, fd, writer, args);
        writer.print(prefix); writer.print("urlStr="); writer.println(this.urlStr);
        writer.print(prefix); writer.print("result="); writer.println(this.result);
    }

}
</code></pre>

<p>キモはコンストラクタと <code>loadInBackground()</code> の辺りですかね。 <br />
その他のメソッドはわりと定型的な感じで、よくわからなくてもこう書いておけばうまく動く、程度に把握しといても当面は困らないです。</p>

<p>これをどう使うかというと、まず呼び出す <code>Activity</code> を <code>android.support.v4.app.FragmentActivity</code> から継承したものにします。 <br />
さらにインターフェース <code>android.support.v4.app.LoaderManager.LoaderCallbacks&amp;lt;T&gt;</code> も持たせます。</p>

<pre><code class="language-java">import org.json.JSONObject;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.Log;

public class FetchJsonActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks&lt;JSONObject> {

    private static final String TAG = FetchJsonActivity.class.getSimpleName();

    private static final String KEY_URL_STR = "urlStr";

    @Override
    protected void onCreate(Bundle saved) {
        super.onCreate(saved);
        Log.d(TAG, "onCreate");

        setContentView(R.layout.main);

        Bundle args = new Bundle(1);
        args.putString(KEY_URL_STR, "https://api.twitter.com/1/statuses/user_timeline.json?screen_name=twj_dev");
        getSupportLoaderManager().initLoader(0, args, this);
    }

    @Override
    public Loader&lt;JSONObject> onCreateLoader(int id, Bundle args) {
        String urlStr = args.getString(KEY_URL_STR);
        if (! TextUtils.isEmpty(urlStr)) {
            return new AsyncFetchJSONLoader(getApplication(), urlStr);
        }
        return null;
    }

    @Override
    public void onLoadFinished(Loader&lt;JSONObject> loader, JSONObject data) {
        // TODO 取得できた data で何かする
    }

    @Override
    public void onLoaderReset(Loader&lt;JSONObject> data) {
        // 特に何もしない
    }

}
</code></pre>

<p>こうすると、<code>onCreate()</code> の最後にある <code>getSupportLoaderManager()...</code> の辺りで上に書いた <code>AsyncFetchJSONLoader</code> が別スレッドでスタートし、終わったら <code>onLoadFinished()</code> が UI スレッドでコールバックされる、といった動作になります。 <br />
この <code>Activity</code> のコード内ではすべてのメソッドが UI スレッド上でのみ実行されるものに限定されるので、非同期処理に必須のスレッドセーフ的な注意がほぼ必要なくなって、何をするにも気楽でいられますね。</p>

<p>もうちょっと掘り下げると、</p>

<pre><code class="language-java">getSupportLoaderManager().initLoader(0, args, this);</code></pre>

<p>この <code>initLoader(0, null, this)</code> の引数 <code>0</code> と <code>null</code> が、</p>

<pre><code class="language-java">@Override
public Loader&lt;JSONObject> onCreateLoader(int id, Bundle args) {
    // ...
}
</code></pre>

<p>というメソッドの <code>id</code> と <code>args</code> に渡されてます。</p>

<p>設計した人の狙いとしては、<code>onCreateLoader()</code> に渡される id の値に応じて戻り値にする <code>Loader&lt;T&gt;</code> のインスタンスを切り替えたり、<code>Loader&lt;T&gt;</code> インスタンスの初期化に必要なパラメーターを <code>args</code> で渡したりできるようにってところだと思います。 <br />
このサンプルではそこまで凝ったことはやってませんけど。</p>

<p>いにしえの <code>AsyncTask</code> は、裏に用意されたスレッドプール上でなんとなく <code>AsyncTask</code> インスタンスが順次実行されて結果を UI スレッドで受け取る、といった用途で使う設計でした。</p>

<p>が、この設計だと UI スレッドで結果を受け取る <code>doPostExecute()</code> や、非同期処理前に UI スレッドで実行される <code>doPreExecute()</code> も <code>AsyncTask</code> クラス上に実装されてしまうので、<code>Activity</code> クラス間との依存関係がどうしても発生しやすくなっていました。 <br />
わりと <code>Activity</code> クラス内に入れ子で <code>AsyncTask</code> クラスを実装しがちになってソースファイルが長くなり、見通し悪いってレベルじゃねーぞと気が重くなるプログラマーも多かったんじゃないですかね。</p>

<p>ほかにも <code>AsyncTask#cancel()</code> 周辺にバグがあったりなかったりして面倒がくさい事態が招かれる不幸なケースがあったかもという話もあるようですが、まぁそれはともかく。</p>

<p>たぶんそうした苦労や苦情がきっかけとなって、Android 3.0 で「ローダー」という <code>Activity</code> による非同期処理の強い味方が追加されました。</p>

<blockquote>
  <p>1.2 ローダ - ソフトウェア技術ドキュメントを勝手に翻訳 <br />
<a href="https://sites.google.com/a/techdoctranslator.com/jp/android/guide/activities/loaders">https://sites.google.com/a/techdoctranslator.com/jp/android/guide/activities/loaders</a></p>
</blockquote>

<p>このローダーというものが <code>Activity</code> と <code>AsyncTask</code> の間に立ってくれるようになって、お互いのクラスにあるメソッドを UI スレッドと別スレッドそれぞれからいい具合に呼び出してくれるおかげで、上記のような実装ができるようになったというわけです。おそらく。</p>

<p>このローダーの真価は、<code>AsyncTask</code> だけでなく <code>Cursor</code> を使う場合に発揮されると個人的には考えてるんですけど、長くなったのでそれは次回の講釈で(CV 芥川隆行)。</p>
]]>
    </content>
</entry>

<entry>
    <title>Twitter の JSON に罪はない</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/12/twitter-json.html" />
    <id>tag:archive.guma.jp,2010://2.29</id>

    <published>2010-12-02T02:24:00Z</published>
    <updated>2014-09-08T01:36:32Z</updated>

    <summary>   TwitterのステータスIDが53bitを越えたお話 - tmytのらく...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Java" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="JavaScript" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Twitter" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<blockquote>
  <p>TwitterのステータスIDが53bitを越えたお話 - tmytのらくがき <br />
<a href="http://d.hatena.ne.jp/tmyt/20101201/1291166929">http://d.hatena.ne.jp/tmyt/20101201/1291166929</a></p>
</blockquote>

<p>から引用。</p>

<blockquote>
  <p>このうちXMLで処理してる場合は内部で64bit INTで処理していれば特に問題は起きません。</p>
</blockquote>

<p>こういう微妙なまちがいをしてる人はこの記事書いた人だけでなく大勢いるようだけど、記事としてはまとまっていたので参照。</p>

<p>JSON という書式は、確かに JavaScript から派生したサブセットですので、</p>

<blockquote>
  <p>JSONを仕様書通りにパースするとidの値はdouble</p>
</blockquote>

<p>と考えてしまうのも無理はない気はします。 <br />
が、まちがいであるのも確かです。</p>
]]>
        <![CDATA[<blockquote>
  <p>RFC 4627 - The application/json Media Type for JavaScript Object Notation (JSON) <br />
<a href="http://tools.ietf.org/html/rfc4627">http://tools.ietf.org/html/rfc4627</a></p>
</blockquote>

<p>たぶん引用部分の「仕様書」が指してる RFC の文書を読めばわかるとおり、JSON では数値に double 型を使う、などとは書かれていません。単に「10 進数で小数点を含んでもいいし指数表現も OK」とだけ規定されているだけです。</p>

<blockquote>
  <p>JSON <br />
<a href="http://json.org/json-ja.html">http://json.org/json-ja.html</a></p>
</blockquote>

<p>こっちは日本語ですね</p>

<p>なので、JSON で 53bit を越える整数値を表現することは全然可能なわけです。</p>

<p>じゃあ XML ならどうなんだというと、<a href="https://api.twitter.com/1/statuses/show/10137062187474945.xml">この例</a> を見てもわかるとおり、以前から存在する id タグに 53bit 以上が必要になったツイート ID が普通に入ってます。 <br />
XML でも同様に、表現できる数値の範囲に制限はありません。仕様上は 1024bit 整数値でも表せます。</p>

<p>だったら JavaScript でもブラウザ上ならパースできるから XML に切り替えればいいのね、なんていうのはもちろん間違いです。</p>

<p>今回の原因は「JavaScript やその派生の ActionScript では 53bit を越える整数が扱えない」に尽きます。詳しい説明は、最初に引用した記事にあるとおりです。</p>

<p>JavaScript の仕様として「数値はすべて 64bit 浮動小数点型」と決まっているので、53bit 以上の大きい整数は扱えません。 <br />
JSON だろうが XML だろうが JavaScript を使っている限り同じ症状が出ます。</p>

<blockquote>
  <p>Snowflake: An update and some very important information - Twitter Development Talk | Google グループ <br />
http://groups.google.com/group/twitter-development-talk/msg/c30b408a6c09e3d1</p>
</blockquote>

<p>Twitter のエンジニアも「JavaScript のようないくつかの言語では 53bit を越える整数を扱えないので注意して」と言ってますし、回避策も「数値でなく文字列値も並列するのでそっち使って」とあります。 <br />
JSON に原因があって XML なら問題なし、というのなら、JavaScript とかは XML に切り替えるって回避策も有効なはずですが、それはありません。あくまで言語の仕様が原因ですよということです。</p>

<p>そもそも JSON で 53bit 以上の整数が扱えないのなら、最初からそんな「ルール違反」な JSON 文字列を Twitter が採用できるわけがないはず。</p>

<p>「XML ならだいじょうぶ」という根拠はどこにもないのに、どうしてこういう考え方が出てくるのか...。</p>

<p>ほかの言語で書かれた JSON デコーダーの場合はその実装次第ですが、JSON はあくまで「ただの文字列」とほかのデータ型とを相互変換するための約束事としてまとめられた書式ですので、たいていは「ただの文字列」として取り出せたり、数値や配列といったほかの形式と見なして取り出せるように作られてます。</p>

<blockquote>
  <p>JSON in Java <br />
http://www.json.org/java/index.html</p>
</blockquote>

<p>たとえば Java で書かれたこの JSON パーサーでは、与えたキーから取り出す値のデータ型はプログラムで指定するようになっているので、今回 Twitter の仕様変更で追加されたキー id_str の値を long 型で取り出すこともできます。 <br />
内部的にはすべての値を「ただの文字列」としてパースして取り出すときに指定された型に変換してるだけで、利用目的が決まっていないライブラリとしては正しい方針だと思います。</p>

<p>JavaScript ベースだから数値は全部 64bit 浮動小数点型でパースしようなんてのは JSON の仕様を理解してる人からは出ない発想でしょうし、ほかの言語なら数値の内部表現にもいろんなタイプがありますからそれらを有効利用できるような実装にするのが自然だと思います。</p>

<p>今回の問題は、あくまで「言語仕様による整数の内部表現の制約」が原因なので、XML なら解決するといった見方はできないと思います。</p>

<p>JSON に罪はありません。</p>
]]>
    </content>
</entry>

<entry>
    <title>JavaScript の文字列連結ベンチマーク</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/11/javascript-1.html" />
    <id>tag:archive.guma.jp,2010://2.28</id>

    <published>2010-11-25T03:44:00Z</published>
    <updated>2014-08-30T20:52:12Z</updated>

    <summary>JavaScript で文字列を繋げるのは += 演算子がベター、というのがもう...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>JavaScript で文字列を繋げるのは += 演算子がベター、というのがもう 1 - 2 年ほど前から定石になっているらしい。</p>

<p>以前は「Array.push() で配列に格納して Array.join() で繋げる」ほうが圧倒的に処理速度が速かったんですが、最近のブラウザだと最適化が進んでパフォーマンスは逆転してるそうな。</p>

<blockquote>
  <p>JavaScript で、文字列の連結は "+=" が高速 - 地潜の日記 <br />
<a href="http://blog.livedoor.jp/jimuguri/archives/51347962.html">http://blog.livedoor.jp/jimuguri/archives/51347962.html</a></p>
</blockquote>

<p>そういうことを google 検索で上位に来る上のブログで知ったんですけど、この人また他人の書いた記事を鵜呑みにしてて、なぜ配列を使うのが常識になっていたのかも知らないみたいなのでアテになりません。</p>

<p>なので実測。</p>
]]>
        <![CDATA[<p>s += "0" と var s = []; s.push("0") とを 100 - 5,100 回ほど繰り返す処理をそれぞれ 5,000 回ループさせた結果が下のような感じでした。いずれも Windows XP SP3 上でいくつかアプリ起動してる一般ユーザー的な環境で実行してます。 <br />
なんで連結の繰り返し数に幅があるのかっていうと、ここを固定にしてたらコンパイラが最適化したらしく同じような結果しか出てこず計測にならなかったので、5,000 回ループ中に 1 ずつ増える感じにしたからです。</p>

<p>せっかくなので、"0" 1 文字連結する場合と "000..." と 64 文字連結する場合の 2 種類を計ってみました。</p>

<h3>Firefox 3.6.12</h3>

<pre><code>s += "0" 結果 : 761 ms  
s.push("0") 結果 : 2,120 ms
</code></pre>

<h3>Chrome 9.0.587.dev</h3>

<pre><code>s += "0" 結果 : 144 ms  
s.push("0") 結果 : 411 ms

s += "000..." 結果 : 204 ms  
s.push("000...") 結果 : 1,507 ms
</code></pre>

<h3>Safari 5.0.1 (7533.17.8)</h3>

<pre><code>s += "0" 結果 : 980 ms  
s.push("0") 結果 : 823 ms

s += "000..." 結果 : 964 ms  
s.push("000...") 結果 : 2,271 ms
</code></pre>

<h3>IE 8.0.6001.18702</h3>

<pre><code>s += "0" 結果 : 6,109 ms  
s.push("0") 結果 : 8,047 ms

s += "000..." 結果 : 6,235 ms  
s.push("000...") 結果 : 10,999 ms
</code></pre>

<p>適当すぎるアレですけど、<a href="/sample/benc.html">ここ</a>に使ったコード置いときますね。</p>

<p>Firefox で 64 文字版のテストはどうにも結果が返ってこず、ひどいときは Firefox が落ちてしまう場合もあったので計測不可ってことで。</p>

<p>結果から見て、どのブラウザでもおおむね s += "0" を使ったほうが速いって感じですね。 <br />
Safari は s.push() が 1 文字だとむしろ速いぐらいなのに 64 文字だと大幅に遅くなってて不思議。</p>

<p>JavaScript の文字列は名前だけ発祥元の Java を受け継いだのか immutable 不変オブジェクトになってます。 <br />
不変って文字のとおり、たとえば "1234" という文字列は一度メモリに割り当てられたら最後、二度と変化しないってことです。</p>

<p>変化しないので、"1234" + "0" という処理は「ふたつの文字列長を足した 5 文字分のメモリを確保して、そこにそれぞれコピーする」というわりと手間がかかるやり方でたいてい実行されてます。 <br />
s += "0" という命令文は、s と "0" を足した長さで確保したメモリにそれぞれをコピーした後のアドレスを s に持たせる、という感じになるわけです。</p>

<p>処理中はコピー元とコピー先が同時に存在することになるのでメモリ消費も一時的には倍になるという富豪的プログラミングな感じですが、毎回すべての文字列をコピーしまくるってのは実に効率の悪い処理になります。 <br />
なので、実装によっては最初から文字列の長さより多めのメモリを確保しつつその文字列の長さを別に持っておいて、連結の際は余ってるバッファにコピーしてから文字列の長さを足す、という感じになってる場合もあるんじゃないですかね。Java でいうところの StringBuilder のようなもので。</p>

<p>余ってるバッファだけでは足りない場合は結局メモリの再確保と全コピーという無駄な処理が必要になってしまいますけど、余らせるバッファ長をできるだけ賢く確保させるのが、開発者の腕に見せ所にもなるわけで、あるいはこの辺の調整が各ブラウザの速度差に関係してるかもしれません。</p>
]]>
    </content>
</entry>

<entry>
    <title>PC の時計が遅れていたら Twitter 専用クライアントは使えない</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/11/pc-twitter.html" />
    <id>tag:archive.guma.jp,2010://2.27</id>

    <published>2010-11-22T17:53:00Z</published>
    <updated>2014-09-08T07:55:48Z</updated>

    <summary>Twitter の専用クライアントをセットアップするときの FAQ として「アカ...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Twitter" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="チャーハン諸島" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>Twitter の専用クライアントをセットアップするときの FAQ として「アカウントが認証されなくて使えない」というのがあります。</p>

<p>Web だと普通にログインできるアカウントが ID もパスワードも合ってるのに専用クライアントでエラーになってしまう原因は、ほぼまちがいなく <strong>PC 内蔵時計がズレてる</strong>です。</p>

<p>認証に使われてる OAuth / xAuth プロトコルが<a href="http://openid-foundation-japan.github.com/draft-hammer-oauth-10.html#verify_request" title="The OAuth 1.0 Protocol">タイムスタンプも利用する仕様</a>になっていて、このタイムスタンプがあまりに古いと Twitter では不正なリクエストとしてエラーにされてしまいます。 <br />
専用クライアントではタイムスタンプを PC 内蔵時計から得ているので、時計が狂ってるとエラーになってしまうのはそういうわけです。
これは OAuth の仕様で「タイムスタンプが古いリクエストは拒否ってもいいよ」となっていることを受けての Twitter 側の仕様のようです。</p>

<p>で、実際どれくらいズレてたらアウトなのか、手っ取り早く実際に PC の時計ずらして試してみました。</p>

<p>結果からいうと、<strong>「タイムスタンプが約 15 分以上標準時から遅れていたら Twitter の OAuth / xAuth 認証は失敗する」</strong>でした。 <br />
未来に進んでた場合は 24 時間以上先に進めても問題なかったです。</p>

<p>標準時ってのは世界標準時のことですけど、普通ローカルタイムに合わせてる PC 内蔵時計でも OS やプログラムがうまいこと世界標準時に換算して扱ってくれるので、とりあえず時報に時計合わせしとけば特に気にする必要はないです。</p>

<p>専用クライアントでアカウントがうまく設定できない、という人は PC の内蔵時計が遅れてないかチェック、というか <strong>ほぼ 100 パーセント遅れてる</strong>から時計合わせすれば解決します。きっと。</p>

<p>手前味噌ですけど、Twitter クライアントアプリ･<a href="/rice-islands.html">チャーハン諸島</a>には PC の時計が指す日時をポストしてくれる「妖怪ちくわぶ」機能がありますから、日時のズレをチェックしたい場合にはお勧めです！というか、15 分以上遅れてたら認証失敗でポストもできないので意味ないですけど！</p>

<p>15 分も時計が遅れてて認証失敗してたら、Twitter サーバのレスポンスに含まれる日時に時計合わせしてくれるクライアントアプリが存在してもいいと思う。チャーハン諸島は Java なので時計合わせとか超敷居高くてほぼ実装できない機能ですけど。</p>

<p>Twitter のサーバ時計との差が問題なら Twitter サーバの時計に合わせるべきなんじゃないか、という話もあるかと思いますが、Twitter は時刻合わせ用のサーバを公開していませんし、そもそもインターネットの標準として UTC 協定世界時に同期されているので、ユーザー側も自国の標準時に同期させれば問題ありません。</p>
]]>
        
    </content>
</entry>

<entry>
    <title>Java で bit.ly API を SSL で呼び出す</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/11/java-bitly-api-ssl.html" />
    <id>tag:archive.guma.jp,2010://2.26</id>

    <published>2010-11-17T02:37:00Z</published>
    <updated>2014-09-09T23:30:36Z</updated>

    <summary>URL 短縮サービスの bit.ly には OAuth 認証用とかで SSL で...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>URL 短縮サービスの <a href="http://bit.ly/">bit.ly</a> には OAuth 認証用とかで SSL でアクセスできる API のエンドポイント https://aps-ssl.bit.ly/ があるんですけど、ここのサーバ証明書が <a href="http://www.startssl.com/">StartSSL</a> って CA のを使ってて、その CA 証明書を Java 標準のキーストアが持っていないからさあ大変。 <br />
知らない CA に署名された証明書だから "sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target" なんて例外が出て通信できません。</p>

<p>Firefox とかでアクセスしてみると普通に成功するので、うさんくさい CA ではないようです。一般的な Web ブラウザがルート証明書を持っているなら信用して問題ないはず。</p>

<p>単に使う分には %JAVA_HOME%/lib/security/cacerts に CA のルート証明書をインポートすればいいんですが、Java のアップデートで cacerts が変わるたびにインポートしないといけないしめんどくさいです。 <br />
今回の bit.ly のように StartSSL 使ってるとこに SSL でアクセスするときだけ StartSSL のルート証明書を読むようにすればいいだけなので、専用のキーストアを作って読み込むようにしてみました。</p>
]]>
        <![CDATA[<p>キーストアの作り方は簡単で、Java 添付の keytool というコマンドラインアプリを使います。 <br />
証明書は IE や Firefox、Chrome といった Web ブラウザからエクスポートもできますが、CA 自身が配布してるファイルをダウンロードするのが正攻法でしょう。 <br />
<a href="https://www.startssl.com/certs/">https://www.startssl.com/certs/</a> からルート証明書 ca.crt と中間証明書 sub.class2.server.ca.crt の 2 ファイルをゲットしておきます。bit.ly のサーバ証明書は class2 ってやつで署名されてるので、両方ないと認証されません。</p>

<pre class="prettyprint">
>keytool -importcert -keystore キーストアのファイル名 -store_pass キーストアのパスワード -trustcacerts -alias startcom.ca -file ca.crt
>keytool -importcert -keystore キーストアのファイル名 -store_pass キーストアのパスワード -alias startcom.ca.class2 -file sub.class2.server.ca.crt
</pre>

<p>「キーストアのファイル名」と「キーストアのパスワード」は Java コードから参照するのでメモっときましょう。それほど重要なファイルでもないのでパスワードは適当でもいいと思います。</p>

<p>キーストアが用意できたら、あとは SSL 通信する時にこのキーストアを使うようにすればいいだけです。 <br />
Java での SSL 通信は javax.net.ssl.HttpsURLConnection を使うと簡単、というか java.net.URL.openConnection() で https な URL を開けばこれが返ってきます。 <br />
HttpsURLConnection の connect() を呼び出す前に setSSLSocketFactory(SSLSocketFactory sf) を呼び出して、用意したキーストアを利用する SSLSocketFactory を渡してやります。</p>

<p>通信のたびにキーストアを読み込んだりするのもアレなので、下のようなクラスで必要なときだけ installCaCert() に HttpsURLConnection オブジェクトを渡してやるといいと思います。 <br />
キーストアファイルは class ファイルと同じディレクトリに置く必要がありますが、別の場所に置きたければ StartComCert.class.getResourceAsStream(storeName) の部分を書き換えてやってください。</p>

<pre class="prettyprint">
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

public class StartComCert {

    private static final ThreadLocal<SecureRandom> random = new ThreadLocal<SecureRandom>(){
        @Override
        protected SecureRandom initialValue() {
            return new SecureRandom();
        };
    };

    private static final KeyManager[] keyManager;
    private static final TrustManager[] trustManager;

    static {
        try {
            String storeName = "キーストアのファイル名";
            char[] storePass = "キーストアのパスワード".toCharArray();
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(StartComCert.class.getResourceAsStream(storeName), storePass);

            KeyManagerFactory keyManFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManFactory.init(keyStore, storePass);

            TrustManagerFactory trustManFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManFactory.init(keyStore);

            keyManager = keyManFactory.getKeyManagers();
            trustManager = trustManFactory.getTrustManagers();
        } catch (Exception e) {
            throw new RuntimeException("Failed to load KeyStore", e);
        }
    }

    private StartComCert() {
        throw new AssertionError("should not invoke");
    }

    public static void installCaCert(HttpsURLConnection conn) {
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(keyManager, trustManager, random.get());
            conn.setSSLSocketFactory(sslContext.getSocketFactory());
        } catch (KeyManagementException ignore) {
            throw new AssertionError("will not happen");
        } catch (NoSuchAlgorithmException ignore) {
            throw new AssertionError("will not happen");
        }
    }
}
</pre>

<p>たぶん JRE 1.4 以降なら動きます。 <br />
これで https://aps-ssl.bit.ly/ にも問題なくアクセスできるはずです。配布する場合もキーストアごと jar にまとめればいいだけなのでラクですね。</p>

<p>KeyManager[] と TrustManager[] ってスレッド意識せず共有してしまっていいのかどうかかいまいち不明なので、もしアレならこれも ThreadLocal に持たせてしまったほうがいいんでしょうね。</p>
]]>
    </content>
</entry>

<entry>
    <title>Twitter の User Streams とは</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/09/twitter-user-streams.html" />
    <id>tag:archive.guma.jp,2010://2.24</id>

    <published>2010-09-15T10:57:17Z</published>
    <updated>2014-09-09T20:10:15Z</updated>

    <summary>発表は何か月も前でしたが、最近になって対応クライアントがリリースできるようになっ...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Twitter" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>発表は何か月も前でしたが、最近になって対応クライアントがリリースできるようになってきて再注目を浴びてきた Twitter の <strong>User Streams</strong>。</p>

<p>User Streams とはいったい何なの？スリップストリームの仲間？とか微妙なボケをかます人もいるかもしれませんけど、どういうものなのかかんたんに説明しつつ自作アプリの紹介などしていきたいと思います。</p>
]]>
        <![CDATA[<h1>User Streams とは</h1>

<p>かんたんに言うと、<strong>いつものタイムラインがほぼリアルタイムで更新される API</strong>です。</p>

<p>公式のドキュメントは、開発向けの英語版だけですけど <a href="http://dev.twitter.com/pages/user_streams">http://dev.twitter.com/pages/user_streams</a> にあります。</p>

<p>ツイートをリアルタイムに取得できる Streaming API というものは以前から普通に公開されていたのですが、取得できるものはキーワード検索とかの限られたツイートだけで、いつもの自分のタイムラインを取ってくるのは不可能でした。 <br />
なので、Twitter 社と契約を結んだ一部の大手サービス企業(google とか)を除いて、普通のタイムラインをリアルタイムに取得するとかありえなかったわけです。</p>

<p><strong>User Streams</strong> はこれまでのタイムライン取得と何が違うかというと、<strong>Web ブラウザでいうところ「リロード」が不要</strong>って点に尽きます。</p>

<p>これまでのタイムライン取得は、Web にしろ専用クライアントにしろ、定期的にサーバーへ問い合わせ、自分のタイムラインにあるツイートがあればユーザー側から取りに行っていました。 <br />
で、サーバー側は問い合わせを受けて、その内容に応じて新着分なり既存分なりのツイートを探して集めてユーザー側に送り返しているのです。このタイムライン取得というリクエストに、こないだのワールドカップで規制されたりして話題によく上る <strong>API</strong> 利用回数が消費されています。</p>

<p>これはたとえ話でも何でもなく、専用クライアントでも定期的にリロードしてタイムライン上のツイートを受け取って表示していたのです。いや、今でもこのあたりは変化ないので「表示している」ですね。見た目にわからないだけで、ブラウザで F5 押す操作を自動的に実行していただけと。</p>

<p>ところが User Streams のほうは、一度サーバーへリクエストしたらユーザー側から切断するまでずっと、自分がフォローしてる人のツイートが届いた瞬間に、ほぼタイムラグなくユーザーに横流ししてくれるのです。 <br />
stream(連続した流れ)というだけあって、こちらからいちいち「新しいの、ある？」とか訊ねるまでもなく、サーバーのほうから「はい新着ツイートでーす」とどんどん送ってきてくれるわけです。</p>

<p>いつも数件、フォローの多い人なら数十件のツイートが断続的に現れていたタイムラインを見慣れた人からすると、まるでチャットのようにポストされたてのツイートが次々に流れてくる様はとても新鮮です。 <br />
フォロー数の多い人なら、時間帯によってタイムラインが流れる速さになんとなく差があるように感じることも多かったと思いますけど、その「速さの差」が直接体感できる、といったらわかりやすいでしょうか。ポストの多い時間帯だとものすごい勢いでタイムラインが流れていきますし、ポストの少ない時ならタイムラインがほとんど動きません。</p>

<h1>User Streams で受け取れるもの</h1>

<p>User Streams のもうひとつの特徴は、<strong>「ツイート以外のイベントもリアルタイムに届く」</strong>です。</p>

<p>ざっと箇条書きで並べると、</p>

<ul>
<li>ツイートの削除</li>
<li>DM の送信</li>
<li>送信済み DM の削除</li>
<li>他人からのフォロー</li>
<li>自分のツイートの「お気に入り」登録</li>
<li>公式 RT</li>
<li>リストへの登録･抹消</li>
<li>自分のリストのフォロー / リムーブ</li>
</ul>

<p>といった「イベント」もリアルタイムで通知されます。</p>

<p>操作ミスとかに気付いてポストしたばかりのツイートを削除する、なんてことは誰しも経験してると思いますが、User Streams を利用しているフォロワーがいれば「あ、このツイート削除したんだ」とその瞬間に知られるわけで、人によってはわりと恐ろしい感じです。 <br />
とはいえポストしたものはその時点で公開されるわけですし、さっき見たツイートが消えていれば誰だって削除したんだと気付くわけですから、ミスったツイートを誰にも知られず抹殺することは事実上不可能ですし。後で消さなきゃいけないようなヤバいポストはするなよってゆー当たり前のことをしてればいいだけです。</p>

<p>「お気に入り」登録、いわゆる「ふぁぼられ」や公式 RT なんかも、誰かが操作した瞬間に通知されます。 <br />
これは相手がフォローしてる・してない無関係に「自分のツイート」がふぁぼられ・RT されたものすべてが対象になるっぽいですから、手が空いたらふぁぼったーや favstar チェックしないと気が違いそうになるほどのふぁぼられ乞食にはたまらない機能ですね。あ、これは僕のことなんですけど。
毎日大量にふぁぼられてる人は逆に迷惑かもしれませんから、そういうのをうまいこと処理してくれるクライアントを探すしかないですね。チャーハン諸島とかいかがですか？</p>

<p>あと他人からフォローされた通知が来るのはいいんですけど、他人からリムーブ or ブロックされたよ通知は来ないのがちょっとアンバランス。せめてリムーブ通知だけでも来てくれれば「誰もリムらないで...でも誰からリムられたかは知っておきたい...////」なんて複雑な乙女心も満たせてド M なユーザー大喜びだと思うんですけど。</p>

<p>ほかにも自分の操作によるフォローやリムーブ、ブロック登録なんかも通知されるんですが、自分で操作してるものをリアルタイムでお知らせされても大半は「知ってるよ！」な情報ですからあんまりどうでもいいですね。悪党に自分のアカウントを乗っ取られて知らないうちに...といった最悪の事態にすぐ気づけるきっかけになるかも、という点では有用かと。</p>

<p>ただ肝心な点は、これらのイベントも対応したクライアントでなければ何も出てこないので意味がないってことですね。 <br />
これまでの専用クライアントは基本的に「ツイートをいかにうまく表示するか」という点にのみ注力してるものばかりで、それ以外のイベントに関しては知りようがなかったのもあって扱い方からしてそもそもノーマークだったりしますから、単に User Streams に対応しましたってだけの既存クライアントではこうしたイベントから生まれるメリットは得られないかもしれません。</p>

<p>どのみち、これらのイベントも「リアルタイム」に届くってことで、それが発生したときに User Streams 開いてないといけないわけですから、四六時中 Twitter に張り付いてるタイムライン監視員でもないと拾う機会もそれほどないかもですし。</p>

<h1>User Streams の注意点</h1>

<p>という風に、タイムラインをチェックするという目的においてはかなり最強に強まった感が漂いまくる User Streams ですが、万能というわけではありません。</p>

<p>まず第一の問題点は、いうまでもなく <strong>まだβテスト中</strong> ということです。</p>

<p>クライアント作者とかが実験的に使う場合は公開されてる URL で試せるんですが、これはプレビュー版でサーバーのキャパとか超低いのでアプリとしてリリースするものには使っちゃダメよってことになってます。 <br />
リリースしたい場合は Twitter 社にメールして許可をもらえばいいんですけど、当然開発向けには英語しかサポートしてないですから、あとはわかるな的な。</p>

<p>もうひとつの問題点は、<strong>流速が速くなりすぎると取りこぼしが生じる</strong>ことです。</p>

<p>これは従来の TL 取得でも「取得漏れ」があったのであまり変わりがない感じですが、従来のほうは最新から何件とかを指定して「ツイートくれ」とリクエストしたものはだいたい確実に取ってこられたので、自分の TL の流速に合わせて取得間隔や件数を調整すれば取得漏れはほぼ防げました。</p>

<p>User Streams のほうは、ストリームとして流すより速くポストされまくると「流され待ち」のツイートがどんどんたまってきて、これが一定数(たぶん未公開)を越えるとスキップされて流されなくなるようです。 <br />
一応「多かったのでスキップしたよ。飛ばした件数はいくつだよ」という通知はされるので、気が利いたクライアントならその飛ばされた範囲を推測して従来の方法で再取得する、といった気の利いた処理をしてくれるでしょうけど、これはクライアント次第ですので過度の期待はできません。</p>

<p>ストリームに流せないほど大量にポストされてる状況なら、多少の漏れはスルーしたほうがユーザー的にも幸せな感じはしますし、自分で書いておいてアレですけどいうほどの問題点ではない気もします。 <br />
それに、上に書いたように「飛ばされた範囲を推測して再取得」というのは従来のクライアントではかなり難しい処理だったものが、ストリームから「順番飛ばしました」通知を受け取れることでやさしくなった、とも取れますから、総合すると問題の深刻さが軽くなったとも取れますし。</p>

<h1>User Streams を体験したい</h1>

<p>現時点で、正式に User Streams β版に対応したクライアントがいくつか存在します。 <br />
知っているかぎりだと以下のとおり。抜けがあったらすみません。</p>

<ul>
<li><a href="http://sites.google.com/site/yorufukurou/">夜フクロウ</a> (Mac OS X 10.5.8 以降専用)</li>
<li><a href="http://www.echofon.com/twitter/mac/">Echofon for Mac</a> (Mac OS X 10.5 以降専用)</li>
<li><a href="http://www.tweetdeck.com/streaming/">TweetDeck BETA Ver.0.35.0.1</a> (Win &amp; Mac &amp; Linux 対応、要 Adobe Air)</li>
<li><a href="http://seesmic.com/seesmic_desktop/sd2/">Seesmic Desktop 2</a> (Win &amp; Mac、要 Silverlight)</li>
<li><a href="http://archive.guma.jp/rice-islands.html">チャーハン諸島</a> (Win &amp; Mac &amp; Linux 対応、要 Java 6)</li>
</ul>

<p>わりと Mac 有利な情勢で、その他の OS では何かしらのランタイムが必要になるのが現状です。というか、今のところ OS ネイティブアプリって夜フクロウさんだけなんじゃないかしら。ステキ。</p>

<p>最近の PC だと Java にしても Air にしてもじゅうぶん使い物になるパフォーマンスですし、ちょっと試してみるためだけでもインストールしても困りはしないと思います。 <br />
どちらかといえば実績豊富な Java がいちばん信用できるとこですし、そうなると「チャーハン諸島」一択ですね！手前みそですみません！ごめんなさい！</p>

<p>わりと一般論で専門的な話をすると、Win 用のクライアントは有名･無名を問わず Twitter の API レスポンスに XML を選んでる場合が多いんですけど(そのほうが開発が楽だから)、User Streams は今のところこれをサポートしてなくて JSON しか使えないんですね。 <br />
なので今の実装でレスポンスを解釈することができないので、JSON が解釈できるライブラリなどを付け足すか、いっそ XML を捨てて JSON に乗り換えるかというわりと大きな仕様変更・追加実装が必要になるので、対応はなかなかアレなんじゃないでしょうか。とか言ってたらサクッと対応してきたりしてまた赤っ恥かくことになるかもしれませんから、作者の人たちに期待しますとオトナ的なお茶の濁し方で曖昧な感じにしていきたいと思います。</p>

<p>公式のドキュメントによると、PC 向けのクライアントは User Streams への移行し、従来の REST API は補助的に使う程度であまり依存しないようにすることがもう既に推奨されていたりするので、特にフリーソフト作者の日曜プログラマの皆さんは腕に見せ所ですね。</p>
]]>
    </content>
</entry>

<entry>
    <title>Twitter の API 制限が増やせるとか</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/09/twitter-api.html" />
    <id>tag:archive.guma.jp,2010://2.23</id>

    <published>2010-09-07T07:29:31Z</published>
    <updated>2014-09-09T23:21:26Z</updated>

    <summary>Twitter の BASIC 認証が廃止されて、外部アプリ・サービスは OAu...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Twitter" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="メモ" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="twitter" label="Twitter" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>Twitter の BASIC 認証が廃止されて、外部アプリ・サービスは OAuth 認証が必須になったわけですが、この OAuth 認証パラメーターの渡し方でおもしろいことに気付きました。</p>
]]>
        <![CDATA[<p>通常、OAuth 認証は次のようにアカウント認証に必要なパラメーターを HTTP リクエストの Authorization ヘッダで渡します。</p>

<pre class="prettyprint">GET /1/statuses/home_timeline.json?count=100 HTTP/1.1
Authorization: OAuth oauth_consumer_key="???",oauth_signature_method="HMAC-SHA1",oauth_timestamp="0000000000",oauth_nonce="???",oauth_version="1.0",oauth_token="00000000-????",oauth_signature="????"
Host: api.twitter.com
Accept-Encoding: gzip, deflate
User-Agent: boeboe
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
</pre>

<p>これは BASIC 認証と同じ方法で、Authorization ヘッダにあるパラメーターでもってこのリクエストがどのアカウントを使ったものかを特定できるようになっています。ここに OAuth プロトコル的な正しいパラメーターを入れておかないと「アカウントが違います」的なエラーになる、という寸法ですね。</p>

<p>上の例ではセキュリティ的なアレを考慮して「正しいパラメーター」を全部消してますけど、ちゃんとやれば次のようなレスポンスが返ってきます。</p>

<pre class="prettyprint">HTTP/1.1 200 OK
Date: Tue, 07 Sep 2010 06:25:30 GMT
Server: hi
Status: 200 OK
X-Transaction: 0000000000-00000-00000
X-RateLimit-Limit: 350
ETag: "4f05766016563d89d76886504b8e9a39"-gzip
Last-Modified: Tue, 07 Sep 2010 06:25:30 GMT
X-RateLimit-Remaining: 349
X-Runtime: 0.13102
Content-Type: application/json; charset=utf-8
Pragma: no-cache
X-RateLimit-Class: api_identified
X-Revision: DEV
X-RateLimit-Reset: 1283844330
Set-Cookie: k=???; path=/; expires=Tue, 14-Sep-10 06:25:30 GMT; domain=.twitter.com
Set-Cookie: guest_id=0000; path=/; expires=Thu, 07 Oct 2010 06:25:30 GMT
Set-Cookie: lang=en; path=/
Set-Cookie: _twitter_sess=????; domain=.twitter.com; path=/
Vary: Accept-Encoding
Content-Encoding: gzip
Connection: close</pre>

<p>例によってクッキーとかセッション ID とか全部消してますけど、このレスポンスにある X-RateLimit-Limit ヘッダに「最大 API 呼び出し回数」が、X-RateLimit-Remaining ヘッダに「残り回数」が入ってます。上の例だと 349 / 350 回、所定の API が呼び出せまっせ、ということになります。 <br />
で X-RateLimit-Reset ヘッダにある時間(値は 1970/1/1 0:00:00 からの経過秒数)に X-RateLimit-Remaining がリセットされますよ、ということもわかります。</p>

<p>ところが、Twitter だと OAuth 認証に必要な「正しいパラメーター」を違う渡し方をしても通ってしまうようなんですね。</p>

<p>その渡し方とは、次のように「正しいパラメーター」を  GET の QUERY_STRING に渡してしまうというもの。</p>

<pre class="prettyprint">GET /1/statuses/home_timeline.json?count=100&oauth_consumer_key=????&oauth_nonce=????&oauth_signature_method=HMAC-SHA1&oauth_timestamp=0000000000&oauth_token=00000000-??&oauth_verifier=0000000&oauth_version=1.0&oauth_signature=???? HTTP/1.1
User-Agent: boeboe
Content-Type: application/x-www-form-urlencoded
Host: api.twitter.com
Connection: keep-alive</pre>

<p>よく見ると oauth_verifier という上の例には付いてないパラメーターが付いてますけど、これは OAuth 認証手順の最初のほうにある「アクセストークンの取得」に必要なものですね。つまり API に渡すパラメーターにプラスして OAuth 認証のアクセストークン取得パラメーターを付けてるわけです。 <br />
これは POST のリクエストボディを使っても同様のようです。上の例では GET 使ってるので Content-Type ヘッダは不要ですけど、コピペしたら付いてきたのでそのままにしてるだけです。</p>

<p>こんな「間違った」やり方でも普通に自分のアカウントとして認証されて、レスポンスも全く問題なく得られるんですけど、その内容が少し違ってます。</p>

<pre class="prettyprint">HTTP/1.1 200 OK
Date: Tue, 07 Sep 2010 06:24:33 GMT
Server: hi
Status: 200 OK
X-Transaction: 0000000000-00000-0000
X-RateLimit-Limit: 150
ETag: "c532a31f7aecfca1c532c63ad654275d"
Last-Modified: Tue, 07 Sep 2010 06:24:31 GMT
X-RateLimit-Remaining: 149
X-Runtime: 1.42691
Content-Type: application/json; charset=utf-8
Pragma: no-cache
X-RateLimit-Class: api
X-Revision: DEV
X-RateLimit-Reset: 1283844271
Set-Cookie: k=????; path=/; expires=Tue, 14-Sep-10 06:24:31 GMT; domain=.twitter.com
Set-Cookie: guest_id=????; path=/; expires=Thu, 07 Oct 2010 06:24:31 GMT
Set-Cookie: lang=en; path=/
Set-Cookie: _twitter_sess=????; domain=.twitter.com; path=/
Vary: Accept-Encoding
Connection: close</pre>

<p>このレスポンスだと X-RateLimit-Limit ヘッダが 150 に、X-RateLimit-Remaining ヘッダが 149 になってます。つまりあと 149 回所定の API 呼び出しができるってことで、最初の例と違う値になってるんですね。</p>

<p>タイムスタンプはいじってないので、どちらも続けて送ったリクエストです(実はふたつめの例のほうが先に実行されてるのもバレバレ)。 <br />
両者の数字の減り方は独立してて一方が 349/350 でもう一方が 149/150 だから、この 2 種類を使い分ければ 498/500 の API 呼び出しが可能になってしまう、と。  </p>

<p>Twitter の OAuth 認証は API 呼び出し回数にアカウント毎の制限をはかけるのも目的のひとつっぽいですが、<strong>本来 350 回のところプラス 150 回余計に TL 取得したりユーザー情報見たりできるわけですよ奥さん！</strong></p>

<p>これは普通に exploit だろ...常識的に考えて...。</p>

<p>なのでこれを利用して何かしら不利益を被ってもわたしは知りませんから試したければ自己責任でどうぞだし、Twitter に報告したいけど英語でこれを説明するの骨が折れるのでまだやってないわけだし、周辺調査とかもしてないから既知の現象かどうかも知らないだしし！  </p>

<p>あと関係ないけど API コールでも cookie 喰わせようとしてるってことは、クライアント的には cookie 食べた振りしたほうがサーバーに対して優しい振る舞いになったりするんですかね。</p>
]]>
    </content>
</entry>

<entry>
    <title>チャーハン諸島 for Mac</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/05/-for-mac.html" />
    <id>tag:archive.guma.jp,2010://2.21</id>

    <published>2010-05-30T15:59:40Z</published>
    <updated>2014-09-07T19:04:44Z</updated>

    <summary>   Twitter クライアント「チャーハン諸島」の詳細はこちら  http:...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Java" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Twitter" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="チャーハン諸島" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="java" label="Java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="twitter" label="Twitter" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="チャーハン諸島" label="チャーハン諸島" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<blockquote>
  <p>Twitter クライアント「チャーハン諸島」の詳細はこちら <br />
<a href="http://archive.guma.jp/rice-islands.html">http://archive.guma.jp/rice-islands.html</a></p>
</blockquote>

<p>チャーハン諸島を Mac OS X 用アプリケーションにパッケージングしてみました。</p>

<p>チャーハン諸島 for Mac OS X (Snow Leopard 以降) <br />
<a href="http://archive.guma.jp/rice/rice_Mac.zip">http://archive.guma.jp/rice/rice_Mac.zip</a> <br />
※当初公開したものは、全く起動できないという斬新な新機能が追加されてしまっていたので差し替えました。</p>

<p>といっても中身はこれまでとまったく同じで、機能的に何も増えたり減ったりしてません。使い方も一緒です。 <br />
MacBook Pro 買って嬉しそうにいろいろいじってたらできただけです。</p>

<p>Mac は Java アプリを普通のアプリっぽくパッケージングするツールが素で添付されてるんですね。 <br />
こういうプログラマー優遇なところはありがたいです。</p>

<blockquote>
  <p>「チャーハン諸島」に関する<a href="http://archive.guma.jp/cat7/">記事一覧</a></p>
</blockquote>
]]>
        
    </content>
</entry>

<entry>
    <title>「チャーハン諸島」 Version 0.07 リリース</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/05/-version-007.html" />
    <id>tag:archive.guma.jp,2010://2.20</id>

    <published>2010-05-24T08:26:31Z</published>
    <updated>2010-06-11T08:22:15Z</updated>

    <summary>   Twitter クライアント「チャーハン諸島」の詳細はこちら  http:...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Java" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Twitter" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="チャーハン諸島" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="java" label="Java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="twitter" label="Twitter" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="チャーハン諸島" label="チャーハン諸島" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<blockquote>
  <p>Twitter クライアント「チャーハン諸島」の詳細はこちら <br />
<a href="http://archive.guma.jp/rice-islands.html">http://archive.guma.jp/rice-islands.html</a></p>
</blockquote>

<p>Twitter クライアント「チャーハン諸島」をバージョンアップしました。 <br />
今回は API 規制にお悩みの方にはわりと目立つ変更点があるので、ブログの記事も新たに書き起こしてみようかと。</p>

<p>最新バージョンの Ver.0.07 での主な変更点は次のとおり。</p>

<ul>
<li>メインとサブの 2 種類のアカウントを登録できる。</li>
<li>投稿時、メインアカウントが API 規制を受けていたらサブアカウントに切り替えて投稿する機能を追加。</li>
<li>投稿パネルのサイズを 4 パターンから選択できるようにした。</li>
</ul>

<p>要するに、アカウント 2 個用意しとけば実況等で盛り上がって投稿しすぎたとしても、エラーやアカウントの切り替えに煩わされずに済む、という感じで。</p>

<p>メインアカウントを優先して利用するようにしているので、メインが API 規制を受けていないかぎりはサブアカウントでログインすることはありませんし、サブアカでログイン中にメインアカの API 規制が解除されればメインに切り替わります。 <br />
もちろんアカウントの切り替えは自動で行われ、その間も自在に投稿できます。</p>

<p>「チャーハン諸島」は Windows / MacOS / Linux など、Java が対応しているさまざまな OS 上で動作します。 <br />
<a href="http://www.java.com/">Java 公式サイト</a> から最新版の Java をインストールすればすぐに使えます。既にインストールされている場合は、もっとすぐに使えます。</p>

<p>ダウンロードはこちらから。すでに利用されているユーザーは、起動時に自動アップデートされていますからダウンロードは不要です。</p>

<p>チャーハン諸島 <a href="http://archive.guma.jp/rice/rice.zip">http://archive.guma.jp/rice/rice.zip</a> <br />
Windows 版 <a href="http://archive.guma.jp/rice/rice_win.zip">http://archive.guma.jp/rice/rice_win.zip</a></p>

<p><a class="image" href="http://archive.guma.jp/images/rise_ss_.jpg"><img alt="rise_ss_.jpg" src="http://archive.guma.jp/assets_c/2010/05/rise_ss_-thumb-320x296-9.jpg" width="320" height="296" class="mt-image-none" style="" /></a></p>

<p>数字とかが表示されている辺りを右クリックするとメニューが、左クリックでウィンドウの移動ができます。</p>

<p>この「チャーハン諸島」を使えば、かの偉大な大陸への憧れに焦がれながら抱いていた、手の届かない歯がゆさとはもうオサラバです！(海外通販番組の吹き替え風に)</p>

<h1>適当な Q&amp;A</h1>

<ul>
<li>投稿できるのはわかったんですけど、TL とか見られないんですか？ <br />
メニューから「パネルを開く」を選べば、TL のツイートとかが表示されるパネルが現れます。1 パネル 1 ツイートという省エネ設計ですので、必要時にだけ使うことをオススメします。</li>
<li>リプライとかできないんですか？ <br />
上記で開いたパネルにツイートが表示されているときにパネルの上のほうを右クリックすると、なんとメニューが開きます。あとはわかるな？わかるよね？わかってください。</li>
<li>なんか動かなくなったんですけど。 <br />
アプリを再起動しても直らなかったら、アプリをフォルダごと削除してまたダウンロードし直してみてください。</li>
<li>プロキシ設定はどこ？ <br />
あー今出ました！さっき出ましたから！そろそろお宅に着くかと思います！</li>
<li>ところで誰が作ってるの？ <br />
わたしです。何かあれば @<a href="http://twitter.com/Mocel">Mocel</a> までお気軽に。</li>
</ul>

<blockquote>
  <p>「チャーハン諸島」に関する<a href="http://archive.guma.jp/cat7/">記事一覧</a></p>
</blockquote>
]]>
        
    </content>
</entry>

<entry>
    <title>Java でもカモフラージュ率の高い Twitter クライアントを</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/05/java-twitter.html" />
    <id>tag:archive.guma.jp,2010://2.19</id>

    <published>2010-05-19T16:52:00Z</published>
    <updated>2010-12-15T01:10:16Z</updated>

    <summary>「ラーメン大陸 http://25re.com/web.shtml#rcweb」...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Java" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Twitter" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="チャーハン諸島" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="java" label="Java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="twitter" label="Twitter" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="チャーハン諸島" label="チャーハン諸島" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>「ラーメン大陸 <a href="http://25re.com/web.shtml#rcweb">http://25re.com/web.shtml#rcweb</a>」という素晴らしい Twitter クライアントをご存じですか。</p>

<p>これはどんなときでも、たとえ Twitter のサーバの調子が悪いときでも投稿力が変わらないただひとつのクライアントとして、多くの人に親しまれています。</p>

<p>ところがこのクライアントは Windows 専用だったため、それ以外の OS で投稿力を落としたくない人は使いたくても使えなかったのです。</p>

<p>そこで、Windows 以外の OS でも使えるよう Java でできるかぎり全力でパクってみました。</p>

<p>チャーハン諸島 for Windows <a href="http://archive.guma.jp/rice/rice_win.zip">http://archive.guma.jp/rice/rice_win.zip</a>
チャーハン諸島 for Mac(Snow Leopard 以降) <a href="http://archive.guma.jp/rice/rice_Mac.zip">http://archive.guma.jp/rice/rice_Mac.zip</a>
チャーハン諸島 <a href="http://archive.guma.jp/rice/rice.zip">http://archive.guma.jp/rice/rice.zip</a>  </p>

<p><a class="image" href="http://archive.guma.jp/images/rice_ss.jpg"><img alt="rice_ss.jpg" src="http://archive.guma.jp/assets_c/2010/05/rice_ss-thumb-446x398-7.jpg" width="446" height="398" class="mt-image-none" style="" /></a></p>

<p>最新版の Java をインストールしてから、上の Zip ファイルを適当なディレクトリに展開して、Win 版は rice.exeを、Mac 版は「チャーハン諸島」アプリを実行してみてください。 <br />
Linux とかだと rice.sh を実行するといい感じになります。</p>

<p>自動アップデーター付きなので、プログラムが更新されていれば自動的にこっそり更新されます。
いろいろうまくいっていれば設定ダイアログが表示されると思います。</p>

<p>OAuth 認証使ってますので、初回実行時は設定ダイアログの「コード取得」ボタンを押して Twitter の認証ページを開き、このダサいネーミングの「チャーハン諸島」を許可してやってください。 <br />
そしたら 7 ケタほどの数字が表示されますから、それをダイアログの「認証コード」にコピペして「認証」ボタンを押すだけ。
サーバが重いと失敗することがありますが、そのときはイチからやり直してやってください。</p>

<p>「糞すぎるｗｗｗｗｗ即アンインスコするしｗｗｗｗｗｗｗ」という場合は、ディレクトリごと削除すれば抹殺完了です。レジストリとか全然触ってないので安心ですね。ていうか Java からレジストリとか...だし。</p>

<p>で、どうせパクるなら TL 画面も楽しい感じにしようと思って、「1 ツイート 1 ウィンドウ」で、付箋紙アプリっぽくしてみました。
投稿ウィンドウの数字の辺りを右クリックするとメニューが表示されますから、なんか適当にいじってみてください。</p>

<p>これならおおっぴらに TL を眺めるのが憚られる環境でも、「これ付箋紙アプリですし」みたいな顔して平気で眺めていられます！ <br />
激しくツイートの多い TL だと間違いなく全部追っかけられませんけど、まぁ、そこはそういうものだと割り切れる人だけに使える、間口の狭いアプリだということで。</p>

<p>そんな感じで、パネルいっぱい表示してたらひとつひとつのパネルが島のように見えてきて、パクるなら似たような名前にしようと思い「チャーハン諸島」とかどうしようもない名前になってしまいました。</p>

<p>すみません。</p>

<p>作った人 : @<a href="http://twitter.com/Mocel">Mocel</a></p>

<blockquote>
  <p>「チャーハン諸島」に関する<a href="http://archive.guma.jp/cat7/">記事一覧</a></p>
</blockquote>
]]>
        
    </content>
</entry>

<entry>
    <title>Twitter の TL をリアルタイムに</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/05/twitter-tl.html" />
    <id>tag:archive.guma.jp,2010://2.18</id>

    <published>2010-05-03T14:05:07Z</published>
    <updated>2010-05-03T14:28:16Z</updated>

    <summary>Twitter の &quot;User Streams&quot; が開発用途向けに試験公開されて...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="Java" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Twitter" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="java" label="Java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="twitter" label="Twitter" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>Twitter の "User Streams" が開発用途向けに試験公開されて、ともかく誰でも使える状態になってるので適当に触ってたりするわけですが。</p>

<p>すでに方々でレビューとか解説とかされてますけど、ストリーミングってことでいつも TL がほぼリアルタイムに流れてくるので、もうブログとか掲示板感覚ではなく、完全にチャットと化しますね Twitter が。 <br />
あなたが投稿した瞬間、あなたをフォローしてるわたしのストリームにツイートが届いてるわけです。ラグは 1 秒もありません。あなたに限らず、わたしがフォローしてる人のツイートすべてが 1 秒足らずでストリームに流れてくるわけで、今までとは TL の見え方が全然変わってきますね。</p>

<p>この User Streams は試験的な公開だからかツイートだけでなく、フォローしてるの人の「フォロー」とか「ふぁぼり」とか「ツイート削除」、これまでの TL には流れてこなかった情報も流れてきます。やっぱりリアルタイムで。 <br />
それがどうしたのって感じですけど、その気になれば「自分がふぉろってる人が最近フォローした人ホットリスト」とか「ツイート削除が多い人ランキング」とかがローカルで作れたりしますね。</p>

<p>TL 眺めるにはかなり便利な User Streams ですが、開発用の公開ってことでまだアプリとか配布できません。サーバ側がまだ準備できてないらしく、アプリとか配るとキャパをあっさりオーバーして大変なことになるからかと。</p>

<p>Java の勉強がてらせっかく作ったアプリも配れないので、せめてもの応用で「使っていい」ほうの Streaming API をリアルタイムに眺めるアプリを置いたりしてみます。  </p>

<p><a class="image" href="http://archive.guma.jp/images/streamviewer_01.jpg"><img alt="streamviewer_01.jpg" src="http://archive.guma.jp/assets_c/2010/05/streamviewer_01-thumb-320x302-3.jpg" width="320" height="302" class="mt-image-none" style="" /></a></p>

<p>ハッシュタグをリアルタイムに追ったりするものです。 <br />
追いたいタグを #aaa,#bbb とか複数あるならカンマで区切って入力し SET ボタンを押してください。Twitter のユーザー名とパスワードを尋ねられるので、入力してじっとしてればハッシュタグを含むツイートが流れてくると思います。 <br />
リアルタイムに更新してるので、ボタン押したのに何も起こらない場合はそういうツイートをだれもしていないってことになります。</p>

<p>飽きたら CLOSE ボタンを押すか、ウィンドウを閉じてやってください。 <br />
レジストリとか設定ファイルとか全然使ってないので、不要になったらダウンロードしたファイルを削除すれば跡形もなく消え去ります。パスワードとかどこにも記憶してないのでご安心を。</p>

<p>Win 7 と Win XP でしか動作確認してないけど、Java だからほかの OS でもなんとなく動いたりすると思います。</p>

<p>アプリ: <br />
<a href="http://archive.guma.jp/2010/05/03/StreamViewer.jar">StreamViewer.jar</a></p>
]]>
        
    </content>
</entry>

<entry>
    <title>また JavaScript</title>
    <link rel="alternate" type="text/html" href="https://archive.guma.jp/2010/02/-javascript.html" />
    <id>tag:archive.guma.jp,2010://2.17</id>

    <published>2010-02-27T06:36:05Z</published>
    <updated>2010-02-27T06:52:04Z</updated>

    <summary>前の記事 http://archive.guma.jp/2010/02/java...</summary>
    <author>
        <name>Mocel</name>
        
    </author>
    
        <category term="JavaScript" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="javascript" label="JavaScript" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="ja" xml:base="https://archive.guma.jp/">
        <![CDATA[<p>前の記事 <a href="http://archive.guma.jp/2010/02/javascript.html">http://archive.guma.jp/2010/02/javascript.html</a> で書いた「それっぽいクラス定義」で、インスタンスを作る関数の戻り値にいわゆるクラスメソッドを突っ込む部分が二段構えになってるのは。</p>

<pre class="prettyprint">
    var that = {};

    var init = function(opts){
        method.prepare(opts);
        return that;
    }
    that.init = init;
</pre>

<p>ここがどうして var that = { init: function(){ ... } }; となっていないのか。</p>
]]>
        <![CDATA[<p>次の例で見てみるとわかるけど、クラスメソッドから別のクラスメソッドを呼び出すのが安全になるから。</p>

<pre class="prettyprint">
var createRequest = function(method, param){
    var that = {};

    var init = function(opts){
        method.prepare(opts);
        return that;
    }
    that.init = init;

    var send = function(sender){
        sender.send(that);
        return that;
    }
    that.send = send;

    var setParam = function(k, v){
        param[k] = v;
        return that;
    }
    that.setParam = setParam;

    var getparam = function(k){
        return param[k];
    }
    that.getParam = getParam;

    var setHost = function(h){
        return setParam("host", h);
    }
    that.setHost = setHost;

    return that;
}

var request = createRequest("GET", { id: "boeboe" });
</pre>

<p>ここで setHost() は内部的に setParam() を呼び出してるけど、呼び出してるのは createRequest() 中で定義されてる setParam() と明確に固定されることになる。だから、作ったインスタンスの setParam() を上書きしたとしても、setHost() が呼び出すのは元々の setParam() と保証されるわけで。</p>

<pre class="prettyprint">
var request = createRequest("GET", { id: "boeboe" });
request.setParam = function(o, k, v){
    o[k] = v;
    return;
}
</pre>

<p>とかできてしまうんだけど、こうしたとしても setHost() は createRequest() 内の setParam() を呼び出すように固定されててここは上書きできないから、後から変更を加えられても確実に動作するわけです。 <br />
まぁ request.setHost を上書きされてしまうと意味ないんですが、そのときにちゃんと動作するかどうかは上書きした人の責任になるから気にする必要はないかと。それに request.setHost を上書きしたとしても、別の場所で creareRequest() して得られるインスタンスには全く影響がないわけですし。</p>

<p>さらに、スコープ的には後付けの setParam() から createRequest() の内部が見えなくなっているので、後からいくらメソッドとかを上書きしようと method とか param とかの変数を変更することはできません。 <br />
安全ですね。</p>
]]>
    </content>
</entry>

</feed>
