android.compileOptions.targetCompatibility は sourceCompatibility と一致していなければいけない

(追記: 最初targetCompatibilityを1.6, sourceCompatibilityを1.7にするのがベストだと考えたが、ビルドできなかったので修正)

Android Studio (as of 0.5.5) で新規プロジェクト作成のとき、Java7を選ぶことができる。しかし、Java7に対応しているのは Android 4.4からなので、minSdkVersionが 19 でないかぎりJava7のフル機能を使えるわけではない*1。プロジェクト作成直後は以下の様な build.gradle が作成されるが、Java7で追加されたクラスライブラリの新機能は使えない。

//app/build.gradle
android {
 
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

}

このとき、sourceCompatibilityとtargetCompatibilityが一致していないと "Information:javacTask: source release 1.7 requires target release 1.7" と怒られるので、間違って 1.7 のクラスライブラリを使ってもなにも言われないので注意しないといけない。

Java7の新しいクラスライブラリをうっかり使って実行時例外になるのも困るので、targetCompatibilityは1.6にしておくのがよさそうだ。そうしておくと、 Objects.equals() などを使おうとすると正しくコンパイルエラーになる。

android {
 
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_6
    }

}

*1:Android 4.4でもフル機能を使えるわけではないようだけど。

ASMをつかってdex前にclass fileを覗き見る

Manipulating Java Class Files with ASM 4 - Part One : Hello World!Androidのプロジェクトにやってみた。

まず /buildSrc/build.gradle *1 に ASM の依存を書く。

apply plugin: 'groovy'

repositories {
    mavenCentral()
}

dependencies {
    compile gradleApi()
    compile localGroovy()

    compile 'org.ow2.asm:asm:5.0.+'
}

要はclass fileをコンパイルした直後、dex file (dalvik executable) を生成するまえにASMのClassVisitorで覗き見てやればいいわけで、moduleのbuild.gradleでこんな感じにするとよい。

android.applicationVariants.all { variant ->
    variant.javaCompile.doLast {
        String path = file('build/classes/debug/com/github/gfx/asmexample/app/MainActivity.class')
        ClassDumper.dump([path])
    }
}

あとは、ClassDumperを書く。今回は buildSrc/src/main/groovy/com/github/gfx/asmexample/ClassDumper.groovy にロジックを書いた。中身は上記ブログそのまま。

package com.github.gfx.asmexample

import org.objectweb.asm.*

public class ClassDumper extends ClassVisitor {

    public static void dump(ArrayList<String> args) throws IOException {
        ClassVisitor visitor = new ClassDumper(Opcodes.ASM5);

        for (String file : args) {
            InputStream ins = new FileInputStream(file);

            new ClassReader(ins).accept(visitor, 0);
        }
    }

    public ClassDumper(int v) {
        super(v);
    }

