特定のURLをフックしてアプリを起動させる(暗黙的インテント)

今jQueryMobileの勉強を兼ねてつぶあにのスマートフォンサイトを作っているんですが、どうせならアプリと連携してみようということで調べました。

やりたいこと
・特定のURLクリック時にアプリをインストールしている場合はアプリケーション選択ダイアログが出て、ブラウザで遷移するかアプリを起動するか選べる
・アプリをインストールしていない場合は普通にページ遷移

具体的に言うと、オンエアー中のアニメのタイムラインを見に行こうとするとアプリケーションに移動するか、スマフォサイトで用意したページに行くか選べるようにしたいってこと。

まず、AndroidManifest.xmlを変更

   <activity
      android:name=".Test"
      android:label="@string/test.title">
      <intent-filter>
        <action
          android:name="android.intent.action.VIEW" />
        <category
          android:name="android.intent.category.DEFAULT" />
        <category
          android:name="android.intent.category.BROWSABLE" />
        <data
          android:scheme="http"
          android:host="test.tsubuani.com"
          android:pathPrefix="/live" />
      </intent-filter>
    </activity>

intent-filterにaction.View,category.DEFAULT,cateogry.BROWSABLEを追加。
dataにscheme,host,pathPrefixを追加。
schemeやhostとかはそれぞれ依存しているので注意が必要。
pathPrefixの他にも、pathやpathPatternなんてのも指定できる。
細かい仕様は下記参照。
http://www.techdoctranslator.com/android/guide/manifest/data-element

今回の例で言うと

http://test.tsubuani.com/live*******

っていうURLにアクセスしようとするとTestActivityが起動するという感じになる。

ActivityはonNewIntentに処理を書いてやればよい

  @Override
  protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);

    Intent intent = getIntent();
    if( intent != null ) {
      /** リンク先のURLを取得する。 */
      String data = intent.getDataString();
      if(data != null){
        //urlを元にごにょごにょ
      }
    }
  }

これで、アプリをインストールしている人はアプリで実況TLを見ることが出来るし、
そうでない人はスマフォサイトで用意したページに遷移させることが出来る。
Androidはこんな感じでアプリ同士の連携がすごい簡単に出来るのが素敵。

Twitterで話題のアニメを集めるつぶあにというサービスをリリースしました。

えごリブ作ってたときに知人に見てもらったときに、どういう経緯か忘れたけど
「アニメのツイートを話数毎にまとめたら面白いんじゃね?」っていう話が出たので作ってみました。
といってもβ版公開したのは6月末だけど。

Twitterのアニメのハッシュタグを収集してランキングしたり、エピソード毎にツイートのチャートを表示するサービスです。
DVDの売り上げランキングとかとはまた違った動きになるんじゃないでしょうか。

最初はアニメの番組表とか登録するの無理だろって思って作る気は無かったんだけど、
しょぼいカレンダーってところがAPIを公開していたので
これならハッシュタグ登録するくらいであとはほっといてもイケる!と思い、6月の頭頃からコソコソと作ってました。

Webアプリについて

今回は技術的にえごリブとやることはバッチ処理くらいしか違いがなかったので
2週間位で大体出来てあとはデザインとかキャッシュの
調整とかって感じで割りとサクっと作れたと思う。CakePHPに大分慣れてきた。
まぁバッチ作ってるときは動きが全然見えなくて何度かやめかけたけど。画面があるって大事。テンション的に。
ただ、ツイートのデータが1週間に50万とかのペースで増えていってるので
SQLのチューニングをちゃんとしないとすごーく遅くなりそうです。今までこんだけのデータ扱う経験は無かったので
SQLの勉強にはなった。

サービス的にはハッシュタグを集めてるサイトっていうとハッシュタグクラウドあたりが
有名だと思うけどアニメに特化してエピソード単位でツイートをまとめるサービスはあんまり
見かけないのでそれなりに新しいはず!きっと。

