Android開発 〜そんなところ自分で作る必要ないよ〜


マニュアルに目を通さないで、適当なブログを適当にググッて実装すると陥りやすい、「自分で書く必要のないところまで自分で実装して疲れたりバグったり」というケース。経験したものをずらずらと。

* コードはうろ覚え
* 会社のプレゼンの資料で作ったので精査できてない(TODO 後で整理する)


## 概要

* RosourcesTypes
* Loader
* DataProvider

これらを使って楽をしようという話のケーススタディー的なものです

## 画像リソースのdpiごとの伸縮

単純に拡大縮小するだけなのに、いろんな解像度分の画像を用意して配置するやつ

### Before

猫のアイコン icon_neko.pngを以下のように配置

```:ディレクトリツリー
res/
    drawable-mdpi/
        icon_neko.png (16x16)
    drawable-hdpi/
        icon_neko.png (24x24)
    drawable-xhdpi/
        icon_neko.png (32x32)
    drawable-xxhdpi/
        icon_neko.png (48x48)
    drawable-xxxhdpi/
        icon_neko.png (64x64)
```

ほんとうに必要なのか。

---

それ、AndroidのPreScaleでやってくれるよ。



### After

```:ディレクトリツリー
res/
    drawable-xxxhdpi/
        icon_neko.png (64x64)
```

* 一番近いdpiのリソースを自動伸縮して表示してくれる
* pre処理なので更新のたびに毎回伸縮しているわけではないので軽い


### 注意点

以下の場合は頑張ったほうがいい

