のらねこの気まま暮らし

技術系だけど、Qiita向きではないポエムとかを書くめったに更新されないヤツ

CUI(CLI)でAndroidプロジェクトを作成・Build環境を整える呪文

FireFoxOSの話から一転してAndroidです。
僕はEclipseが苦手で(というかとあるテキストエディタが便利すぎて)、かつGUIよりCUIで開発したい奇特な人間で、いままでどうしてもAndroidの開発にコミットシたがらなかったんです。


最近は、コマンドラインベースのAndroid環境の構築方法とかもWebですぐ見つかったりして、さくっと実装できそうな感じだったのでためしに作ってみました。

ついでにVPSをUbuntu 12.4にアップデートしました。

環境

Ubuntu 12.4

Javaのインストール

add-apt-repositoryを使えるようにする

aptでインストールできるようにするためadd-apt-repositoryを使いたいんですけど、デフォルトではそのコマンドが存在しない。ググったら以下のようにすれば行けるって書いてあったからそうした←

sudo aptitude install python-software-properties 

aptにrepositoryを追加・インストールする

どうやらaptのJDKは webupd9teamさんにあるらしいの。追加する。

sudo add-apt-repository ppa:webupd8team/java 
sudo -H apt-get update
sudo aptitude install oracle-java7-installer 

`oracle-java7-installer`をインストールすればいいはずなんだけど、途中で失敗する。
403が返ってきているみたい。

仕方ないので 1.8入れてみる

途中まで追ってはみたものの、さっぱりわからず、眠いし面倒くさいしで勢いのままjava8を入れた。

sudo aptitude install oracle-java8-installer  
java -version
java version "1.8.0-ea"
Java(TM) SE Runtime Environment (build 1.8.0-ea-b93)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b34, mixed mode)

入った。
※java7入らない理由が分かる方、オシエテクダサイ・・・・

次から本題

android-sdkをインストールする

公式から適当なディレクトリにzipをダウンロードして解凍、ディレクトリをいじってパス通す。
なお、Macとかで`android update sdk`とかコマンドを打ち込みとGUIが立ち上がるけど、CUIのVPSだと当然あのGUIは使えない。どうするかというと、`--no-ui`オプションをつける。

mkdir tmp; cd tmp
curl -L -O http://dl.google.com/android/adt/adt-bundle-linux-x86_64-20130522.zip 
unzip adt-bundle-linux-x86_64-20130522.zip 
sudo mv ~/tmp/adt-bundle-linux-x86_64-20130522 /usr/local/lib/android   
echo 'export PATH="$PATH:/usr/local/lib/android/sdk/tools"' >> ~/.zprofile
android update sdk --no-ui   

これでよし。

※`unzip`コマンドはUbuntu 12.4のデフォルトではインストールされてないので、`sudo aptitude install unzip`

apache-antをインストールする

流れとしてはandroid-sdkと同様

curl -L -O http://ftp.jaist.ac.jp/pub/apache//ant/binaries/apache-ant-1.9.1-bin.zip
unzip apache-ant-1.9.1-bin.zip 
echo 'PATH="$PATH:/usr/local/lib/apache-ant-1.9.1/bin"' >> ~/.zprofile
source ~/.zprofile
ant

完成

android create projectとか試せばいいと思う。

FirefoxOSを試してみる 第2回

前提のお話

FirefoxOSを試す
提供されてるサンプルアプリが動いたよ!の続き。

volo serveって・・・rを仲間はずれにするなよ!!

volofileが定義しているコマンドなのでカスタマイズできるらしいのよ。

firefoxos-quick-start/app/volofile

こいつをいじって、serveをserverにしてやる。

serveコマンドの定義