ちなみにTwitterStremingAPIを使用してツイートの収集をしていますが、
収集は自宅のPCでやってるので、うちの回線が止まるとツイートが取得できなくなるという
かなり危なっかしい構成です。実際開発中に一回30分くらい繋がらなくなった時がありました。
出来れば収集自体もよそでやりたいけどレンタル鯖とかじゃ無理だしVPSとかだとメモリが…。
誰かお金ください。

Androidアプリについて

最初は作る気無かったけど、AndroidでTwitterStreamingAPIっていけるのかな?
って思ったので、実況ツイートをリアルタイムで追えるという単純なアプリを作ってみました。
原型は2日位ですぐに出来たけど、やっぱりツイート量が多いと処理が厳しいっぽい。
まず、リストビューが意外に重い。秒間分間500ツイートくらいになるともう結構カクカクになる。
なので、ScrollViewを使って擬似リストビューっぽくしたらなんとか動くようになった。まだちょっと怪しいけど。
それからテストするチャンスが週に3回くらいしかないのがきついw(30分で2万ツイートを余裕で越えるようなのは今だとタイバニ・アイマス・青エク・プリキュアくらいしかない。全部見てないけど。)
あと初めてPreferenceActivityを使ってみたけどすごい便利!簡単すぎだった。

作ってみて思ったのはアニメのツイート量まじ多いのね。30分で5万ツイートとか頭おかしい。でもだからこそ結構見た目面白いサービスになったかなとは思います。まぁ僕は実況とかしないけど。

機能的にはユーザーページの機能をもう少し足してみたいのと、あといくつか細かい機能のアイデアがあるのでもうちょい作り続けそう。統計データも結構面白いのでそういうのも見れる画面を作ろうかなと思ってます。でもTERAのOBT始まったら多分そっちやる。TERAやりたい。まだ?
とはいっても僕自身が実況しないのでユーザーページの機能を拡張するモチベーションが続くかは謎。

Androidでゲーム・書籍管理!なえごリブ for Androidをリリースしました。

えごリブのAndroid版がやっと完成してリリースしました。\(^o^)/
デバッグを何人か知り合いに手伝ってもらい、色々致命的なバグが見つかって
ほんと自分のへたれ加減が良く分かったけどなんとかリリースできました。しちゃいました。

えごリブ for Android

どんな感じなのかは上記サイトから!
機能としては

  • キーワードによるアイテムの検索
  • バーコードの読み取り
  • テンプレート設定
  • 登録アイテムの編集
  • 登録アイテムの検索
  • 登録アイテムへのコメント(レビューとかメモとか)
  • 登録アイテムの関連商品検索

といったことが出来るアプリです。

AsyncTask、ListViewあたりの扱い方のコツを掴むまでがキモでした。
そっから先はかなりサクサクと作ることが出来た。むしろAPI作成するほうがめんどいと感じるレベル。
特に画面遷移が簡単すぎ。Intentすげー…。
4月頭くらいからのんびりと作り始めたので制作期間は大体一ヶ月といったところですね。遅いね。ごめんね3流で。
ま、まあゲームもしてたし仕事もしてたしね!

開発以外の部分のディベロッパー登録や、
アプリケーションの登録、公開といった手順も非常に分かりやすくて感動した。
iPhoneと比べると雲泥の差だわ。

今後はWeb版共々少しずつ改良していこうと思います。
Android持ってる人で読書やゲームする人は是非使ってみて感想をぷりいいいいず。

Androidアプリは作っていてかなり楽しかったのでまた何か作ってみたいなあ。あとAndroidもっと普及してくれ!

ListViewの中のボタンのクリックイベントをActivityに通知する

ちょいとはまったのでメモ。

ListViewの中でどうしてもボタンを並べて、ボタンが押されたらActivityに通知させたかったのですが、なかなかスマートな方法が見つからなかった…。

結局OnClickListenerを実装させたListViewのサブクラスを作って、adapter#getViweの中でリスナーの登録をさせることによって実現しました。

具体的なソースは下記のような感じ。

