Hatena::ブログ(Diary)

Islands in the byte stream

2014-04-19

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
    }

}

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

2014-04-17

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を使う

2014-04-16

Android端末でassertを有効にする

以下のやりかたでできました (Android 4.1.1 GenyMotion)。わりと簡単にできるので、検証端末は常にenabledでよさそう。

adb shell setprop debug.assert 1 # enable assert
adb shell setprop debug.assert 0 # disable assert

2014-04-12

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 は技評社からいただきました。ありがとうございます。

2014-04-07

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: