Annotation Processing(apt)のまとめ+AndroidAnnotations(AA)とAutoValueのサンプルコード書いてみた。

  • 53
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

最初に

本来は@takahiromさんが書く日だったのですが。もう埋まっちゃったよ!と僻んでいたら枠もらえました。本当にありがとうございました!

@takahiromさんとはある勉強会でお会いしたのを機にPreLollipopTransitionなどで絡み出した縁で仲良くさせて頂いています。@takahiromさんの記事を期待していた方はこちらへおまわりください。

そもそも誰?

本題に入る前に、そもそもお前誰だ?なんでAPTを語る?って人が多いと思うので、自己紹介させて下さい。
Annotation Processing Tool(またはAPT)でのコード自動生成が好き過ぎて自作のステートパターン用APTライブラリ公開しただけでは飽きたらず、AndroidライブラリでAnnotationといえば、AndroidAnnotations(AA)でしょってことで、PRを投げ始めました。ついさっき何個目かのPRがマージされました:v:

contributors.png

(Collaboratorではないです。)

なので、ある程度APTに関しては理解しているつもりですが、間違った理解をしている可能性もありますので、その点は優しく指摘して頂ければと思います。

本題

Annotation Processingをこの1年触り続けていて、いろいろわかったことが増えたので参考になる記事やサンプルコードをまとめたいと思います。

Annotation Processing(APT)って何?

@opengl-8080さんのPluggable Annotation Processing API 使い方メモが非常に詳しく記載されています。

どうAndroid開発で利用出来るかはJakeさんのスライドを参照して下さい。

APTライブラリがなぜ流行っているのかというのをざっくりまとめると、

  • リフレクションなどと違い、実際のコードを生成するのでオーバーヘッドがない(ButterKnifeのように生成したコード内でリフレクション使っているライブラリもあります。)
  • ライブラリとしてランタイムで含まれるコード量は少ない(またはない)ので、apkのサイズが小さく保てる
  • 実際に生成されたコードを確認出来るので、デバッグしやすい

APTライブラリの使い所

APTライブラリの特徴は以下のものが多い印象があります

  • 開発者の大半の人が書かなくてはならないコードを書かなくても良くする
  • 特殊な使い方をするための機能は実装しない
  • 必要なコードだけ生成する

この特徴から、私はAPTライブラリ選定基準を以下としています。

  • dependanciesの指定がprovidedでいけるもので、機能が優れているなら即導入。
  • compile指定なものは何がruntimeで必要なのか確認し、機能が優れていて、増えるファイルサイズ次第では導入する。

開発中のアプリへの導入時は以下の点に気をつけています。

  • 新規のクラスに対しての導入は特に問題がないので、慣れるまでは新規クラスに対してのみ導入する。
  • 既存のものに導入するときは、生成されたコードとの重複コードに要注意。リファクタリングは必須。作り直しになる可能性大。無理しない。命大事に。

APT作ってみたい!

@rejasupotaroさんのAndroidでaptのライブラリを作るときの高速道路という素晴らしい記事がありますのでこちらを参照して下さい。

APTライブラリ作成に便利なツール

重複しているかもしれませんが、APTライブラリを作成時に便利なライブラリがありますので、その紹介をしておきます。

android-apt

Android StudioでAPTライブラリを動かすのに便利なgradle plugin。公式では2点便利なポイントがあげられています。

  • Annotation Processingライブラリをコンパイルタイムだけ含まれるように出来る
  • Source Pathを正しく設定してくれるので、自動生成されたソースをAndroid Studio上で確認出来る

非常に良いpluginで最近のAnnotation Processingライブラリでは多くのライブラリが導入手順内で採用を促しています。
Android Gradleプラグインに取り込まれたら良いのにと言っている方も結構います。自分含め。

こんな感じで使います。

build.gradle
buildscript {
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt' // android-aptを指定

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.github.shiraji.sample"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

// いくつかaptを使うと重複してコンパイルエラーになることがあるので、おまじない。
android {
    packagingOptions {
        exclude 'META-INF/services/javax.annotation.processing.Processor'
    }
}

dependencies {
    provided 'com.github.shiraji:kenkenpa:1.0.3'
    apt 'com.github.shiraji:kenkenpa-compiler:1.0.3' // apt指定が出来るようになる。
}

build.gradleの指定以外特に何もしなくて良いので、使い勝手が良いです。

Javapoet

コード生成するコードはかなり複雑で、変数一つ作成したり読み込んだりするのに非常に多くのコーディングが必要になります。それを便利にしてくれるライブラリがJavapoet。新しくAnnotation Processingライブラリ作成するなら必須だと思います。
ちなみに、下で紹介するライブラリのうち、squareさん以外のものはまだこのライブラリを導入していないものもあり、そのプロジェクトのコード読むと結構辛い思いをします。

Javapoetの詳細は@opengl-8080さんのJavaPoet 使い方メモが詳しいです。

jcodemodel

com.sun.codemodelをforkしたライブラリ。Javapoetを使ってないライブラリでは利用されていることがある。com.sun.codemodelの使いにくい部分が修正されています。歴史も長く、2005年くらいから開発されています。これから新規で作るのであれば、残念ながら、Javapoetを利用したほうがシンプルに構成出来ると思います。

AndroidのAPTライブラリ

有名どころだと

開発が止まっちゃっていますが、日本で有名なJsonPullParserもAnnotation Processingを利用しています。

もうすぐ正式版になるAndroid-Ormaも面白そうです。いつかPR投げることができたらなーと狙ってます。

この中で自分がよく知っているAndroidAnnotations/AutoValueの使い方などを紹介しようと思います。

AndroidAnnotations(AA)

AAは歴史が長く、2010年から開発されており、eBusinessInformationという会社がスポンサーとしてコアコミッターをつけ、AAの開発が活発に行われるよう手助けしてくれています。
このプロジェクトの開発に自分が携わり出したのは2015年10月頃で、上記の通りどうせ好きなら一番すげーところでコード書いたろ!という邪な気持ちで参加しました。

こんなルーズな開発者でも受け入れてくれた要因を上げるとすると

  • issueのラベリングがしっかりしていた
  • どこの議論の場でもレスポンスが早い。
  • スキルレベルが高く、コードレビューの指摘が鋭い。
  • 英語ルーズでも案外なんとかなる。
  • コマンド一発でテストしっかり実施してくれる。

特にContributionWelcomeラベルは助かりました。

さて、そんなAAですが、AOSPの開発速度が早過ぎるため、開発者不足に陥っております。
結構重要な機能も後回しにせざるを得ない状況です。
日本のAndroiderさんでOSSやりたいと思っている方ぜひ一緒にやりませんか?

AAを使ってみる

勝手にAAの紹介+勧誘したところで、実際どれくらい使えるの?ということを説明したいと思います。

これも案の定すでにQiitaにあって、@hiroqさんが公開してくれている、Android StudioでAndroidAnnotations3.2を使う方法を読んでその通りにやれば利用出来ます。

この手順に沿って、簡単なFragment付きのActivityの遷移があるプロジェクトを作ってみます。

現在AAの最新バージョンは3.3.2ですので、それを利用します。

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button toAAButton = (Button) findViewById(R.id.to_aa_button);
        toAAButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AAActivity_.intent(MainActivity.this).start();
            }
        });
    }
}
AAActivity.java
@EActivity(R.layout.aaactivity_layout)
public class AAActivity extends AppCompatActivity {
    @ViewById(R.id.textView)
    TextView mTextView;

    @Background(delay = 2000)
    void doInBackgroundAfterTwoSeconds() {
        logTextView();
    }

    @UiThread(delay = 1000)
    void doInUiThread() {
        mTextView.setText(R.string.changed);
    }

    @AfterViews
    void afterInject() {
        logTextView();

        doInBackgroundAfterTwoSeconds();
        doInUiThread();
    }

    private void logTextView() {
        Log.i("AAActivity", "TextView: " + mTextView.getText());
    }
}
AAFragment
@EFragment(R.layout.aafragment_layout)
public class AAFragment extends Fragment {
}
aaactivity_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="AAActivity"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <fragment
        android:id="@+id/efragment"
        android:name="com.github.shiraji.aa_sample.AAFragment_"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.github.shiraji.aa_sample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".AAActivity_" />

    </application>

</manifest>
app/build.gradle
apply plugin: 'com.android.application'
+apply plugin: 'android-apt'
+
+def AAVersion = '3.3.2'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.github.shiraji.aa_sample"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
+
+    apt "org.androidannotations:androidannotations:$AAVersion"
+    compile "org.androidannotations:androidannotations-api:$AAVersion"
}
+
+apt {
+    arguments {
+        androidManifestFile variant.outputs[0]?.processResources?.manifestFile
+    }
+}

生成されたコード

AAActivity_.java
public final class AAActivity_
    extends AAActivity
    implements HasViews, OnViewChangedListener
{

    private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier();
    private Handler handler_ = new Handler(Looper.getMainLooper());

    @Override
    public void onCreate(Bundle savedInstanceState) {
        OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
        init_(savedInstanceState);
        super.onCreate(savedInstanceState);
        OnViewChangedNotifier.replaceNotifier(previousNotifier);
        setContentView(layout.aaactivity_layout);
    }

    private void init_(Bundle savedInstanceState) {
        OnViewChangedNotifier.registerOnViewChangedListener(this);
    }

    @Override
    public void setContentView(int layoutResID) {
        super.setContentView(layoutResID);
        onViewChangedNotifier_.notifyViewChanged(this);
    }

    @Override
    public void setContentView(View view, LayoutParams params) {
        super.setContentView(view, params);
        onViewChangedNotifier_.notifyViewChanged(this);
    }

    @Override
    public void setContentView(View view) {
        super.setContentView(view);
        onViewChangedNotifier_.notifyViewChanged(this);
    }

    public static AAActivity_.IntentBuilder_ intent(Context context) {
        return new AAActivity_.IntentBuilder_(context);
    }

    public static AAActivity_.IntentBuilder_ intent(android.app.Fragment fragment) {
        return new AAActivity_.IntentBuilder_(fragment);
    }

    public static AAActivity_.IntentBuilder_ intent(android.support.v4.app.Fragment supportFragment) {
        return new AAActivity_.IntentBuilder_(supportFragment);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (((SdkVersionHelper.getSdkInt()< 5)&&(keyCode == KeyEvent.KEYCODE_BACK))&&(event.getRepeatCount() == 0)) {
            onBackPressed();
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public void onViewChanged(HasViews hasViews) {
        mTextView = ((TextView) hasViews.findViewById(id.textView));
        afterInject();
    }

    @Override
    public void doInUiThread() {
        handler_.postDelayed(new Runnable() {


            @Override
            public void run() {
                AAActivity_.super.doInUiThread();
            }

        }
        , 1000L);
    }

    @Override
    public void doInBackgroundAfterTwoSeconds() {
        BackgroundExecutor.execute(new BackgroundExecutor.Task("", 2000, "") {


            @Override
            public void execute() {
                try {
                    AAActivity_.super.doInBackgroundAfterTwoSeconds();
                } catch (Throwable e) {
                    Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
                }
            }

        }
        );
    }

    public static class IntentBuilder_
        extends ActivityIntentBuilder<AAActivity_.IntentBuilder_>
    {

        private android.app.Fragment fragment_;
        private android.support.v4.app.Fragment fragmentSupport_;

        public IntentBuilder_(Context context) {
            super(context, AAActivity_.class);
        }

        public IntentBuilder_(android.app.Fragment fragment) {
            super(fragment.getActivity(), AAActivity_.class);
            fragment_ = fragment;
        }

        public IntentBuilder_(android.support.v4.app.Fragment fragment) {
            super(fragment.getActivity(), AAActivity_.class);
            fragmentSupport_ = fragment;
        }

        @Override
        public void startForResult(int requestCode) {
            if (fragmentSupport_!= null) {
                fragmentSupport_.startActivityForResult(intent, requestCode);
            } else {
                if (fragment_!= null) {
                    if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
                        fragment_.startActivityForResult(intent, requestCode, lastOptions);
                    } else {
                        fragment_.startActivityForResult(intent, requestCode);
                    }
                } else {
                    if (context instanceof Activity) {
                        Activity activity = ((Activity) context);
                        ActivityCompat.startActivityForResult(activity, intent, requestCode, lastOptions);
                    } else {
                        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
                            context.startActivity(intent, lastOptions);
                        } else {
                            context.startActivity(intent);
                        }
                    }
                }
            }
        }

    }

}