まずレイアウト。サンプルなんで適当。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
>
  <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="sample" />

  <FrameLayout
    android:layout_width="fill_parent"
    android:layout_height="0dip"
    android:layout_weight="1"
  >
    <com.sample.MultiButtonListView
      android:id="@+id/list"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" />
  </FrameLayout>
</LinearLayout>

続いてリストの各行のレイアウト

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical"
>
  <TextView
    android:id="@+id/titleText"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
  <TextView
    android:id="@+id/authorText"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
    
  <LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
  >
    <Button
      android:id="@+id/titleBtn"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="title" />
    <Button
      android:id="@+id/authorBtn"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="author" />
  </LinearLayout>
</LinearLayout>

で、問題のListViewのサブクラス。

/**
 * リスト内にボタンを配置してそれが押されたとき通知してくれるListView
 * @author Tom
 *
 */
public class MultiButtonListView extends ListView implements OnClickListener {
  
  /**
   * コンストラクタ
   * @param ctx
   */
  public MultiButtonListView(Context ctx){
    super(ctx);
  }
  
  /**
   * コンストラクタ
   * @param ctx
   * @param attrs
   */
  public MultiButtonListView(Context ctx , AttributeSet attrs){
    super(ctx , attrs);
  }
  
  /**
   * リスト内のボタンがクリックされたら呼ばれる
   */
  public void onClick(View view) {
    int pos = (Integer)view.getTag();
    this.performItemClick(view, pos, view.getId());//idって普通なに渡すの?
  }

}

単にOnClickListenerを実装して、その中でpeformItemClickを呼んでるだけ。
viewのgetTagでpositionを取ってるけど、それはAdapterの中で設定してあげてます。ところで引数の3つ目のidってなに渡すの?使ったこと無いんだけど。
adapterはArrayAdapterのサブクラスで下記みたいな感じ。

public class BookAdapter extends ArrayAdapter<Book> {
  private LayoutInflater inflater;
  private int resId;
  
  public BookAdapter(Context ctx , int resId , List<Book> items){
    super(ctx, resId , items);
    this.resId = resId;
    this.inflater = (LayoutInflater)ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  }
  
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    MultiButtonListView list = null;
    try{
      list = (MultiButtonListView)parent;
    }catch(Exception e){
      e.printStackTrace();
    }
    if(convertView == null){
      convertView = inflater.inflate(resId, null);
      holder = new ViewHolder();
      holder.titleView = (TextView)convertView.findViewById(R.id.titleText);
      holder.authorView = (TextView)convertView.findViewById(R.id.authorText);
      holder.button1 = (Button)convertView.findViewById(R.id.titleBtn);
      holder.button2 = (Button)convertView.findViewById(R.id.authorBtn);
      holder.button1.setOnClickListener(list);
      holder.button2.setOnClickListener(list);
      
      convertView.setTag(holder);
    }
    else{
      holder = (ViewHolder)convertView.getTag();
    }
    Book book = this.getItem(position);
    holder.titleView.setText(book.getTitle());
    holder.authorView.setText(book.getAuthor());
    holder.button1.setTag(position);
    holder.button2.setTag(position);
    
    return convertView;
  }
  
  private static class ViewHolder{
    public TextView titleView;
    public TextView authorView;
    public Button button1;
    public Button button2;
  }
}

parentをキャストしているのと、buttonにリスナー登録しているところ以外は特に変わったことはしていない。

で、最後にActivity

public class MultiSample extends Activity implements OnItemClickListener {
    private static List<Book> books;
    static{
      books = new ArrayList<Book>();
      books.add(new Book("なれる!SE3 失敗しない?提案活動 (電撃文庫)" , "夏海 公司"));
      books.add(new Book("はじめてのAndroid2プログラミング", "柴田 文彦"));
      books.add(new Book("魔術はささやく (新潮文庫)" , "宮部 みゆき"));
      books.add(new Book("幽霊船が消えるまで―痛快本格推理 (祥伝社文庫―天才・竜之介がゆく!)" , "柄刀 一"));
      books.add(new Book("イニシエーション・ラブ (ミステリー・リーグ)" , "乾 くるみ"));
      books.add(new Book("GOTH 僕の章 (角川文庫)", "乙一 "));
      books.add(new Book("ネガティブハッピー・チェーンソーエッヂ (角川文庫)" , "滝本 竜彦"));
      books.add(new Book("アメリカ銃の謎 (創元推理文庫 104-10)" , "エラリー・クイーン"));
    }
    
    private ListView list;
    private BookAdapter adapter;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        list = (ListView)findViewById(R.id.list);
        adapter = new BookAdapter(this, R.layout.list_row, books);
        list.setAdapter(adapter);
        list.setOnItemClickListener(this);
    }
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
      Book book = adapter.getItem(pos);
      switch(view.getId()){
      case R.id.titleBtn :
        Toast.makeText(this, book.getTitle() , Toast.LENGTH_SHORT).show();
        break;
      case R.id.authorBtn :
        Toast.makeText(this, book.getAuthor() , Toast.LENGTH_SHORT).show();
        break;
      }
    }
    
}

Bookオブジェクトは単純にtitleとauthorを持っているだけ。

これで、リストの中のボタンイベントを普通にonItemClickで拾えるようになった!
サンプル画像は下記のような感じに。
f:id:tomstay:20110421220113j:image

そもそもリストの中にボタンなんぞ並べずにコンテキストメニューとか使えって話かもしれないけど。ただ、今考えてるのはボタンを押したらダイアログが出るような処理を考えているので2連続ダイアログっぽいのが出るのは微妙じゃね?って思ったのでボタン並べるという所業におよんだのでありました。

おかしなところがあったらそっと教えてください。

Android開発メモ

忘れそうなのでメモ。

エミュレータの起動元のマシンを見に行く方法

開発環境でサーバを立ててそこのAPIを叩きにいく場合はエミュレータではlocalhostと入れるとエミュレータの中を見に行ってしまう。
10.0.2.2を指定してやると良い。

画面の回転禁止

Androidは画面を傾けると勝手に横向きにしたり、縦向きにしてくれたりするけど、縦固定、横固定にしたいときはAndroidManifest.xmlの指定したいactivityに下記を追加

  • 縦固定
android:screenOrientation="portrait"
  • 横固定
android:screenOrientation="landscape"

画面が回転したときに回転する前のデータを保存する

Androidは画面の向きを切り替えるときは初期化するようで、onCreateからやり直している。
これはつまり、onCreateのときにAPIで情報を取得して、それを表示する。みたいな処理をすると画面の向きが変わるだけでまた情報を取得しにいくという素敵なことになる。
で、回転前にデータを一旦保存しておきたい場合はonRetainNonConfigurationInstanceってメソッドを使うと簡単に保存できる。

Activitiyを継承した何か

  private SaveData saveData;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.mypage);
    
    /**回転時のデータ復元*/
    saveData = (SaveMypage)getLastNonConfigurationInstance();
    if(saveData == null){
      //データが無かったときの処理
    }
    else{
      //あったときの処理
    }
  }
  /**
   * 回転時のデータ保存
   */
  @Override
  public Object onRetainNonConfigurationInstance() {
    return saveData;
  }

こんな感じ。詳しくは下記に載ってる。

https://sites.google.com/a/techdoctranslator.com/jp/resources/articles/articles-index/faster-screen-orientation-change

Androidでボタンを横に並べて自動で折り返す

はい、タイトル分かりづらいですね。まぁ前回の続きなんですけど。

AndroidのLinearLayoutとかを使って横にViewを並べると、長すぎる奴はそのまま外に出しちゃったり変な風にクシャッとしてくれたりして自動で折り返してくれない。
でもボタンを横に並べて、画面からはみ出そうになったら折り返して表示したい!しろよ!って思いまはしたものの、よい案が浮かばなかったのでWebViewと連携してその描画はCSSでやればいいんじゃね?って思って作っていたんですが、どうもレスポンスがよろしくない。
気晴らしにGジェネやっていたら、相対レイアウト使えばまさか!?って思いついて早速やってみたら出来たのでメモ。

まずはlayout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ScrollView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <RelativeLayout
      android:id="@+id/rl"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content">

    </RelativeLayout>
  </ScrollView>
</LinearLayout>

ボタンは動的に出すので何も無いlayout。ScrollViewのなかにRelativeLayoutを入れているだけです。

Activityはこんな感じ。importとかは端折った。

public class TagsActivity extends Activity {
  private static String[] tags = { "hoge", "fuga", "bar", "foo", "もげ",
      "daadae", "aadfefaea", "12345678901234567890", "ちょっと長い", "1", "hoge",
      "fuga", "bar", "foo", "もげ", "daadae", "aadfefaea", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1",

  };

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    // 1000個くらいいれると若干固まるから別スレッドで作るとかやったほうがいいかも?
    for (int i = 0; i < tags.length; i++) {
      String s = tags[i];
      Button btn = new Button(this);
      RelativeLayout.LayoutParams prm = new RelativeLayout.LayoutParams(
          LinearLayout.LayoutParams.WRAP_CONTENT,
          LinearLayout.LayoutParams.WRAP_CONTENT);
      // ボタン間のマージン 0にしても隙間が埋まらないんだけど!要調査
      prm.setMargins(0, 0, 0, 0);
      btn.setText(s);
      btn.setId(i + 1);
      RelativeLayout r = (RelativeLayout) findViewById(R.id.rl);
      r.addView(btn, prm);

    }
  }

  @Override
  public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl);
    // 子供の数を取得
    int l = rl.getChildCount();
    // 無いなら何もしない
    if (l == 0) {
      return;
    }
    // ディスプレイの横幅を取得
    WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    int max = display.getWidth();

    int margin = 0;
    // 一番最初は基点となるので何もしない
    View pline = rl.getChildAt(0);
    // 一行全体の長さ
    int total = pline.getWidth() + margin;
    for (int i = 1; i < l; i++) {
      int w = rl.getChildAt(i).getWidth() + margin;
      RelativeLayout.LayoutParams prm = (RelativeLayout.LayoutParams) rl
          .getChildAt(i).getLayoutParams();
      // 横幅を超えないなら前のボタンの右に出す
      if (max > total + w) {
        total += w;
        prm.addRule(RelativeLayout.ALIGN_TOP, i);
        prm.addRule(RelativeLayout.RIGHT_OF, i);
      }
      // 超えたら下に出す
      else {
        prm.addRule(RelativeLayout.BELOW, pline.getId());
        // 基点を変更
        pline = rl.getChildAt(i);
        // 長さをリセット
        total = pline.getWidth() + margin;
      }
    }
  }
}

割と簡単。
onCreate時にButtonインスタンスを作って、RelativeLayoutにどんどん追加しています。
ボタンの数が多くなるようだったら別スレッドで処理したほうがいいのかな。ここも要検討。
Viewの長さはonCreateのときに取得しようとしてもまだ描画されてなく0になってしまうのでonWindowFocusChangedのタイミングで取得します。(タイミング的にここであってるよね?)
この辺のライフサイクルはほんとFlexと似てる。Flexの時もコンポーネントの長さを取得するのに頭を痛めた記憶があります。もう忘れたけど。

で、onWindowFocusChangedのところで、各ボタンの長さとディスプレイの長さを取得して、配置する場所を決めてるといった感じ。

今のところは当然ながらクリックしたときのレスポンスも良い感じ。これで先に進める…!?

AndroidでJavaScriptとJavaの連携

ボタンを一列に並べて、画面からはみ出るようだったら折り返すってビューを作りたかったんですが、JavaのViewで実現するのに妙案が浮かばず、じゃあWebViewで出しちゃえばいいじゃない!ってことで、JavaScriptとどうやって連携するのか調べました。

