Androidアプリ開発 Support Annotations 定数にannotationを使う

2016年09月22日(編集2016年09月22日)
このエントリーをはてなブックマークに追加

Androidアプリ開発でAnnotationを使うと、コードの可読性を向上させ、バグを混入する確率を減らすことができます。
この記事は、Androidアプリで定数にAnnotationを使った実装方法を記載した記事です。

環境はAndroid 7.0 (API level 24) です。

環境

  • OS X Yosemite
  • Oracle jdk version 1.8.0_72
  • Android Studio 2.2.0
  • android sdk 24

難易度

初心者向け

Support Annotations

Support Annotationsは、Androidアプリ開発時に用いることができるアノテーションの集まりです。
機能の向上やコード量が減るというようなメリットはありませんが、コードの可読性をあげ、バグの混入を減らすことができます。

インストール

Support Annotationsをインストールするには、build.gradleに次のように記述します。

{project_folder}/build.gradle
android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"
    // something
}
dependencies {
    // something
    compile 'com.android.support:support-annotations:24.2.1'
}
        

Build → Rebuild Project でライブラリをインストールします。

定数

Androidアプリを作成する場合、定数の宣言にはEnumを利用するべきではありません
なぜなら、Enumを使うとDEXファイルの容量が増加します。1回使うだけで整数版よりも約13倍のメモリ使用量になります。

当然、アプリにEnumsの数が多いほど容量も増えます。大きなアプリやライブラリであるほど、オーバーヘッドは膨れ上がります。

アプリはロードされる時、Androidはアプリのためのシステムメモリを確保します。さらにサブスペース内に、DEXコード全部をロードさせておきます。
つまり、DEXファイルの容量が増加すると、システムメモリに負担をかけることになります。

ProGuardを利用すると、Enumは整数に変換されます。それでもEnumを使うのは極力避けるべきです。

定数アノテーション

定数にannotationを利用します。

サンプルとして本を管理するBookクラスに、本のカテゴリーを管理するcategoryフィールドを用意します。

{project_folder}/model/Book.java
package java_lang_programming.com.android_reactivex_demo.model;

import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.text.TextUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * An Model class for Book
 */
public class Book implements Parcelable {

    public static final String TAG = "Book";
    // table name
    public static final String TABLE_NAME = "books";
    // table name aliases
    public static final String TABLE_NAME_OMISSION = "b";
    // column list constant
    public static final List<String> COL = Collections
            .unmodifiableList(new LinkedList<String>() {
                {
                    add("id");
                    add("title");
                    add("category");
                    add("summary");

                }
            });

   +@Retention(RetentionPolicy.SOURCE)
    @IntDef({
            CATEGORY_BOOK,
            CATEGORY_KINDLE,
    })
    public @interface category {
    }

    public static final int CATEGORY_BOOK = 1;
    public static final int CATEGORY_KINDLE = 2;


    // id
    public long id;
    // title
    public String title;
    // category
    public int category;
    // summary
    public String summary;

    public Book() {
    }

    public static final Parcelable.Creator<Book> CREATOR
            = new Parcelable.Creator<Book>() {
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    private Book(Parcel in) {
        id = in.readLong();
        title = in.readString();
        category = in.readInt();
        summary = in.readString();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeLong(id);
        out.writeString(title);
        out.writeInt(category);
        out.writeString(summary);
    }

   +/**
     * Return true if book
     *
     * @param category
     * @return
     */
    public boolean isBook(@category int category) {
        if (CATEGORY_BOOK == category) {
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        StringBuffer str = new StringBuffer();
        str.append("Book [");
        str.append(" id=" + id);
        if (!TextUtils.isEmpty(title)) {
            str.append(", title=" + title);
        }
        str.append(", category=" + category);
        if (!TextUtils.isEmpty(summary)) {
            str.append(", summary=" + summary);
        }
        str.append("]");
        return str.toString();
    }
}
        

■ 実装の解説

1. @IntDef

定数の宣言は、Enumでなく@IntDefを使います。
@IntDefを使うことでBuild time safetyを適用し、int変数を使って得られるサイズとパフォーマンスの利点を保てます。

@Retention(RetentionPolicy.SOURCE)
@IntDef({
        CATEGORY_BOOK,
        CATEGORY_KINDLE,
})
public @interface category {
}

public static final int CATEGORY_BOOK = 1;
public static final int CATEGORY_KINDLE = 2;
        

@Retentionはアノテーションの有効範囲です。
RetentionPolicy.SOURCEはソース内のみで、コンパイルすると、classファイル内にアノテーションの情報は残りません。

2. メソッドの引数に使用

宣言したアノテーションはメソッドの引数に適用できます。

public boolean isBook(@category int category) {
    if (CATEGORY_BOOK == category) {
        return true;
    }
    return false;
}
        

このように宣言することで、意図しない定数を使用するとコンパイルエラーが発生するようになるので、安全なコーディングができます。

意図しない定数

アノテーションで宣言した定数を使用

アノテーションで宣言した定数

分かりやすいですね。

まとめ

Support Annotationsを使うと、バグの混入の可能性を減らすコードを書くことができます。
自分以外にも読みやすいコードになるので、なるべく利用するようにしてください。

The price of ENUMs (100 Days of Google Dev)

関連記事

タグ検索で調べてみよう

Android7.0 Android Studio