AAFragment_.java
public final class AAFragment_
    extends com.github.shiraji.aa_sample.AAFragment
    implements HasViews
{

    private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier();
    private View contentView_;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
        init_(savedInstanceState);
        super.onCreate(savedInstanceState);
        OnViewChangedNotifier.replaceNotifier(previousNotifier);
    }

    @Override
    public View findViewById(int id) {
        if (contentView_ == null) {
            return null;
        }
        return contentView_.findViewById(id);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        contentView_ = super.onCreateView(inflater, container, savedInstanceState);
        if (contentView_ == null) {
            contentView_ = inflater.inflate(layout.aafragment_layout, container, false);
        }
        return contentView_;
    }

    @Override
    public void onDestroyView() {
        contentView_ = null;
        super.onDestroyView();
    }

    private void init_(Bundle savedInstanceState) {
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        onViewChangedNotifier_.notifyViewChanged(this);
    }

    public static AAFragment_.FragmentBuilder_ builder() {
        return new AAFragment_.FragmentBuilder_();
    }

    public static class FragmentBuilder_
        extends FragmentBuilder<AAFragment_.FragmentBuilder_, com.github.shiraji.aa_sample.AAFragment>
    {


        @Override
        public com.github.shiraji.aa_sample.AAFragment build() {
            AAFragment_ fragment_ = new AAFragment_();
            fragment_.setArguments(args);
            return fragment_;
        }

    }

}

どうやったらBackgroundで処理を実行するのかなどを考える必要はありません。Backgroundでの処理なのか、UiThreadなのかなどをメソッドではっきり分けられるのもコードが見やすくなります。

プロジェクト全体にAAを反映させるのが一番良いと考えていますが、既存のアプリでも新規追加するクラスからAAを利用することが可能ということを証明するため、MainActivityはAAを利用していません。

サンプルコードはGitHubにおいておきました。

他にも例えば、versionによって、getColor()Resourceから取得するのか、ContextCompatからするのかなどを利用しているライブラリのバージョンの違いを吸収してくれます。

これがまさにAAが行いたい

[AA] takes care of the plumbing, and lets you concentrate on what's really important.1

他の機能にも興味がある方はAAが提供しているAnnotationリストを参照して下さい。

また、最近流行りのKotlinでもAAは利用出来ることがkotlin公式ブログのBetter Annotation Processing: Supporting Stubs in kaptでkaptの使い方のサンプルとして紹介されています。AAのContributorさんたちもKotlinでの動作を確認しています。Kotlinの利用考えている方はぜひこちらも確認してみて下さい。

AAの次のバージョンについて

サンプルコードを書いてまで導入促しておいてですが、実は次のバージョン更新はメジャーバージョンアップデートとして、v4.0.0になります。

このバージョンがいつリリースされるかは現在話し合いの最中です。はっきりとした日付はわからないのですが、allow method injectionはマージしたいなという話は上がっていて、この投稿の数時間前にマージされました(大慌てで文章変えました)。このissueのwikiが完成し次第、リリース準備に取り掛かる可能性が高いです。