volofileの252行目

    serve: function(d, v, namedArgs) {

serverコマンドに書き換え

    server: function(d, v, namedArgs) {

serverコマンド、いっきまーす!

 (mizuki)$ volo server
starting web server on port 800

grunt.jsで管理したい

grunt-voloなるものがある。

https://github.com/volojs/grunt-volo

gruntのinstallはお済みですか?

$ cd firefoxos-quick-start/app
$ npm install grunt grunt-cli grunt-volo

globalにgruntを入れてたけど、localがどうのこうので動かずだったのでlocalで入れなおした。

Gruntfile.jsにtasksを登録してくださいな

  grunt.loadNpmTasks('grunt-volo');                                                                                                                                      

こいつ・・・動くぞ!

(mizuki)$ grunt volo:serve
Running "volo:serve" (volo) task
starting web server on port 8008

で、テストアプリは動いたのよ。

プロセスが重複しているとevents.jsがコケる(らしい)

ちょっとハマったのでメモ。
下記のエラーは、volo serveを2回連続で呼び出した時に発生するっぽい。
プロセス殺してvolo serveしたらエラーでなかった。

(mizuki)$ volo serve
starting web server on port 8008

events.js:71
        throw arguments[1]; // Unhandled 'error' event
                       ^
Error: listen EADDRINUSE
    at errnoException (net.js:770:11)
    at Server._listen2 (net.js:910:14)
    at listen (net.js:937:10)
    at Server.listen (net.js:986:5)
    at Function.app.listen (/Users/mizuki/project/firefoxos-quick-start/app/node_modules/connect/lib/proto.js:229:24)
    at Object.module.exports.serve [as run] (/Users/mizuki/project/firefoxos-quick-start/app/volofile:279:41)
    at commands.run (/usr/local/share/npm/lib/node_modules/volo/lib/commands.js:154:37)
    at _fulfilled (/usr/local/share/npm/lib/node_modules/volo/node_modules/q/q.js:860:32)
    at resolve.promiseSend.done (/usr/local/share/npm/lib/node_modules/volo/node_modules/q/q.js:881:34)
    at makePromise.promise.promiseSend (/usr/local/share/npm/lib/node_modules/volo/node_modules/q/q.js:553:9)

俺はcoffeeで書くぞジョジョーッ!!

現在volofileをcoffee化してgruntでcompileするようにしようとしている。
次回乞うご期待!!

FirefoxOSを試してみる

まえがきとかそんなものはない

FirefoxOSを試す。
それがたったひとつのシンプルな答え。

FirefoxOSについてぼくが知っていること

  • FirefoxOS持ってません。(世に出ているのかも知らん)
  • FirefoxOS Simuratorなるものがある
  • FirefoxOS simuratorはFirefoxのAdd-onとして提供されている
  • FirefoxOSはJavaScript, CSS, HTMLで構築できる

以上っ!!!!!
※この記事を書くまで、Fire”F"oxだと思ってました。Firefoxです。FFって略し方はおかしい!

なにはともあれFirefox(Desktop)をインストールする

Mac Book AirにはFirefox入ってなかったの。

以下よりインストール。
http://www.mozilla.jp/firefox/

Firefox OS Simurator をインストールする

以下にやり方はまとまってる。(英語)
https://developer.mozilla.org/ja/docs/Mozilla/Firefox_OS/Using_Firefox_OS_Simulator

Add-onは下からインストールできる(上記ドキュメントにもリンクはある)
https://addons.mozilla.org/en-US/firefox/addon/firefox-os-simulator/

Dashbordを開く

[ツール]->[Web開発]->[Firefox OS Simurator]

Simuratorを起動する

[Stopped]っていうトグルっぽいボタンが左っかわにあるのでそいつをクリックしてやる。

開発環境

基本的にnode.jsとvolojsというライブラリを使ってサーバ立ててごにょごにょするっぽい。

node.jsのインストール

さっき書いた。
http://mizuki-r.hatenablog.com/entry/2013/04/24/194255

volojsのインストール

globalにインストールしていいのか?とは思ったけど気にせずドーン

$ npm install -g volo

※path通ってなかったので、/usr/local/share/npm/binにpath通した。

サンプルアプリをチェックアウトしてくる

$ git clone git://github.com/mozilla/firefoxos-quick-start.git
$ cd firefoxos-quick-start/app
$ volo serve
starting web server on port 8008

Simulatorのブラウザを開いてhttp://localhost:8008を叩く

と動く。




今日はここまで!!1

Mac Book Air に node.js を入れる

今の時代、node.jsなしじゃ生きられませんよね。というわけでMac Book Airにnode.js入れる。

前提

  • OSX
  • homebrewインストール済み

node.jsのインストール

$ brew install node
==> Downloading http://nodejs.org/dist/v0.8.18/node-v0.8.18.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/node/0.8.18
==> make install
==> Caveats
Homebrew installed npm.
We recommend prepending the following path to your PATH environment
variable to have npm-installed binaries picked up:
  /usr/local/share/npm/bin
==> Summary
🍺  /usr/local/Cellar/node/0.8.18: 879 files, 14M, built in 3.0 minutes

versions

(mizuki)$ node -v                                                                                                                                                    [~]
v0.8.18
(mizuki)$ npm -v                                                                                                                                                     [~]
1.2.2

Backbone.jsのpopstate時にルートページのcallbackが実行される

Backbone.jsとjQuery pjaxを併用してフロントエンド充してるところで、思わぬ罠にハマったので記録しとく。

問題

var MyRouter = Backbone.Router.extend({
    "events" : {
        ""        : 'home',
        "path/to" : 'fuga'
    },
    "home" : function () {
        console.log('--route:home--');
    },
    "fuga" : function () {
        console.log('--route:fuga--');
    }
});

$(function () {
    var router = new MyRouter();
    Backbone.history.start({"pushState" : true});
});

こういうコードが合った時、以下のような遷移を行うとする。

/ -> /huga/hoge -> /path/to -> (ブラウザバック)

そうすると、次のような関連性でcallbackが行われる。

遷移 URL console.log
/ / --route:home--
/huga/hoge /huga/hoge
/path/to /path/to --route:fuga--
ブラウザバック /huga/hoge --route:home--

callbackを登録してない"/hoge/huga"のパスでhomeのコールバックが呼ばれてしまう。

原因

console.logとかでchrome developerToolを使ってデータの遷移を追ってみた。

backbone.jsでpopstateされてからコールバックが実行されるまでの流れ

  1. popstateのイベントに登録されているのは、Backbone.History.checkUrlメソッド
  2. Backbone.History.checkUrlの中で呼ばれるBackbone.History.loadUrlメソッド呼ばれる
  3. Backbone.loadUrlはBackbone.History.getFragmentからfragmentを受け取り・上書きする
  4. Backbone.loadUrlはgetFragmentで書き換えたfragmentを元にcallbackを選択・実行する

どうやらBackbone.History.loadUrlメソッドがfragmentを書き換えているらしい

1170: var fragment = this.fragment = this.getFragment(fragmentOverride);

this.fragmentにブラウザバックしたときのURLが入っている。
しかし、fragmentOverrideはundefinedが代入されており、それがthis.getFragmentに渡される。

getFragment内でfragmentOverrideが == null だった場合、fragmentを書き換える。
その結果、fragment === ''という判定がくだされ、""のrouteに紐づくhomeのコールバックが実行される。

fragment == null

undefined == null はtrueとして扱われるため、このよくわからない上書きが発生する。
もし、undefinedを許可したくないのであれば(fragment === null)と型を見ればいい。

Backbone.History.getFragmentはundefinedも許容したい。

上記判定を行うとすると、fragment = ''の時だけfragmentの取得が行われる。
しかし、実は""へのアクセス時のfragmentOverrideはundefinedが格納される。

じゃあこうするしか無いな。

if (fragment === null || fragment === undefined)

・・・・あれ? ソレ結局元にもどっただけじゃんか。

解決策

前回に引き続き@が解決策を提案してくれたよ!
提案というか、発見なんだけど。こうすればいいんじゃね?と。

http://stackoverflow.com/questions/6088073/default-routes-in-a-backbone-js-controller

routesに*{なまえ}のrouteを登録するとデフォルトでrouteを設定してくれる。
こいつを設定すると、homeのtrigger実行されなくなる、と。

"routes" : {
    "*path" : "defaultRoute"
}

ためしてみた。

var MyRouter = Backbone.Router.extend({
    "events" : {
        "*path"   : 'defaultRoute', // <- 追加
        ""        : 'home',
        "path/to" : 'fuga'
    },
    "defaultRoute" : function () {
        console.log('--route:default--');
    },
    "home" : function () {
        console.log('--route:home--');
    },
    "fuga" : function () {
        console.log('--route:fuga--');
    }
});

$(function () {
    var router = new MyRouter();
    Backbone.history.start({"pushState" : true});
});

/ -> /huga/hoge -> /path/to -> (ブラウザバック)

遷移 URL console.log
/ / --route:home--
/huga/hoge /huga/hoge --route:default--
/path/to /path/to --route:fuga--
ブラウザバック /huga/hoge --route:default--

本当だ・・・!

なんでかってーと

pushStateがtrueの時、Backbone.History.loadUrlでfragmentからcallbackが該当するものを探しだすんスよ。
その結果がcheckUrlに渡されて、callbackがあるものはtrueを返すが、trueが無かったら今度はHashから探しにいくんすよ。
Hashでもcallbackが見つからなかったら、""が実行されるらしくて。

なんで、全部のrouteにcallbackが登録されていれば、そいつ(defaultRoute)が実行される。

なんてわかりにくい・・・!

結論

routesにdefaultを登録してやれ。

たぶんBackbone.jsはRoutesにすべてのrouteのこーるばっくを登録している前提で組まれているから。


まったく、Backboneのコンセプトは好きだが、コードは好きになれない。


今回もヘルプで助けてくれた@と、JSで困ったらとりあえず質問を投げてアドバイスくれる@にさんくす。

backbone-pjax.jsを書いた

まえおき

  1. Backbone.jsとpjax使いたいよ!
  2. Backbone.jsだけでpjax実装できるけどjquery-pjaxで至れりつくせりしたいよ!
  3. Backbone.jsとjquery.pjax.jsを同居させるとpushStateを2回しちゃうよ!
  4. /(^o^)\ナンテコッタイ
  5. 解決策↓

Backbone.js × jQuery pjax - しるてく

ここでの話

  • Backbone.js と jquery.pjaxを同じモノとして扱いたい
  • Backbone.Routerと密に連携したい

Backbone.js と jquery.pjaxを同じモノとして扱いたい

  • pjaxもroutingの一部だと思うの役割的に
  • Viewのいたるところで$(document).on('pjax:hoge', ...)するのはコードが散在して保守性下がる
  • Backbone.Routerの一部を上書きする必要がある。pjax用のviewとrouterの双方に依存するのはちょっとキモチワルイ

Backbone.Routerと密に連携したい

  • Backbone.RoutrerはTriggerの実行後にroute:hogeというトリガが発火する
var router  = new MyRouter();
router.on('route:index', function () {
  console.log('ここはホームですよ!');
});
  • but, trigger設定していないとrouteイベントが発火しない
    • ページ共通で走らせたいBeforeDispatchみたいなのはどこでやればいいんだろう?
    • pjax使ってるなら、pjax:complete, pjax:end等選択肢はある。
  • Backbone.Routerのイベントとしてpjaxも連動できたら直感的(?)に実装できるんじゃないだろうか

提案

Backbone.Pjax.js書いた。
https://github.com/rymizuki/backbone-pjax

  • Historyを複製して、上位クラスに影響無いようにした
    • もうちょい突っ込んで改造できるかも
  • Backbone側ではpushStateさせない
    • 前述にあるとおり
  • Backbone.Pjax.Routerをextendして、pjaxのoptionとかをオーバーライドで実装できるように
    • こっちの方がBackbone的にはわかりやすいんじゃないかと
  • routerにpjax系のイベントをbindできるように

追記

Backbone.Pjax.jsのリンクをgistに貼ってたのでgithubに差し替えた

2013/03/30 追記

Backbone.Pjaxのつかいかた

readmodules

<script src="/plugin/jquery-pjax.js"></script>
<script src="/plugin/underscore.js"></script>
<script src="/plugin/backbone.min.js"></script>
<script src="/plugin/backbone.pjax.js"></script>

javascript

(function () {
    "use strict";

    var MyRouter = Backbone.Pjax.extend({
        "pjax" : {
            "container" : '#container', // $.pjaxのcontainerとなる要素を指定してください
            "options" : {               // $.pjaxのoptionを指定してください
                "timeout" : 2000
            }
        },
        "initialize" : function () {
            // pjaxイベントの設定

            if ($.support.pjax) { // ブラウザがpjaxをサポートしているか
                this.pjaxInit(); // $.pjaxの初期化処理
                this.on('pjax:beforeSend', function () { // $.pjaxのbeforeSendのイベントにbindする
                    console.log('pjax before send');
                },this);
                this.on('pjax:end', this.pageInit, this); // $.pjaxのendのイベントにbindする
            }
        },
        // 以降はBackbone.Roterと同様に利用できる
        "routes" : {
            // 
        }
    });
})();

Backboneとjquery.pjaxの相性は悪い

前置き

以前こんな記事を書いた。
http://mizuki-r.hatenablog.com/entry/2013/02/02/150356

Backboneでpjaxの実装書くより、jquery-pjax使ったほうが楽だし利便性いいよね!
なんかよくわかんないバグにハマったけど試行錯誤してたらうごいたよ!
っていう内容。


お ま え は 何 を 言 っ て い る ん だ

試行錯誤しているうちになんか動くようになった。誰か説明してくれ。

実 は 解 決 し て な い。
気のせいか、別のバグでpjaxしてるように見えてしてなかったとかそんなオチ。

試行錯誤云々で動くはずがない

というのは、ソースを見ればわかる。

jquery.pjax

https://github.com/defunkt/jquery-pjax

version 1.5.1から引用

function pjax(options) {
  options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)

  // 中略

  if (xhr.readyState > 0) {
    if (options.push && !options.replace) {
      // Cache current container element before replacing it
      cachePush(pjax.state.id, context.clone().contents())

      window.history.pushState(null, "", stripPjaxParam(options.requestUrl))
    }

    fire('pjax:start', [xhr, options])
    fire('pjax:send', [xhr, options])
  }

  return pjax.xhr
}

と、pjaxメソッドの中にベタっと埋め込まれている。

backbone.js

http://backbonejs.org/

version 0.9.10から引用
長いので、Backbone.history.navigateの部分だけ抽出

    navigate: function(fragment, options) {
      if (!History.started) return false;
      if (!options || options === true) options = {trigger: options};
      fragment = this.getFragment(fragment || '');
      if (this.fragment === fragment) return;
      this.fragment = fragment;
      var url = this.root + fragment;

      // If pushState is available, we use it to set the fragment as a real URL.
      if (this._hasPushState) {
        this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);

      // If hash changes haven't been explicitly disabled, update the hash
      // fragment to store history.
      } else if (this._wantsHashChange) {
        this._updateHash(this.location, fragment, options.replace);
        if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
          // Opening and closing the iframe tricks IE7 and earlier to push a
          // history entry on hash-tag change.  When replace is true, we don't
          // want this.
          if(!options.replace) this.iframe.document.open().close();
          this._updateHash(this.iframe.location, fragment, options.replace);
        }

      // If you've told us that you explicitly don't want fallback hashchange-
      // based history, then `navigate` becomes a page refresh.
      } else {
        return this.location.assign(url);
      }
      if (options.trigger) this.loadUrl(fragment);
    },

