EclipseでAndroid開発環境

CakePHPの勉強は一段落したのと(マスターしたわけではない)、会社のスマフォを少し自由に使えるようになったので、今度はAndroidアプリの勉強をする。もう本も買ってしまった。Webのほうももう少し機能追加したいところもあるけどとりあえずおいておいておく。ディスガイア4はニャンコ海賊団がでなくて修羅いけないし。
まぁ僕はスマフォ持ってないんですけどね!夏モデルが発表されたら買うかも、買わないかも。

というわけで、まずはEclipseで開発環境を整えるメモ。

ここがとってもよくまとまってました。ほとんどこれのまま。
http://sky.geocities.jp/izeefss/develop/android/env_eclipse.html

Android SDKをダウンロード

http://developer.android.com/index.html
ここのDownloadから環境にあったものを落としてくる。
今回は「android-sdk_r10-windows.zip」を取得。
ダウンロードしたら、解凍して任意のフォルダに。

ADTをインストール

EclipseAndroidアプリを開発するためのプラグインをインストール
ヘルプ→新規ソフトウェアのインストール
作業対象に
https://dl-ssl.google.com/android/eclipse/
を入力して、追加ボタンを押して名前にはADTとかAndroidoPluginとか入れる。
4つくらい出てくるので全てチェックしてインストール。色々聞かれるけど全てイエス。終わったら再起動。

AndroidSDKの設定

Eclipseのウィンドウ→設定→Andoroid
SDKのロケーションにSDKのフォルダを選択してOKを押す

AndoroidPlatformAPIのインストール

Eclipseのウィンドウ→Android SDKおよびADVマネージャー
InstalledPackageを選択してUpdateAllを押す
必要なパッケージにチェックを入れてインストール
基本的に最新のバージョンさえあれば下位のAPIも含まれているらしいけど、
下位のバージョンで開発する場合はそのパッケージが必要になるらしい。
とりあえず僕は1.6以降全部いれておきました。

これでおしまい。
一応HelloWorldまではやったけど、当分は入門書を読み進めながらサンプルを動かしていくつもり。
iPhoneアプリは少しかじったことがあるけど、Andoroidはどうなんでしょうか。コンポーネントとかどの程度用意されてるんだろ。まぁそもそもAndoroidアプリいじったことをほとんど無いので、定番のUIとかも調べる必要があり、覚えることはかなりありそう。
最終目標はもちろんえごリブ for Android。出来るのはいつになるやら。
目標があると勉強するモチベーションもきっと続くはず。多分。メイビー。
まぁディスガイア4優先だな…。

CakePHPのrecursive=2のデータの取ってきたがよくわからんちん。

CakePHPでfindとかを使うときにrecursiveでアソシエーションするレベルを制御できるっていうのをつい最近知ったんですが、belognsToのテーブルを2階層分取って来たい時でもSQLの発行が3回以上されてる。なんか使い方間違っているのかしら。
えごリブでもいくつか使えそうな部分があると思っているんですが…、どうしたものか。

具体的には下記のような感じのテーブルがあるとします。

user_items(UserItem)

id item_id user_id
1 1 1
2 3 1

items(Item)

id name category_id
1 hoge 1
2 fuga 1
3 bar 2

categories(Category)

id name
1 book
2 game

user_itemsのアソシエーション

var $belongsTo = array("Item");

itemsのアソシエーション

var $belongsTo = array("Category");

要するに、ユーザーアイテムはアイテムに対して多対1で、アイテムはカテゴリーに対して多対1のような関係です。
で以下のようなfind文を実行する

$result = $this->UserItem->find("all", array(
	"limit" => 20,
	"recursive" => 2
));

そうすると、Itemは当然一発でleft joinして取ってきてくれるんですがCategoryは別SQLを発行して取ってきてる。以下みたいな感じ。