* Androidの拡大縮小のアルゴリズムが気に入らない
* 単純な拡大縮小では駄目なケース(例えば, 1pxの線は縮小されても1pxであってほしいとき）
* ドキュメントにこの内容そのものが書いてあったわけではなく、Prescalingの説明と実動作を見た私感にすぎないので程々に疑って下さい(むしろ解像度ごとのリソースを用意することを推奨している)

### 参考

* Supporting Multiple Screens | Android Developers http://developer.android.com/intl/ja/guide/practices/screens_support.html#DensityConsiderations



## 画面幅に応じた画面切り替え

* 画面が狭い端末だと表示しきれないので、狭い用のlayoutをプログラムで分岐して表示させるケース
* タブレットでは2ペイン、スマートフォンでは普通のデザインでやりたいケース

### Before

```java:HogeActivity.java
public class HogeActivity extentds Activity {

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 画面の横幅が360dp以上かどうかで表示するlayoutを切り替える
        DisplayMetrics metrics = getResources().getDisplayMetrics();
        if(metrics.widthPixels / metrics.density >= 360) {
            setContentView(R.layout.activity_hoge_w360);
        }else {
            setContentView(R.layout.activity_hoge);
        }
    }
}
```

```:ディレクトリツリー
res/
    layout/
        activity_hoge.xml (デフォルト)
        activity_hoge_w360.xml (横360以上用)
```

---

それ、AndroidのResourceTypeの指定で出来るよ。

## After

```java:HogeActivity.java
public class HogeActivity extentds Activity {

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hoge);
    }
}
```

```:ディレクトリツリー
res/
    layout/
        activity_hoge.xml (デフォルトのlayout)
    layout-w360dp/
        activity_hoge.xml (widthが360dp以上の時に適応してほしいlayout)
```

* 縦幅横幅うち小さいほうがNdpi以上のとき reources-swNdp なども可能


### 注意点

* Andorid3.2未満では resource-wNdp の指定が出来ない
    * 代わりにresource-lergeなどといった名前ベースの指定を利用するしかない。

### 参考

* Supporting Multiple Screens | Android Developers http://developer.android.com/intl/ja/guide/practices/screens_support.html
* Providing Resources | Android Developers http://developer.android.com/intl/ja/guide/topics/resources/providing-resources.html



## バージョンに応じた処理分岐

* このlayout指定はAPInn以上じゃないと動かないから処理を分岐して頑張ったり、警告を無効化して考えるのを辞めたりするケース

### Before

```java:HogeActivity.java
public class HogeActivity extentds Activity {
    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // APIレベルが11以上かどうかで処理を分ける
        if(Build.VErSION.SDK_INT => 11) {
            setContentView(R.layout.activity_hoge_v11);
        } else {
            setContentView(R.layout.activity_hoge);
        }
    }
}
```

```:ディレクトリツリー
res/
    layout/
        activity_hoge.xml (デフォルト)
        activity_hoge_v11.xml (API level 11をサポートしているデバイス用)
```

---
それ、AndroidのResourceTypeの指定で出来るよ。


### After

```java:HogeActivity.java
public class HogeActivity extentds Activity {

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hoge);
    }
}
```

```:ディレクトリツリー
res/
    layout/
        activity_hoge.xml (デフォルト)
    layout-v11/
        activity_hoge.xml (API level 11をサポートしているデバイス用)
```

### 注意点

* Androidのresourceだけでは済まないレベルの分岐は、ソースコードで頑張ってやるしか無い

### 参考

* Providing Resources | Android Developers http://developer.android.com/intl/ja/guide/topics/resources/providing-resources.html

## 非同期通信と画面反映

WebAPIから情報を取ってきて画面に設定するだけなのに、
AsyncTaskを定義して、Activityベースのsyncronizedを書いてロックしてなどなど頑張るケース



### Before

```java
public class HogeActivity extentds Activity {
    // ロック用
    private AsyncTask<Void, Void, String> task;

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hoge);

        final TextView text = (TextView) findViewById(R.id.text1);
    
        assignloadtask();
    }

    // なんかのリスナー
    public void onNankaListener() {
        assignloadtask();
    }

    // ロックされてなければロード
    private synchronized void assignLoadTask() {
        if(task == null) {
            task = new AsyncTask<Void, Void, String>() {
                @Override
                protected String doInBackground(Void... params) {
                    String result;
                    // 時間のかかる通信処理
                    return result;
                }

                @Override
                protected void onPostExecute(String result) {
                    super.onPostExecute(result);
                    if(result != null) {
                        text.setText(result);
                    }
                    task = null;
                }
            };
            task.execute();
        }
    }
}
```

--- 

それ、Loader使えば簡単にかけるよ。

あまり短くなってないけど、ライフサイクル管理とか、再参照の時のキャッシュとか、ロックとかちゃんと書けば書けるらしい！

### After

```java
public class HogeActivity extentds Activity implements LoaderCallback<String> {

    private static final int LOADER_ID = 0;

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hoge);

        final TextView text = (TextView) findViewById(R.id.text1);
    
        getLoaderManager().initLoader(LOADER_ID, null, this);

    }

    // なんかのリスナー
    public void onNankaListener() {
        getloadermanager().restartLoader(LOADER_ID, null, this);
    }

    @Override
    public Loader<String> onCreateLoader(int arg0, Bundle arg1) {
        return new AsyncTaskLoader<String>(getActivity()) {
            @Override
            public String loadInBackground() {
                String result = null;;
                // 時間のかかる通信処理
                return result;
            }
        };
    }

    @Override
    public void onLoadFinished(Loader<String> arg0, String arg1) {
        if(result != null) {
            text.setText(result);
        }
    }

    @Override
    public void onLoaderReset(Loader<String> arg0) {
    }
}
```

### 注意点

* Android3.0からなので2対応の場合はサポートライブラリを使いましょう

### 参考

* Loaders | Android Developers http://developer.android.com/intl/ja/guide/components/loaders.html


## データーの更新による他の画面の更新

一覧・詳細パターンで、詳細画面でデータを更新して、一覧画面に戻ってきたら更新内容を反映してなきゃいけないやつ

### 他のページに解説を譲ります

サンプルを書こうとしたら、思いの外長くなりそうだったので、参考サイトに説明を譲ります。



### 注意点

* 結構難しい
* 解説ページが少ない（というよりこれを使わないで頑張る記事が多い　気がする)
* 解説ページがSQLITEDBに対するアクセスの例ばかり
    * 個人的にRestfulAPIに相性抜群と思っているので、後で書きます！

### 参考
* ContentProvierとCursorLoaderとFragmentを使ったサンプルを作ってみた - Androidはワンツーパンチ 三歩進んで二歩下がる http://sakura-bird1.hatenablog.com/entry/20121102/1351876650
* Android - ContentProvider - Qiita http://qiita.com/kojionilk/items/9bb1a28d04dc87d301b9
* Android - CursorLoader - Qiita http://qiita.com/kojionilk/items/512334d0dd0a4d4d05d2
* Loaders | Android Developers http://developer.android.com/intl/ja/guide/components/loaders.html
* Content Providers | Android Developers http://developer.android.com/intl/ja/guide/topics/providers/content-providers.html