BackboneのpushStateは、Backbone.Router.navigateを呼び出すことでpushされる。
navigateはpushStateしつつ、routesに登録したコールバックを実行するため、navigateを使わない、という選択肢は取れない。

Backbone.Router.navigateは、Backbone.history.navigateにエリアス貼ってるだけなのでそっちみればいい。

設定や一部上書き程度でどうにかなるレベルじゃない

どちらも大きなメソッドに埋め込まれているので、仮にどちらかのpushStateを無効にできたとして、それによる弊害の方が大きくなるため、試行錯誤でパラメータ変えたり上書きしたりする程度でこの不具合は潰せない。

Backbone.history.navigateの引数でどうにかできないか

navigateは(fragment, options)を取ってる。fragmentは今回の件に関係ないので省略。
optionsは以下のパターンを想定している

  • trigger: true|false
    • trigger(Router.routesに登録されているコールバック)を呼び出すか
  • replace: tule|false
    • pushStateではなくreplaceStateを使うか

第二引数にオブジェクト({})ではなく真偽値(true|false)を渡すことも可能だが、その場合、triggerのみがtrueになる。

本当に2回pushStateされているのか

Backbone.jsとjquery-pjaxを読み込んだページで、developer toolでhistoryのAPI叩いてみれば一目瞭然。

まとめ

最初の話

BackboneのRouterでtirgger呼び出しつつpjaxしたい。
Backboneは自前でpjax実装しないといけないのでちょっと面倒。
jquery-pjaxを使いたい。

間違った話

設定しだいでBackbone.historyとjquery-pjaxを併用できるよ!

本当の話

Backbone.historyとjquery-pjaxを並行で使おうとすると、pushStateが2回走るため、同じ履歴が2つ登録してしまうという不具合を引き起こす。

当日追記

silversさんがこの問題に対する解決策をあげてくれました。
silvers++

http://ofsilvers.hateblo.jp/entry/backbone-with-jquery-pjax