SELECT `UserItem`.`id`, `UserItem`.`user_id`, `UserItem`.`Item_id`,  `Item`.`id`, `Item`.`name`, `Item`.`category_id` 
FROM `user_items` AS `UserItem` 
LEFT JOIN `items` AS `Item` ON (`UserItem`.`Item_id` = `Item`.`id`)  
LIMIT 20

SELECT `Item`.`id`, `Item`.`name`, `Item`.`category_id`
FROM `items` AS `Item` 
WHERE `Item`.`id` = 1 

SELECT `Category`.`id`, `Category`.`name`
FROM `categories` AS `Category` 
WHERE `Category`.`id` = 1 
.....

というわけでしょうがないので現在はrecursivを-1にして、普通にjoinして取ってくるようにしていますがいいのかな。ちなみに以下のようにしてます。

$result = $this->UserItem->find("all", array(
	"limit" => 20,
	"recursive" => -1,
	"joins" => array(		
		array(
			"table" => "items",
			"alias" => "Item",
			"type" => "LEFT",
			"conditions" => array("Item.id = UserItem.item_id")
		),
		array(
			"table" => "categories",
			"alias" => "Category",
			"type" => "LEFT",
			"conditions" => array("Category.id = Item.category_id")
		)
	),
	"fields" => array("UserItem.* , Itemt.* , Category.*")
));

こうすると、SQLは一回しか発行されないのですっきりするんですが、使い方としてあってるんだろうか。

CakePHPの勉強を兼ねて作ってたWebサービスができたー。

2月の半ばあたりから仕事が終わったあとにCakePHPの勉強を兼ねてちょこちょこ作ってたWebサービスがとりあえず形になったので、成果ということで公開…だ!一応公開β版ってことで。

名前は悩みに悩んですげー適当になりました。僕の、僕による、僕のためのサービス。みたいな。あ、でも便利だから使ってみてよ!多分便利!
どんなサービスかは詳しくはサイトに説明してあります。Webサービスをちゃんと作るって意味でデザインとかもなんとなくっそれっぽく作りました。
ざっくりと言えばブクログとツイログを足して4くらいで割ったサービスです。Twitterからコメント等を投稿できるゲーム・読書管理サービスですね。ほぼ作り終えてから知り合いに見てもらったらピンポイントでかぶってるサービスを教えてもらって、晒すのやめようかと思ったけど、ゲームとか投稿できないみたいだからまぁいいね!読書管理サービス自体もいっぱいあるしね!
使ってみて感想くれたらうれしいです。

サーバースペックとか

さくらのVPSを借りて動かしてます。が、Tweetの収集に関してはうちの使ってないノートでやってます。StreamingAPIを動かして、ツイートがあったらVPSのほうにAPIを投げるってかたちを取ってます。ちなみにそこだけJavaで書きました。Twitter4j便利すぐる!
今のところリアルタイムでAPI送信しにいってますが、万一ツイートが多くなってきつくなってきたらcron使って定期的に送信するように仕様変更するかも。

開発言語

前述の通り、PHPです。バージョンは5.3.5。フレームワークにCakePHP1.3です。

簡単な感想

$打つのめんどい。まじ。
CakePHPというかそもそもPHPを書くこと自体が初めてなので、根本的な部分で躓いたり調べたりして(定数ってどうやって書くんだっけ?とかあれ、配列の数どうやって取得すんだっけ?とか本当に初歩的な部分からw)最初は遅々として作業が進まず、完成しないんじゃね?と思ってましたがなんとか形に出来てとりあえず良かった。
というよりも、1ヶ月ちょいで完成するとは思ってなかった。これが…CakePHPの力!早い人ならほんと数週間(数日?)で作れちゃうってことだよなあ。
ただ、勉強しながら作っていったので、ソースコードが割りとひどいことになっている。見直さなきゃと思いつつ、目を瞑り続けた。うん、あとでちゃんと見直そう。多分。