どんな機能がv4.0.0に組み込まれるかはmilestone 4.0を確認して下さい。移行方法のドキュメントも作成予定です。

もし、これから新規で導入を検討するならv4.0.0が安定したバージョンになったタイミングで導入検証することをおすすめします。

AutoValue

モバイルDevOps Advent Calendar 2日目劇的にAndroid開発を楽にするコードの自動生成、2つの重大な使いどころ@kikuchyさんからのパスを受けて、AutoValueの使い方も紹介します。(ご期待に応えられていない気がしてますが。。。)

といっても、AutoValueのサンプルコードもすでにQiitaにあるので、@rejasupotaroさんの自動生成によるクラス作成の補助#autoを見てもらうとわかりやすいと思います。

ただ、この記事が投稿された後の2015年8月に非常に大きな機能がマージされました。まだSNAPSHOT版としてのみ公開されていますが、この機能によりAutoValueを拡張することが出来るようになりました。

すでにいくつか拡張ライブラリが作成され公開されています。

この拡張ライブラリのAutoValue: Parcel Extensionを使ってコードを生成してみたいと思います。
(正式版がリリースされたら手順は修正します。)

sonatypeの設定を追加し、AutoValueのSNAPSHOTを使えるようにします。
先ほど記載した、android-aptも使えるようにします。

