EclipseからAndroidStudioへの移行手順まとめ

[AndroidStudio正式版](http://developer.android.com/sdk/index.html)がリリースしました。Eclipse+ADTはオワコンという風潮がより一層強まってきたので、自分がEclipseで開発していたプロジェクトをAndroidStudioに移行した時の手順を残しておこうと思います。

----
#移行するプロジェクトの状態

* コードはgitで管理。
* クラス数は300個くらい。
* mavenもgradleも不使用。ライブラリは`library/`にまるごと入れて、Eclipseでプロジェクトを作ったあとインポート。
* lib、library合わせて依存ライブラリの数は10個以上。

git管理はしてるものの、クラスがそれなりに多いのに依存関係ぐちゃぐちゃで、EventBusとか便利ライブラリ入れていきたいのにちょっと戸惑うなぁという感じでした。「俺もgradleでライブラリ管理したりコンソールから環境ごとにビルドしたりしたいよ！」という思いを胸に秘めながら開発していたわけです。
 

#AndroidStudioのインストール

まずは[AndroidStudio](https://developer.android.com/sdk/index.html)をインストールします。起動してAndroid SDKのパスを設定すれば準備OKです。

 
#プロジェクトの新規作成
EclipseからAndroidStudioへのインポートは、Eclipseのエクスポート機能を使えば簡単に行えます。詳しくは公式の[Migrating to AndroidStudio](http://developer.android.com/sdk/installing/migrate.html)を参考にしてください。

しかし、実際に本番のプロジェクトでやってみると、エラーが出まくってうまくいきませんでした。ライブラリたくさん使った大きめのプロジェクトだとうまくいかないこともあるようです。

また、プロジェクト構成はそのままで、gradleのプロジェクト構成になっていません。これだと結局プロダクトフレーバーといったAndroidStudioの利点が享受できません。

いっそのことgradleプロジェクトを新規作成してbuild.gradleを自分で頑張って書いてコードとリソースをコピーした方が早そうだったので、新規作成することにしました。新規作成はAndroidStudioのダイアログにしたがっていけば迷わずできました。

なんかよくわからない変な感じになったらアンインストールしてやり直した方がいいかもしれません。[AndroidStudio完全アンインストールマニュアル](http://qiita.com/konifar/items/508e26dba72cab52cebd)


#build.gradleを記述

ここが一番の鬼門です。手順としてはこんな感じです。

1. 依存ライブラリをGoogleで調べてgradleの記述方法を見つけて記述
2. gradle syncしてみる
3. エラーが出たらエラー解消、出なければインポートしたライブラリが正しいかを確かめる

例えば、[Aviary](https://developers.aviary.com/docs/android/setup-guide)というライブラリのgradleの記述を『aviary sdk gradle』でGoogleで調べてみると、公式ページにgradleサポートの情報が載っています。サンプルを落としてReadmeを見ると、gradleの記述方法が書いてありました。Aviaryの場合はこんな感じです。

```build.gradle
dependencies {
    compile ('com.aviary.android.feather.sdk:aviary-sdk:3.4.3.351')
}
```

syncする時は、AndroidStudio上部のバーのボタンをクリックします。
![android_studio_migration3.png](https://qiita-image-store.s3.amazonaws.com/0/15267/99038a8d-9195-3111-3e06-41d773014c4c.png "android_studio_migration3.png")

エラーが出たら、メッセージにエラーが表示されます。

![android_studio_migration4.png](https://qiita-image-store.s3.amazonaws.com/0/15267/58664ed8-4677-f938-eb13-980981e442e1.png "android_studio_migration4.png")

基本はこの作業を繰り返していくだけなんですが、たぶん色々エラーにハマるので気合いで乗り切りましょう。
自分がハマったのはこのあたりです。もしかしたら参考になるかもしれません。

* [AndroidStudioでローカルの別プロジェクトをインポートする](http://qiita.com/konifar/items/c9f66482ebdca404f8fa)
* [GooglePlayServicesがgradleでsyncできない時の対処法](http://konifar.com/1420)
* [EclipseからAndroid Studioのせかえ時のgradle対応でハマったエラーまとめ](http://qiita.com/konifar/items/dbbbfd94ad103239c627)

結局出来たbuild.gradleはこんな感じです。（もっといい書き方あったら教えてほしいです）

```build.gradle
apply plugin: 'com.android.application'
 
// load release properties
Properties props = new Properties()
if (rootProject.file("release.properties").exists()) {
    props.load(new FileInputStream(rootProject.file("release.properties")))
} else {
    println "Please create release.properties in project root."
}
 
android {
    compileSdkVersion 19
    buildToolsVersion '19.1.0'
 
    defaultConfig {
        applicationId 'com.konifar'
        minSdkVersion 10
        targetSdkVersion 19
        versionCode 1
        versionName '1.0.0'
    }
    productFlavors {
        development {
            applicationId 'com.konifar'
        }
        staging {
            applicationId 'com.konifar.staging'
        }
        production {
            applicationId 'com.konifar'
        }
    }
 
    signingConfigs {
        release {
            storeFile rootProject.file("konifar.keystore")
            storePassword props.storePassword
            keyAlias props.keyAlias
            keyPassword props.keyPassword
        }
    }
    buildTypes {
        debug {
            // gradle 0.14.+以降だとminifyEnabled
            runProguard false
        }
        release {
            // gradle 0.14.+以降だとminifyEnabled
            runProguard true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }
    packagingOptions {
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/NOTICE.txt'
    }
    dexOptions {
        jumboMode true
    }
}
 
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile project(':libraries:facebook')
    compile ('com.android.support:appcompat-v7:19.+') {
        exclude module: 'support-v4'
    }
    compile ('com.nostra13.universalimageloader:universal-image-loader:1.9.1') {
        exclude module: 'support-v4'
    }
    compile ('com.mobeta.android.dslv:drag-sort-listview:0.6.1') {
        exclude module: 'support-v4'
    }
    compile ('com.jakewharton:butterknife:4.0.1')
    compile 'de.greenrobot:eventbus:2.2.1'
    // - 略 -
}
```

#コードとリソースをコピー

gradle syncして依存ライブラリをダウンロードできたら、Eclipseのプロジェクトのコードとリソースを新しいgradleプロジェクトにコピーします。gradleプロジェクトだと`app/src/main/java/`にコードを配置します。リソースは`app/src/main/res/`です。

これはただコピーするだけで大丈夫でした。lintの設定が違う場合は少しエラーが表示されるかもしれません。`build.gradle`でインポートしたライブラリが間違っていた場合はここでエラーが大量発生するので、頑張ってエラーを解消しましょう。


#ビルドして実行

いよいよビルドして実行です。ここでも色々ハマるポイントはあったので、下記に大体まとめてあります。

* [EclipseからAndroid Studioのせかえ時のgradle対応でハマったエラーまとめ](http://qiita.com/konifar/items/dbbbfd94ad103239c627)
* [Androidでメソッド数が65536を超えた時の対処方法](http://qiita.com/konifar/items/d98c78facbaae63badca)

#Proguardを記述
ビルド時にProguardを使うには、`build.gradle`のbuildTypesに`minifyEnabled true`を記述します。（gradle0.13.+以下だと`runProguard true`です）

```build.gradle
buildTypes {
    debug {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
```

`app/proguard-rules.pro`には、proguardの設定を記述します。

何も指定しないと難読化してはいけないコードまで難読化されてしまって、まったく動かなくなります。keepオプションなどを使っていい感じに除外していく必要があります。例えば、gsonを使う場合は実行時にこんなエラーが出てしまいます。[Proguardを使っている時のgsonのエラー](http://konifar.com/1451)

最終的に下記のような`proguard-rules.pro`を書きました。

```proguard-rules.pro
-dontwarn com.konifar.**
-dontwarn org.apache.**
-dontwarn javax.**
-dontwarn butterknife.**
-dontwarn com.squareup.okhttp.**
 
-keep class com.konifar.** { *; }
-keep interface com.konifar.** { *; }
-keep class com.facebook.** { *; }
-keep interface com.facebook.** { *; }
-keep class org.apache.** { *; }
-keep interface com.google.** { *; }
-keep class com.google.** { *; }
-keep interface java.** { *; }
-keep class java.** { *; }
-keep interface org.json.** { *; }
-keep class org.json.** { *; }
-keep interface android.** { *; }
-keep class android.** { *; }
-keep interface com.aviary.** { *; }
-keep class com.aviary.** { *; }


-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-verbose
-dump class_files.txt
-printseeds seeds.txt
-printusage unused.txt
-printmapping mapping.txt
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
 
-allowaccessmodification
-keepattributes *Annotation*
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
-repackageclasses ''
 
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-dontnote com.android.vending.licensing.ILicensingService
 
# Explicitly preserve all serialization members. The Serializable interface
# is only a marker interface, so it wouldn't save them.
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}
 
# Preserve all native method names and the names of their classes.
-keepclasseswithmembernames class * {
    native <methods>;
}
 
-keepclasseswithmembernames class * {
    public <init>(android.content.Context, android.util.AttributeSet);
}
 
-keepclasseswithmembernames class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}
 
# Preserve static fields of inner classes of R classes that might be accessed
# through introspection.
-keepclassmembers class **.R$* {
  public static <fields>;
}
 
# Preserve the special static methods that are required in all enumeration classes.
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
 
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
} 
 

-keepattributes Signature
 
# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }
 
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }
```

keepオプションなんかはまだまだ超適当です。 本当はもっとちゃんと書かないとダメです。他のプロジェクトのproguardファイルとか見せてもらいたいです。[Android Annotations](http://qiita.com/KeithYokoma/items/2f9dde7edd467e603e97)を使うと、アノテーションでもっと綺麗に記述できるかもしれません。

#変更をgit push

うまくいけばプロジェクトを実行できるはずですが、コードのディレクトリなどがガラッと変わってしまうことでgitの履歴が消えてしまうのは嫌ですよね。

なので、今まで作った構成を参考に元のgit プロジェクトを同じ構成に作り直すことにしました。具体的には`git mv`コマンドを使ってコードとリソースのコピー作業をやり直します。`git mv`を使うと、変更履歴を保持したままファイル位置を変更することができます。

gitプロジェクトを同じ構成に変更できたら、AndroidStudioにインポートして動くかどうか見てみましょう。

 
----

ざっくりですが、こんな流れで移行しました。これから移行を検討している方々の参考になれば幸いです。