なにはともあれ、Webサービスを1から全部一人で作るという経験は初めてだったので本当に良い勉強になりました。CakePHPはもちろんだけど、サーバの設定も初体験。シェルスクリプトも初体験。デザインも初体験(デザインはちょろっとかじったことはありますが)。JavaScriptはまぁ仕事で使ってます(それでもまだ安定してないけど)。

でも何か作りながらだと勉強のモチベーションも維持しやすい。とりあえず作りきった自分に乾杯。

後は自分で使いながらのんびりほそぼそと改善していこうと思います。
まずはディスガイア4とジージェネWクリアする。ゲームのために作ってたサービスなのに、ゲームそっちのけで開発してたとかまさに本末転倒。

Macのcronでpgrepを使う

MacProtsでproctoolsをインストールすると、ターミナルでpgrepやらpkillが使えるようになるんですが、pgrepをシェルスクリプトに書いてcronで動かす場合はフルパスを指定してやらないとダメだった(当たり前か)。
30分くらい躓いた。

pid=`pgrep -f hogehoge`
....

みたいなのを

pid=`/opt/local/bin/pgrep -f hogehoge`

にする。

CakePHPでCSRF対策

CakePHPCSRF対策するにはSecurityコンポーネントを使うと簡単にできるらしいけど、色々ぐぐってみるとなかなか一筋縄でいかなそうであるのと、どうもhiddenの値を書き換えるとエラーを返してくれる高機能がもれなくついてくるらしかったので、自前でトークンを作るコンポーネントを導入しました。


といっても下記URLにまさに探してたようなものがあったので、それを参考に作成しました。ほぼまんまですけど。
http://d.hatena.ne.jp/cake67/20091217/1261019930


しかも機能としてはややダウングレードしてる部分もあったり。
とりあえずものすごーく単純に、下記のような仕様に。

  • コンポーネントは渡されたトークンをチェックしてtrue/falseを返すだけで、エラー時の処理はaction,controller単位でお任せする。
  • ヘルパーはトークンを吐き出すだけ。

使い方は単純に各アクションに

if(!$this->Token->checkToken()){
  //error
}

っていれるだけ。
viewの方には

$token->create();

と入れるだけの簡単なお仕事です。

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

component

<?php
/**
 * CSRF対策用Tokenチェック
 */
define('GET' , 'get');
define('POST' , 'post');
class TokenComponent extends Object
{

	/**
	 * Components used by TokenHelper
	 *
	 * @var array
	 * @access public
	 */
	var $components = array('Session');

	var $_form = array();
	var $_url = array();
	var $_action;
	var $type;
	var $useToken = true;

	function initialize(&$controller)
	{
		$this->_action = $controller->action;
		if(isset($controller->params['form'])){
			$this->_form = $controller->params['form'];
		}
		if(isset($controller->params['url'])){
			$this->_url = $controller->params['url'];
		}

		$this->Session->startup($controller);
	}

	/* true: Token OK */
	function checkToken($method = POST ,$tag_name = '__Token', $hash_type = 'md5')
	{
		if ($this->useToken === false) {
			return true;
		}

		$hashed_session_id = $this->get_hashed_session_id($hash_type);//元のソースには入ってなかったけどmd5以外を指定するなら必要だよね?
		if($this->_form){
			if(POST == strtolower($method)){
				if(isset($this->_form[$tag_name])){
					return $this->_form[$tag_name] == $hashed_session_id;
				}
			}
			else{
				if(isset($this->_url[$tag_name])){
					return $this->_url[$tag_name] == $hashed_session_id;
				}
			}
			
		}
		return false;
	}

	/* 現在のセッションIDを暗号化して取得 */
	function get_hashed_session_id($hash_type = 'md5')
	{
		$session_id = $this->Session->id(null);

		if (!$session_id) {
			//$this->_blackHole('No Session.');
			return false;
		}

		return Security::hash($session_id. Configure::read('Security.salt'), $hash_type);
	}

}

helper