build.gradle
@@ -3,10 +3,14 @@
 buildscript {
     repositories {
         jcenter()
+        mavenCentral()
+        maven {
+            url = 'https://oss.sonatype.org/content/repositories/snapshots'
+        }
     }
     dependencies {
         classpath 'com.android.tools.build:gradle:1.3.0'
-
+        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }
 @@ -15,6 +19,9 @@ buildscript {
 allprojects {
     repositories {
         jcenter()
+        maven {
+            url = 'https://oss.sonatype.org/content/repositories/snapshots'
+        }
     }
 }

AutoValueと拡張ライブラリをインポートします。

app/build.gradle
 apply plugin: 'com.android.application'
+apply plugin: 'com.neenbedankt.android-apt'

 android {
     compileSdkVersion 23
 @@ -24,4 +25,7 @@ dependencies {
     testCompile 'junit:junit:4.12'
     compile 'com.android.support:appcompat-v7:23.1.1'
     compile 'com.android.support:design:23.1.1'
+
+    provided 'com.google.auto.value:auto-value:1.2-SNAPSHOT'
+    apt 'com.ryanharter.auto.value:auto-value-parcel:0.2-SNAPSHOT'
 }

これで設定周りは完了。

String fooとint barを保持したParcelableクラスを作成したいとし、実際にコードを書いてみます。

AutoValueParcelSample.java
@AutoValue
public abstract class AutoValueParcelSample implements Parcelable {

    public abstract String bar();

    public abstract int foo();

    public static AutoValueParcelSample create(String bar, int foo) {
        return new AutoValue_AutoValueParcelSample(bar, foo);
    }
}

このクラスを利用するには以下のようなコードを書きます。

MainActivity.java
    AutoValueParcelSample sample = AutoValueParcelSample.create("baaaar", 100);
    Log.d("MainActivity", sample.toString());

これで、runしてみます。

D/MainActivity: AutoValueParcelSample{bar=baaaar, foo=100}

AutoValueParcelSample@1234a5などではなく、しっかりとtoString()が機能しています。

AutoValueのコードの説明

実装したコードと生成コードの説明をします。

いきなり出てきたAutoValue_AutoValueParcelSampleって何か?というと、これがAnnotation Processingで自動生成されるクラスです。
AutoValueでは、必ず生成されたクラスはAutoValue_というprefixが付く仕様です。拡張ライブラリもAutoValue_がつきます。ただし、createメソッドで生成方法を提供しているので、AutoValueParcelSampleを使う人はAutoValue_AutoValueParcelSampleの存在を知らなくても特に問題ありません。

実際に生成されたAutoValue_AutoValueParcelSampleを見てみます。
ファイルはapp/build/generated/source/apt配下にあります。

final class AutoValue_AutoValueParcelSample extends $AutoValue_AutoValueParcelSample {
  private static final ClassLoader CL = AutoValue_AutoValueParcelSample.class.getClassLoader();

  public static final Parcelable.Creator<AutoValue_AutoValueParcelSample> CREATOR = new android.os.Parcelable.Creator<AutoValue_AutoValueParcelSample>() {
    @java.lang.Override
    public AutoValue_AutoValueParcelSample createFromParcel(android.os.Parcel in) {
      return new AutoValue_AutoValueParcelSample(in);
    }
    @java.lang.Override
    public AutoValue_AutoValueParcelSample[] newArray(int size) {
      return new AutoValue_AutoValueParcelSample[size];
    }
  };

  AutoValue_AutoValueParcelSample(String bar, int foo) {
    super(bar, foo);
  }

  private AutoValue_AutoValueParcelSample(Parcel in) {
    super(
      in.readString(),
      in.readInt()
    );
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(bar());
    dest.writeInt(foo());
  }

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

Parcelableに関する処理が記載されています。
親クラスである$AutoValue_AutoValueParcelSampleを確認してみます。

 abstract class $AutoValue_AutoValueParcelSample extends AutoValueParcelSample {

  private final String bar;
  private final int foo;

  $AutoValue_AutoValueParcelSample(
      String bar,
      int foo) {
    if (bar == null) {
      throw new NullPointerException("Null bar");
    }
    this.bar = bar;
    this.foo = foo;
  }

  @Override
  public String bar() {
    return bar;
  }

  @Override
  public int foo() {
    return foo;
  }

  @Override
  public String toString() {
    return "AutoValueParcelSample{"
        + "bar=" + bar + ", "
        + "foo=" + foo
        + "}";
  }

  @Override
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }
    if (o instanceof AutoValueParcelSample) {
      AutoValueParcelSample that = (AutoValueParcelSample) o;
      return (this.bar.equals(that.bar()))
           && (this.foo == that.foo());
    }
    return false;
  }

  @Override
  public int hashCode() {
    int h = 1;
    h *= 1000003;
    h ^= this.bar.hashCode();
    h *= 1000003;
    h ^= this.foo;
    return h;
  }

}

このクラスでは、元々のAutoValueが提供している、toString()/equals()/hashCode()の自動生成したコードが含まれています。クラス構成はこんな感じになります。

Parcelable
   └── AutoValueParcelSample // 書いたコード
        └── $AutoValue_AutoValueParcelSample // 自動生成されたauto-valueが提供する実装コード
             └── AutoValue_AutoValueParcelSample // 自動生成されたParcelableの実装コード

10行くらいのコードを書いただけで、Pacelableの実装/toString()/equals()/hashCode()が提供されるようになりました。合計すると100行ほどの書くのは面倒だけど書いてあれば便利なコードが生成されます。

全部揃ったソースも確認してみてください。
※AutoValueには他にも独自のtoString()/equals()/hashCode()メソッドの実装(underride)やBuilderクラス作成も可能です。詳細はREADMEを確認して下さい。

providedによる指定をしているため、コード生成で利用したコードはapkに含まれません。ClassySharkで確認してみます。

classy.png

apk内にはsupportライブラリだけしか含まれていないことがわかります。(単にClassyShark使ってみたかっただけです!)

最後に

本稿でメンション、記事の紹介、ライブラリを紹介されてしまった方々、勝手に記載してしまい、本当に申し訳ありません。批判するような内容を記載しているつもりはありませんが、消してほしいという方いらっしゃいましたら、Twitterで@shiraj_i宛にメンション下さい。即対応します。

注釈

この投稿は Android Advent Calendar 201521日目の記事です。
  • この記事は以下の記事からリンクされています
  • Custom Lint Rulesからリンク