ちなみに何を作ってるのかというとタグを並べて、それを押したらEditTextに文字列を流し込むって画面。はてブアプリをパクr…、参考にして。あれかっこいい。あれもWebViewで実現してるんでしょうか。

で、JSの連携は説明はめんどいので実際のソースコード

まずはlayout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:focusable="false"
  android:focusableInTouchMode="false"
>

  <TextView
    android:id="@+id/tv"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"/>
  
  <Button
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="btn"
    android:onClick="clickBtn"/>
  <WebView
    android:id="@+id/wv"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:focusable="false"
    android:focusableInTouchMode="false"/>
</LinearLayout>

難しいところは何一つない。
お次はActivity。importとかは端折った。

public class JsBridgeActivity extends Activity {
  private static final String HOGE_HTML = "file:///android_asset/hoge.html";

  private TextView textView;
  private WebView webView;

  /**
   * WebViewの中で使うJsObj
   */
  public class JsObj{
    private JsBridgeActivity act;
    private Handler handler;

    public JsObj(JsBridgeActivity act){
      this.act = act;
      handler = new Handler();
    }

    /**
     * ボタンクリック時に呼ばれる
     * @param tag
     */
    public void send(final String str){
      //ここで普通にact.setTextValue(str)とかやっても怒られる。
      handler.post(new Runnable() {
        public void run() {
          act.setTextValue(str);
        }
      });
    }
    /**
     * Textの中の値を取得
     * @return
     */
    public String getTextValue(){
      if(textView == null){
        return "";
      }
      String str = textView.getText().toString() == null ? "" : textView.getText().toString();
      return str;
    }

  }

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

    textView = (TextView) findViewById(R.id.tv);
    textView.setText("ほげー");

    webView = (WebView) findViewById(R.id.wv);
  //js使用可能
    webView.getSettings().setJavaScriptEnabled(true);
    //load
    webView.loadUrl(HOGE_HTML);

    JsObj js = new JsObj(this);
    //JavaScriptではandroidでJsObjにアクセスできる
    webView.addJavascriptInterface(js, "android");
  }
  
  /**
   * ボタン押した
   * @param v
   */
  public void clickBtn(View v){
    webView.loadUrl("javascript:fromAndroid('androidからです')");
  }

  /**
   * textに値を入れる
   * @param str
   */
  public void setTextValue(String str){
    if(textView == null) return;
    textView.setText(str);
  }
}

ポイントはJavaScriptで使うオブジェクトを作ってあげることと、JavaScriptからActivityの値を変更したい場合は、handlerを経由してあげなきゃいけない。
handlerについて詳しくは下記。

http://www.adamrocker.com/blog/261/what-is-the-handler-in-android.html

で、最後にhoge.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1">
<script type="text/javascript" src="js/jquery-1.4.3.min.js"></script>
<script type="text/javascript">

$(document).ready(function(){
  $("#sendBtn").click(function(){
    android.send($("#input").val());
  });
  
  $("#getBtn").click(function(){
    $("#jText").text(android.getTextValue());
  });
});

function fromAndroid(str){
	$("#jText").text(str);
}

</script>
</head>
<body>
<input type="text" value="" id="input" />
<button id="sendBtn">send</button>

<p id="jText"></p>
<button id="getBtn">get</button>
</body>
</html>

ソース見れば分かるように、

    JsObj js = new JsObj(this);
    webView.addJavascriptInterface(js, "android");

と指定してやるとJavaScript側からJavaのメソッドを叩ける。
そしてJava側からJSを叩く場合はloadUrlの引数にJSを書けばよいらしい。すげーかっこわるい!?

ただ、あんまりレスポンスが良くないような気がします。この程度だったらよいけど、今作ってるタグのUIはタグが押されてから文字列を流し込むまで若干もたつく印象。パクリもとの参考にしたはてブのアプリはレスポンスも素敵なんですが…。アプローチが間違ってるんだろうか。ううむ。