<?php
/**
 * CSRF対策用Token出力ヘルパー
 * 要Formヘルパー
 */

class TokenHelper extends AppHelper {
	/**
	 * Other helpers used by TokenHelper
	 *
	 * @var array
	 * @access public
	 */
	var $helpers = array('Form', 'Session');

	/* Tokenをセットしたhiddenタグ出力 */
	function create($tag_name = '__Token', $id = '__Token' ,$hash_type = 'md5')
	{
		$hashed_id = $this-> get_hashed_session_id($hash_type);

		return '<input type="hidden" name="'.$tag_name.'" value="'.$hashed_id.'" id="'.$id.'" />';
	}

	/* 現在のセッションIDを暗号化して取得 */
	function get_hashed_session_id($hash_type = 'md5')
	{
		$session_id = $this->Session->id();

		return Security::hash($session_id. Configure::read('Security.salt'), $hash_type);
	}

}

Ajaxの際にも使用したかったので、inputタグを吐くところはFormHelperは使用せず普通にべた書きしてます。
まぁAjaxのときでも普通にkeyにdata["__Token"]とかしてやれば、dataに入ってくるのかな?

WindowsからMacにリモートログイン(SSH) ドメインとって外からいけるように

前回までは全てローカル内で完結してましたが、外からもいじれるようにしておきたいので、
ドメインを取ってSSHで接続できるようにしました。
今のところはWebサーバとしては使うつもりはないので別にIPアドレスさえ分かれば問題ないんだけど、
毎回変わってめんどいので、無料でそのへんを何とかしてくれる方法があるみたいなので、それを利用しました。

インフラ面は(それ以外もだけど)まったくの素人なんで、なんかセキュリティ的によろしくないことをしているかもしれません。ご指摘があればそっとやさしく教えてください。

1.Macのファイアウォール設定
左上りんごマーク→システム環境設定→セキュリティ→タブのファイアウォール
「特定のサービスおよびアプリケーションにアクセスを設定」にチェックを入れる
現状だとリモートログイン(SSH)しかないはずです。

2.プライベートIPを固定する
これは使ってるルーターとかの環境によって操作方法が違うと思います。
僕の使ってるルーターは下記の奴でした。
http://www.iodata.jp/product/network/router/etx-r/

まずルーターにアクセス
http://192.168.1.1/ , http://192.168.0.1/
とかが普通?

基本設定の「IPアドレス固定割り当て」→MacのMACアドレスを入力してIPアドレスを振る192.168.1.32とか。
高度な設定の「ポートの開放」→SSHのポート番号を入力、公開する機器のIPアドレスを上記で設定したIPにする

ルーターを再起動。

これでグローバルIPからSSHでMacにログインできるようになりました。

色々ぐぐって調べると、Mac側でIPアドレスを手入力にしてあーだこーだ書いてありましたが、僕はルーターの設定のみでいけました。

3.ダイナミックDNSサービスを使ってドメイン取得
色々あるようですが、とりあえずは下記サービスを利用しました。
http://ddo.jp/
無料登録して、ドメインを取得する。

登録が完了したら再びDo.jpのトップページにいって、左上の登録ドメイン、IPアドレス(最初から入ってる)、パスワードを入力して、IPを更新

これで登録したドメイン(*****.ddo.jp)でアクセスできるようになりました

4.WhatUp!をインストール
起動したら上部メニューからWhatUp→環境設定
登録ドメイン、パスワードを入力して、「このサービスを使う」にチェックを入れる。

これでIPが変わった際定期的に更新してくれるようになりました。
が、更新間隔がちょっと分からない。何分おき?
Do.jpを見ると更新用のAPIが用意されているようなので、自分で定期的にそれを叩きに行くようなものを作ってもよいかもしれない。

これで外部からもアクセス可能な環境が整いました。

Webサーバーとして使いたい場合はまた色々ありそうだし、今回はその予定はないので割愛します。