Hatena::ブログ(Diary)

Islands in the byte stream

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:

2014-04-06

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を使うのがよい。