View・通信の処理を簡単実装できるAndroid-Queryの使い方をまとめた

AndroidでView周りの処理もう少し簡単に書けるようになるやつないかなと探していたら[Android-Query](https://code.google.com/p/android-query/)というViewと通信周りのライブラリを見つけて、試しに使ってみたら色々できそうだったので紹介してみる

感想として、個人アプリではガンガン使っていけそうな感じなので積極的に使っていきたい

# 特徴
- Android 2.1以上に対応？
 - デモアプリがそれくらいのバージョンで作られている
 - https://play.google.com/store/apps/details?id=com.androidquery
- Viewの記述をシンプルにできる
- HTTP通信が簡単に行える
 - レスポンスの型を自由に変更できるので、結果を好きに扱いやすい
 - ローカルキャッシュに対応しているので、通信処理を高速化できる
 - 非同期で処理してくれるので、自分でAsyncTask等を使う必要がない(同期処理もできるっぽい)
- Oauth認証もできるらしい(ほとんど試してない)
- 面倒なURLからの画像読み込みにも対応
 - ローカル・メモリの画像キャッシュに対応しているので読み込みを高速化できる

# 導入
どちらか好きな方で入れる
## jarの場合
1. [Android-Query](https://code.google.com/p/android-query/downloads/list)のダウンロードページから最新バージョンのjarをダウンロードしてくる
2. Androidのlibsフォルダにダウンロードしてきたjarを置く

## Mavenの場合
pom.xmlにあるdependenciesの項目内に以下を追加する

```xml:pom.xml
<dependency>
    <groupId>com.googlecode.android-query</groupId>
    <artifactId>android-query</artifactId>
    <version>0.24.3</version>
</dependency>
```
## パーミッションを追加
AndroidManifestにパーミッションを追加
場合によってはさらに増やす必要もあるけど、最低限必要になってくるのは2つ

```java:AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" /> 
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```

# Viewの簡略化
AQueryクラスを使ってViewの操作ができる
キャストする必要がなかったり、一行で複数のViewの指定ができたり便利
## 各クラスのAQueryの使い分け
### ActivityでViewの操作をする
```java:PhotoDetailActivity.java
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // setContetnViewでレイアウトをセットしておく
    setContentView(R.layout.activity_main);
    aQuery = new AQuery(this);
    // 色・文字サイズ・テキストの内容をTextViewセットする
    aQuery.id(R.id.activity_main_text).textColor(Color.RED).textSize(20).text("リス");
    // Buttonが押された時に実行するメソッド名を指定する
    aQuery.id(R.id.activity_main_button).clicked(this, "onButtonClicked");
}

public void onButtonClicked() {
    //ボタンが押された時にTextViewの内容が変更される
    aQuery.id(R.id.activity_main_text).text("押されたリス");
}
```

### FragmentやAdapterで操作をする
rootになるViewをコンストラクタで渡してあげればOK

```java:PhotoDetailFragment.java
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_detail, container, false);
    aQuery = new AQuery(getActivity(), view);
    aQuery.id(R.id.fragment_detail_text).text("Fragment内でも使える");
    return view;
}
```

## View.OnClickListenerを利用する
文字列でイベントの呼び出し制御するのやだなあと思っていたので調べてみると、ちゃんとリスナーでもいけるっぽい

[http://android-query.googlecode.com/svn/trunk/javadoc/com/androidquery/AbstractAQuery.html#clicked(OnClickListener)](http://android-query.googlecode.com/svn/trunk/javadoc/com/androidquery/AbstractAQuery.html#clicked(OnClickListener))

```java:
aQuery.id(R.id.activity_main_button).clicked(new View.OnClickListener() {
    @Override
    public void onClick() {
         aQuery.id(R.id.activity_main_text).text("押されたリス");
    }
});
```

## 画像表示
AQuery#imageで画像の読み込みができる
URL以外にもリソース・Drawable・Bitmapからの読み込みももちろん可能だが、特にこれといって特徴はないのでURLからの読み込みに絞って紹介

### URL

```java:
// hoge.comのfuga.jpgを読み込んできてImageViewにセットする
aQuery.id(R.id.activity_main_image).image("http://hoge.com/images/huga.jpg");
```

### キャッシュを利用する
AQueryのimageメソッドにbooleanを渡すだけでOK

```java:
//  image(対象のURL, メモリキャッシュ, ローカルキャッシュ)
aQuery.id(R.id.activity_main_image).image("http://hoge.com/images/huga.jpg", true, true);
```

### 読み込むサイズを指定する
画像をそのまま読み込むとOutOfMemoryで落ちる可能性があるので縮小してから読み込む必要があるけど、Android-Queryには画像を縮小して読み込む機能があるので自分で実装する必要はなし

*注意点*

- 拡大不可
 - [inSampleSize](http://y-anz-m.blogspot.jp/2012/08/android.html)による縮小なので、指定された幅に近い値で縮小される

幅200pxで読み込む場合はこんな感じ

```java:
//  image(対象のURL, メモリキャッシュ, ローカルキャッシュ, 読み込む幅, 読み込み失敗時に表示するリソース)
aQuery.id(R.id.activity_main_image).image("http://hoge.com/images/huga.jpg", true, true, 200, 0);
```

#### 高さの指定もできるようにしたい
幅と高さどちらか大きい方に適用したい場合もありそうなので何か方法はないかと思い、ソースコードを読んでみた。

*わかったこと*

- どうやら内部的には幅と高さどちらか大きい方の値で縮小できるように作られているけど、肝心のどちらで縮小させるかの部分が隠蔽されていてしかもその値が固定で渡されてしまっている
 - 幅・高さどちらで処理するかで使われてるtargetDimの値を書き換えさせてくれてもよかったのでは
  - https://github.com/androidquery/androidquery/blob/master/src/com/androidquery/callback/BitmapAjaxCallback.java#L92
- callbackのtransformでBitmapの変換処理が行われているので、このメソッドをオーバーライドしてBitmapAjaxCallback#getResizedImageを呼べばいけそうな気がする
 - https://github.com/androidquery/androidquery/blob/master/src/com/androidquery/callback/BitmapAjaxCallback.java#L464
 - https://github.com/androidquery/androidquery/blob/master/src/com/androidquery/callback/BitmapAjaxCallback.java#L383

試してないけどこんな感じで高さを最大値としたリサイズができそう
ポイントはgetResizedImageにfalseを渡している部分

ただこの記述だと読み込み失敗時の画像の差し替えができないので必要な場合はgetResizedImageがnullを返した場合に失敗時の画像に差し替える処理を加える必要がある
実際に使うなら読み込む幅のサイズやbooleanを渡す部分は、別のところで計算して渡すようにする

```java
aQuery.id(R.id.activity_main_image).image("http://hoge.com/images/huga.jpg", true, true, 200, 0, new AbstractBitmapAjaxCallback() {
    @Override
    public Bitmap transform(String url, byte[] data, AjaxStatus status) {
        final File file = status.getFile();
        final String path = file != null ? file.getAbsolutePath() : null;
        return getResizedImage(path, data, 200, false, 0);
    }
});
```

### ローカルキャッシュ先を変えることはできるか
気になったのでソースコード読んでみるとAQUtility#setCacheDirを使うことで自由に変更ができるっぽい
https://github.com/androidquery/androidquery/blob/master/src/com/androidquery/util/AQUtility.java#L516

ちなみにセットしたキャッシュディレクトリはここで使われてる
https://github.com/androidquery/androidquery/blob/master/src/com/androidquery/callback/AbstractAjaxCallback.java#L961

### 画像読み込みの感想
正直なところ、画像読み込みに関しては前に紹介した[Universal Image Loader for Android](http://qiita.com/chuross/items/e3ca79065d9b67716ace)の方が多機能でそんなに手を加える必要性もないため便利に感じた

# HTTP通信
AQuery#ajaxでHTTP通信を使った処理ができる
通信は非同期で行われるため、自分でAsyncTask等を用いて実装するする必要はない

基本的には対象のURLとレスポンスの型とコールバックを指定すればOK

```java:
aQuery.ajax("http://hoge.com/api/v1/huga/animal/list.php?page=1&limit=20", String.class, new AjaxCallback<String>() {
    @Override
    public void callback(String url, String result, AjaxStatus status) {
        // APIのレスポンスが文字列で返ってくる
    }
});
```

レスポンスの型はなんでも指定できるが、変換に失敗した場合はnullが返ってくる

特に手を加えなくても返せる型はここ参照
https://code.google.com/p/android-query/wiki/AsyncAPI?tm=6

自分の好きな型で返したい場合は[Transformer](http://android-query.googlecode.com/svn/trunk/javadoc/com/androidquery/callback/Transformer.html)を実装したクラスをAQuery#transformerで指定してあげれば良い

```java:Profile.java
public class Profile{
    public String id;
    public String name;             
}
```

```java:GsonTransformer.java
public class GsonTransformer implements Transformer{
    public <T> T transform(String url, Class<T> type, String encoding, byte[] data, AjaxStatus status) {                    
        Gson gson = new Gson();
        return gson.fromJson(new String(data), type);
    }
}
```

```java:example.java
GsonTransformer t = new GsonTransformer();
aq.transformer(t).ajax("http://hoge.com/api/v1/huga/user/detail.php?id=1", Profile.class, new AjaxCallback<Profile>(){
    public void callback(String url, Profile profile, AjaxStatus status) {
        Gson gson = new Gson();
        showResult("GSON Object:" + gson.toJson(profile), status);
    }
});
```

## post通信がしたい
引数にパラメータを渡すだけでいける

```java:
Map<String, Object> params = new HashMap<String, Object>();
params.put("name", "dog");
aQuery.ajax("http://hoge.com/api/v1/huga/animal/add.php", params, String.class, new AjaxCallback<String>() {
    @Override
    public void callback(String url, String result, AjaxStatus status) {
        // APIのレスポンスが文字列で返ってくる
    }
});
```

## 結果をキャッシュしたい
```java:
// 15分間キャッシュする
long expire = 15 * 60 * 1000;
aQuery.ajax("http://hoge.com/api/v1/huga/animal/list.php?page=1&limit=20", String.class, expire, new AjaxCallback<String>() {
    @Override
    public void callback(String url, String result, AjaxStatus status) {
        // APIのレスポンスが文字列で返ってくる
    }
});
```

キャッシュを更新したい場合はexpireに-1を渡す

```java:
long expire = -1;
aQuery.ajax("http://hoge.com/api/v1/huga/animal/list.php?page=1&limit=20", String.class, expire, new AjaxCallback<String>() {
    @Override
    public void callback(String url, String result, AjaxStatus status) {
        // APIのレスポンスが文字列で返ってくる
    }
});
```

## 同期処理させたい
同期で処理したい場合は[AQuery#sync](http://android-query.googlecode.com/svn/trunk/javadoc/com/androidquery/AbstractAQuery.html#sync(com.androidquery.callback.AjaxCallback))を使えば良い

```java:
AjaxCallback<JSONObject> callback = new AjaxCallback<JSONObject>();           
callback.url("http://www.google.com/uds/GnewsSearch?q=Obama&v=1.0").type(JSONObject.class);
aQuery.sync(callback);
JSONObject result = callback.getResult();
AjaxStatus status = callback.getStatus();
```

## headerに色々付けてリクエストしたい
AjaxCallback#headerにセットしてあげればOK
複数ある場合はその度にheaderを呼ぶことで追加できる

```java:
AjaxCallback<String> callback = new AjaxCallback<String>();        
callback.url("http://www.google.com").type(String.class);
callback.header("Referer", "http://code.google.com/p/android-query/");
callback.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0.2) Gecko/20100101 Firefox/6.0.2");
aQuery.ajax(callback);
```

## Cookieを使いたい
AjaxCallback#cookieで追加できる
複数ある場合はその度にcookieを呼ぶことで追加できる

```java:
AjaxCallback<JSONObject> callback = new AjaxCallback<JSONObject>();           
callback.url("http://www.androidquery.com/p/doNothing").type(JSONObject.class);
callback.cookie("hello", "world").cookie("foo", "bar");
aQuery.ajax(callback);
```

## 通信処理をまとめてキャンセルさせたい
キャンセルしたい場合は[AQuery#ajaxCancel()](http://android-query.googlecode.com/svn/trunk/javadoc/com/androidquery/AbstractAQuery.html#ajaxCancel())を使う

このメソッドを呼ぶと裏で動いているajaxで呼ばれた処理をすべてをキャンセルすることができる
なのでActivityが破棄される時にこの処理を呼ぶと良い

### 通信処理を個別にキャンセルしたい
何かボタンを押されたときに特定の処理だけ止めることはできないのだろうかと思ったので調べてみたところ、[AbstractAjaxCallback#abort](https://github.com/androidquery/androidquery/blob/master/src/com/androidquery/callback/AbstractAjaxCallback.java#L2008)を呼べばいけそうな気がする