    @Override
    public void visit(int version, int access, String name,
                      String signature, String superName, String[] interfaces) {
        System.out.println("Visiting class: " + name);
        System.out.println("Class Major Version: " + version);
        System.out.println("Super class: " + superName);
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public void visitOuterClass(String owner, String name, String desc) {
        System.out.println("Outer class: " + owner);
        super.visitOuterClass(owner, name, desc);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc,
                                             boolean visible) {
        System.out.println("Annotation: " + desc);
        return super.visitAnnotation(desc, visible);
    }

    @Override
    public void visitAttribute(Attribute attr) {
        System.out.println("Class Attribute: " + attr.type);
        super.visitAttribute(attr);
    }

    @Override
    public void visitInnerClass(String name, String outerName,
                                String innerName, int access) {
        System.out.println("Inner Class: " + innerName + " defined in " + outerName);
        super.visitInnerClass(name, outerName, innerName, access);
    }

    @Override
    public FieldVisitor visitField(int access, String name,
                                   String desc, String signature, Object value) {
        System.out.println("Field: " + name + " " + desc + " value:" + value);
        return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public void visitEnd() {
        System.out.println("Ends here");
        super.visitEnd();
    }

    @Override
    public MethodVisitor visitMethod(int access, String name,
                                     String desc, String signature, String[] exceptions) {
        System.out.println("Method: " + name + " " + desc);
        return super.visitMethod(access, name, desc, signature, exceptions);
    }

    @Override
    public void visitSource(String source, String debug) {
        System.out.println("Source: " + source);
        super.visitSource(source, debug);
    }
}

出力結果:

Visiting class: com/github/gfx/asmexample/app/MainActivity
Class Major Version: 51
Super class: android/app/Activity
Source: MainActivity.java
Inner Class: layout defined in com/github/gfx/asmexample/app/R
Inner Class: menu defined in com/github/gfx/asmexample/app/R
Inner Class: id defined in com/github/gfx/asmexample/app/R
Method: <init> ()V
Method: onCreate (Landroid/os/Bundle;)V
Method: onCreateOptionsMenu (Landroid/view/Menu;)Z
Method: onOptionsItemSelected (Landroid/view/MenuItem;)Z
Ends here

*1:プロジェクトごとのGradleプラグインをしっかり書くときにこのbuildSrcを使う

WEB+DB PRESS Vol.79のiOS7特集を読んだ

iOS7特集と題しつつも、iOSの一般的なことにも触れている。特に、実機へのインストールやPUSH通知についての説明がうれしい。全体的には、iOS開発のチュートリアルを何か一つ終えたあたりに読むと良さそうだ。

ところで、特集内ではバックグラウンドスレッドからメインスレッドに処理を渡すときに [NSObject -performSelectorOnMainThread:withObject:waitUntilDone] を使っているが、これはGCDのほうがいいんじゃないだろうか。

たとえばperformSelectorOnMainThreadだと以下のようになるコードがあるとする:

 [self performSelectorOnMainThread:@selector(foo:) withObject:@"baz" waitUntilDone:NO];

それが、GCDを使うと以下のようになる:

dispatch_async(dispatch_get_main_queue(), ^{
    [self foo:@"baz"];
});

GCDを使うメリットは3つある。

  • GCDのほうがコードが短く、書きやすいし読みやすい
  • performSelectorOnMainThread は引数が0個ないし1個のメソッドしか呼び出せないが、、GCDはブロック単位でスレッドを切り替えるので任意のコードを実行できる
  • performSelectorOnMainThreadに限らないが、selectorを渡して実行するメソッドは動的メソッド呼び出しをするため、コンパイル時に妥当性をチェックできないうえリファクタもしにくい。一方GCDのブロックは静的チェックの対象にでき、リファクタも可能。

一方で、performSelectorOnMainThreadを使うメリットは特にないと思う。

なお、WEB+DB PRESS Vol.79 は技評社からいただきました。ありがとうございます。

WEB+DB PRESS Vol.79

WEB+DB PRESS Vol.79

  • 作者: 成瀬ゆい,そらは(福森匠大),西磨翁,小川航佑,佐藤新悟,塚越啓介,藤原亮,堀哲也,田村孝文,桑野章弘,松浦隼人,中村俊之,田中哲,福永亘,杉山仁則,伊藤直也,登尾徳誠,近藤宇智朗,若原祥正,松木雅幸,奥野幹也,後藤秀宣,羽二生厚美,笹田耕一,平河正博,東舘智浩,渡邊恵太,中島聡,A-Listers,はまちや2,川添貴生,山田育矢,伊藤友隆,村田賢太,まつもとゆきひろ,佐野岳人,山口恭兵,千葉俊輝,平松亮介,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2014/02/22
  • メディア: 大型本
  • この商品を含むブログ (5件) を見る

android.dexOptions.preDexLibrariesを調べた

tips - Improving Build Server performance. :

The Gradle based build system has a strong focus on incremental builds. One way it is doing this in doing pre-dexing on the dependencies of each modules, so that each gets turned into its own dex file (ie converting its Java bytecode into Android bytecode). This allows the dex task to do less work and to only re-dex what changed and merge all the dex files.

While this is great for incremental builds, especially when running from the IDE, this makes the first compilation slower. In general build system will always perform clean builds and this pre-dexing becomes a penality. Since there will not be any incremental builds, it is really not needed to use pre-dexing.

まとめると

  • pre-dexingとは、インクリメンタルビルドのために依存ライブラリを事前にdexに変換すること
  • これはIDEでは必要な設定だけど、常にクリーンビルドするbuild serverでは余計な時間をとるだけなのでdisableしてよい

See Also:

Q. Gradle Wrapper (gradlew) はリポジトリにコミットするの?

短い答え:コミットする。

長い答え:以下参照

Chapter 61. The Gradle Wrapper

The wrapper is something you should check into version control. By distributing the wrapper with your project, anyone can work with it without needing to install Gradle beforehand.

マニュアルにはshouldとあるが、少なくともAndroid開発においてはコミットしなくてもいいケースはない。

gradlewはローカルマシンにGradleをインストールしなくてもビルドできるようにするという側面は確かにあるのだが、GradleはAPI変化の速いプロジェクトなので、gradlewによって使用するGradleのバージョンを固定するという側面もある。

Gradle taskにはデフォルトで"wrapper"というタスクがあり、いつでもgradlewを作れるのだけど、そうやって作ったgradlewのバージョンがそのプロジェクトのビルドに適合している保証はない。またgradleのバージョン違いによるビルドエラーは経験上非常にわかりにくい。

よってgradlewはリポジトリにコミットすべきだし、gradlewのあるプロジェクトでは常にgradlew経由でGradleを使うのがよい。

android-sdk-managerで必要なSDKやビルドツールを自動インストール

builtscriptのdependenciesと apply plugin: "android-sdk-manager" の二行だけで、インストールしたばかりのAndroid SDKしかない状態で ./gradlew assemble ができた。設定が必要ないのはうれしい。

同種のものはほかにもあるけど、JakeWharton氏のプロダクトなのでこれが業界スタンダードになりそう。