2011年3月18日金曜日

Usage of dx --dex (dx --dex の使い方)

USAGE

    dx --dex [OPTIONS] FILES...


OVERVIEW

    Convert Java class files into Dalvik dex format.


OPTIONS

    --

        Stops parsing options. Arguments after this are regarded as FILES
        even if they start with "-". Note that parsing options is stopped
        also when an argument that does not start with "-" is found.

    --debug

    --verbose

    --verbose-dump

    --no-files

        Ignores input files. If this option is given, FILES specified on
        the command line are ignored. To put it another way, if this
        option is not given, FILES must be specified on the command line.

    --no-optimize

        Skips optimization on methods. The resultant dex file is not
        optimized but is generated speedily. See --optimize-list option.

    --no-strict

        Skips the following checks.

          1. Whether the format version of input class files are in the
             range which this dx command can handle.

          2. Whether path names of input class files match the declared
             package/class names.

    --core-library

        Allows classes under the following packages to be contained as
        input files. Note that javax sub packages that are not listed below
        can be contained without this option except classes which directly
        belong to javax package (e.g. javax.MyClass).

          java
          javax.accessibility
          javax.crypto
          javax.imageio
          javax.management
          javax.naming
          javax.net
          javax.print
          javax.rmi
          javax.security
          javax.sound
          javax.sql
          javax.swing
          javax.transaction
          javax.xml

    --statistics

    --optimize-list=FILE

        Specifies a file which contains a newline-separated list of method
        names. Methods listed in the file are optimized. Note that if this
        option is given, methods which are not explicitly listed are not
        optimized.

        This option and --no-optimize-list option are mutually exclusive.
        If neither of this option nor --no-optimize-list option is given,
        all methods are optimized by default unless --no-optimize option
        is given.

    --no-optimize-list=FILE

        Specifies a file which contains a newline-separated list of method
        names. Methods listed in the file are not optimized. Note that if
        this option is given, methods which are not explicitly listed are
        optimized.

        This option and --optimize-list option are mutually exclusive. If
        neither of this option nor --optimize-list option is given, all
        methods are optimized by default unless --no-optimize option is
        given.

    --keep-classes

        Adds input class files into the output dex file. Output files
        generated with this option contain both classes.dex file and
        .class files, so the size is larger than ones generated without
        this option, but such files can be used both on Dalvik VM and
        Java Standard Edition VM.

    --output=FILE

        Specifies the name of the output file. The extension of FILE must
        be one of the following: ".zip", ".jar", ".apk" and ".dex". "-" is
        also allowed as FILE and if it is specified, the output goes to the
        standard output.

    --dump-to=FILE

        Specifies where human-readable output should go. If --dump-method
        option is not given, the default value used when this option is
        not explicitly specified is "-" which means that human-readable
        output is written to the standard output.

    --dump-width=N

        Specifies the maximum width of the human-oriented output. N must
        be equal to or greater than 40.

    --dump-method=METHOD

        Dumps information about a specified method in human-readable format.
        If this option is given, the normal dex output is not generated.

        METHOD is a fully-qualified method name. The last letter of METHOD
        can be '*', and in such a case, '*' is regareded as a wildcard and
        all methods that match the pattern are dumped.

    --positions=POSITION

        Specifies how much position information should be preserved.
        Possible values of POSTION and their meanings are as follows.
        The default value is "lines".

          "none"         No position information is preserved.

          "lines"        Line number information is preserved.

          "important"    Important position information is preserved.
                         This contains block starts and instructions
                         that might throw.

    --no-locals

        Discards information about local variables.


FILES

    Class files, jar files or directories which contain class files.


-------------------------------------------------

■ 使い方

    dx --dex [オプション] ファイル...


■ 概要

    Java クラスファイルを Dalvik DEX フォーマットに変換する。


■ オプション

    --

        オプション解析を終了する。これに続く引数は、たとえ "-" で始まって
        いてもファイルとみなされる。"-" で開始しない引数が見つかったときにも
        オプション解析は終了する。

    --debug

    --verbose

    --verbose-dump

    --no-files

        入力ファイルを無視する。このオプションが与えられると、コマンド
        ライン上に指定されたファイル群は無視される。別の言い方をすると、
        このオプションを与えない場合は、コマンドライン上でファイル群を
        必ず指定しなければならない。

    --no-optimize

        メソッドの最適化をおこなわない。出力 DEX ファイルは最適化されないが、
        生成処理は速くなる。--optimize-list オプションを参照のこと。

    --no-strict

        次のチェックをおこなわない。

          1. 入力クラスファイルのフォーマットバージョンが、dx コマンドが
             扱える範囲にあるかどうか。

          2. 入力クラスファイルのパス名と、宣言されているパッケージ/
             クラス名が合っているかどうか。

    --core-library

        下記のパッケージ下のクラスが入力ファイルに含まれていても許容する。
        このオプションを与えなくても、下記にリストされていない javax サブ
        パッケージを含めることはできるが、javax パッケージに直接属するクラス
        (javax.MyClass 等) は除く。

          java
          javax.accessibility
          javax.crypto
          javax.imageio
          javax.management
          javax.naming
          javax.net
          javax.print
          javax.rmi
          javax.security
          javax.sound
          javax.sql
          javax.swing
          javax.transaction
          javax.xml

    --statistics

    --optimize-list=FILE

        改行で区切られたメソッド名のリストを含むファイルを指定する。その
        ファイルにリストされているメソッド群は最適化される。このオプションが
        指定された場合、明示的にリストされていないメソッドは最適化されない
        ので注意。

        このオプションと --no-optimize-list オプションは相互排他である。
        このオプションも --no-optimize-list オプションも与えられなかった
        場合、--no-optimize オプションが与えられない限り、デフォルトで
        全てのメソッドが最適化される。

    --no-optimize-list=FILE

        改行で区切られたメソッド名のリストを含むファイルを指定する。その
        ファイルにリストされているメソッド群は最適化されない。このオプションが
        指定された場合、明示的にリストされていないメソッド群は最適化される。

        このオプションと --optimize-list オプションは相互排他である。この
        オプションも --optimize-list オプションも与えられなかった場合、
        --no-optimize オプションが与えられない限り、デフォルトで全ての
        メソッドが最適化される。

    --keep-classes

        入力クラスファイル群を出力 DEX ファイルに追加する。このオプションを
        与えて生成されたファイルは classes.dex ファイルと .class ファイル群の
        両方を含む。そのため、このオプション無しで生成されたものよりもサイズは
        大きくなるが、Dalvik VM と Java Standard Edition VM の両方で使用する
        ことができるようになる。

    --output=FILE

        出力ファイルの名前を指定する。FILE の拡張子は、".zip", ".jar", ".apk",
        ".dex" のいずれかでなければならない。FILE として "-" を指定することも
        可能で、その場合、出力は標準出力に対してなされる。

    --dump-to=FILE

        人間が読める形式の出力の送り先を指定する。--dump-method オプションが
        与えられていない場合、このオプションが明示的に指定されていないときの
        デフォルト値は "-" であり、これは、人間が読める形式の出力が標準出力に
        書き込まれることを意味する。

    --dump-width=N

        人間が読める形式の出力の最大幅を指定する。N は 40 以上でなければならない。

    --dump-method=METHOD

        指定されたメソッドに関する情報を人間が読める形式で出力する。この
        オプションが与えられた場合、通常の DEX 出力は生成されない。

        METHOD は完全修飾メソッド名である。METHOD の最後の文字を '*' にする
        ことができ、その場合は '*' はワイルドカードとみなされ、そのパターンに
        適合する全てのメソッドがダンプされる。

    --position=POSITION

        位置情報をどの程度保持するかを指定する。POSITION に取りうる値とそれらの
        意味は下記のとおり。デフォルト値は "lines"。

          "none"         位置情報を保持しない。

          "lines"        行番号情報を保持する。

          "important"    重要な位置情報を保持する。これには、ブロックの
                         開始や例外を投げうるインストラクションが含まれる。

    --no-locals

        ローカル変数に関する情報を破棄する。


■ ファイル...

    クラスファイル、JAR ファイル、もしくはクラスファイルを含むディレクトリ。

2011年3月11日金曜日

SQLiteDatabase is closed automatically under some conditions

To say the result of investigation first, "A database (android.database.sqlite.SQLiteDatabase) used to create a cursor (android.database.Cursor) is automatically closed if some simple conditions are satisfied."

The flow which triggers the auto close is as follows.

  1. Actual data fetch is triggered when either Cursor.getCount() method or Cursor.onMove() method is called.
  2. It triggers SQLiteQuery.fillWindow(CursorWindow, int, int) method to be called.
  3. SQLiteQuery.fillWindow() calls SQLiteQuery.releaseReferences() as its last step.
  4. The implementation of SQLiteQuery.releaseReference() is in its indirect super class, SQLiteClosable. (SQLiteQuery extends SQLiteProgram, and SQLiteProgram extends SQLiteClosable.)
  5. SQLiteClosable.releaseReference() decrements the reference count.
  6. If the reference count becomes 0, SQLiteClosable calls an abstract method, onAllReferencesReleased().
  7. The super class of SQLiteQuery, SQLiteProgram, has the implementation of onAllReferencesReleased().
  8. SQLiteProgram.onAllReferencesReleased() calls releaseReference() method on an SQLiteDatabase instance it holds.
  9. SQLiteDatabase also extends SQLiteClosable. As a result, SQLiteDatabase.onAllReferencesReleased() is called.
  10. SQLiteDatabase.onAllReferencesReleased() calls a native method, dbclose().
  11. dbclose() closes the underlying database by calling sqlite3_close().
Therefore, the coding like below:

  1. Create a cursor.
  2. Create a cursor adapter using the created cursor.
  3. Pass the cursor adapter to a ListView using ListView.setAdapter() method.

causes the database (SQLiteDatabase) to be closed automatically because the implementation of ListView.setAdapter() calls getCount() method of the given cursor and so the steps from (1) to (11) are performed. On the contrary, if the step (1) is not started after a cursor is created, the auto close won't occur and the database remains open.

If you encounter one of the following error messages, there is a possibility that the error is caused by the database auto close. (These are error messages that are contained in the current or past implementations of SQLiteClosable.acquireReference() method.)

"attempt to re-open an already-closed object: " + getObjInfo()
"attempt to acquire a reference on an already-closed " + getObjInfo()
"attempt to acquire a reference on an already-closed SQLiteClosable obj."
"attempt to acquire a reference on a close SQLiteClosable"

If you want to prevent the database from being automatically closed, one possible solution would be to call SQLiteClosable.acquireReference() to prevent the reference count of the database from getting down to 0. In such a case, the database needs to be closed later manually, of course.


ある条件下で SQLiteDatabase は自動的にクローズされる

調査結果を先に言うと、「カーソル(android.database.Cursor)作成時に使用したデータベース(android.database.sqlite.SQLiteDatabase)は、ある要件を満たすと、自動的にクローズされる。

自動クローズが発生するときの処理の流れは次のようになっている。

  1. カーソルの Cursor.getCount() メソッドもしくは onMove() メソッドが呼ばれると、実際のデータ取得処理が走る。
  2. これにより SQLiteQuery.fillWindow(CursorWindow, int, int) メソッドが呼ばれることになる。
  3. SQLiteQuery.fillWindow() は、最後に SQLiteQuery.releaseReference() を呼ぶ。
  4. SQLiteQuery.releaseReference() の実体は、親(SQLiteProgram)の親クラスの SQLiteClosable にある。
  5. SQLiteClosable.releaseReference() は、参照カウントを減らす。
  6. 参照カウントがゼロになると、SQLiteClosable は抽象メソッド onAllReferencesReleased() を呼ぶ。
  7. SQLiteQuery の親クラス SQLiteProgram が onAllReferencesReleased() を実装している。
  8. SQLiteProgram.onAllReferencesReleased() は、保持している SQLiteDatabase のインスタンスの releaseReference() メソッドを呼ぶ。
  9. SQLiteDatabase も SQLiteClosable を継承しているので、結果、SQLiteDatabase.onAllReferencesReleased() が呼ばれる。
  10. SQLiteDatabase.onAllReferencesReleased() は、ネイティブメソッド dbclose() を呼ぶ。
  11. dbclose() は、sqlite3_close() を呼んでデータベースを閉じる。

そういうわけで、

  1. カーソルを作成する。
  2. 作成したカーソルをもとにカーソルアダプタを作成する。
  3. カーソルアダプタをリストビューの setAdapter() メソッドを用いてリストビューに渡す。

というコーディングをすると、リストビューの setAdapter() メソッドの実装がカーソルの getCount() メソッドを呼ぶので、結果上記 (1)~(11) の処理が走り、データベース (SQLiteDatabase) は自動的にクローズされることになる。逆に言うと、カーソルを作成したものの、上記 (1) が発生しないと、データベースの自動クローズ処理は走らないので、放っておくとリークしてしまう。

もしも次のいずれかのエラーメッセージに遭遇したなら、データベースの自動クローズが走ってしまったことが原因の可能性がある。(これらは過去もしくは現在の SQLiteClosable.acquireReference() の実装に含まれていたエラーメッセージである。)

"attempt to re-open an already-closed object: " + getObjInfo()
"attempt to acquire a reference on an already-closed " + getObjInfo()
"attempt to acquire a reference on an already-closed SQLiteClosable obj."
"attempt to acquire a reference on a close SQLiteClosable"

自動でクローズさせたくない場合は、一つの解決方法として、データベースの参照カウントがゼロにならないように SQLiteClosable.acquireReference() を呼ぶという手がある。もちろん後で自分でデータベースをクローズする必要がある。

2011年3月7日月曜日

Android エミュレータが圏外状態になる問題(解決?)

Android エミュレータの電波強度が圏外になってしまうという現象が時折発生し、困っていた。エミュレータ内の設定画面をいろいろ操作しても問題が解決しなかった。問題解決方法を求めて長いことネットを検索していたところ、次のページを見つけた。

Get "No Service" on Android Emulator
http://stackoverflow.com/questions/3098923/get-no-service-on-android-emulator

そのページには「Android エミュレータの電波強度が圏外になってしまう」件について質問があり、それに対する回答の一つに次のようなものがあった。

I ran into this exact problem tonight. My research showed that the emulator tries to connect to the first network adapter it can find. Success or failure is all based on that first adapter. For me, I had a VPN with a name that started with A. I renamed it to "VPN" so that it was below the "Local Network" adapter, restarted the emulator and voila! it worked.

Silly? Yes. Stupid? Yes. Works? Yep.

要点は、「Android エミュレータを動かしているマシンに VPN の設定があり、その名前が、アルファベット順に並べたときに『Local Network』よりも先に来るものだった場合、圏外問題が発生することがある」というものだ。回答者は、名前を(アルファベット順で並べたときに「Local Network」よりも後にくる)「VPN」に変更したら問題が解決した、と言っている。

私の環境にも、アルファベット順に並べた時に「Local Network」よりも前に来る VPN の名前があった。ただ、その名前を変更しなくても、エミュレータを使うときにその VPN を使用していなければ圏外問題は発生しないように見える。そのため、エミュレータが圏外状態になったときは、もし VPN が動いていれば止めて、それでも圏外状態から復帰しなければエミュレータを起動し直す、などとしている。これが正しい問題解決方法なのかどうか分からないが、上記の回答を見つけて VPN に対して注意を払うようになって以降、Android エミュレータの圏外問題で悩むことはなくなった。

2011年3月1日火曜日

Android System.exit(int): 驚愕の実装

Android の System.exit(int) を実行すると、次の順序で処理が進む。

System.exit(int) of Android leads to the code flow shown as below.

  (1) java.lang.System.exit(int code)
  (2) java.lang.Runtime.exit(int code)
  (3) java.lang.Runtime.nativeExit(int code, boolean isExit)
  (4) Dalvik_java_lang_Runtime_nativeExit(const u4* args, JValue* pResult)
  (5) exit(int status)

何に驚愕したかというと、System.exit(int) を実行すると、Dalvik VM の終了処理など全くおこなわれず、Dalvik VM インタプリタの文脈でC言語インターフェースの exit(int) 関数が実行され、そこで Dalvik VM プロセスが終了してしまう点だ。Dalvik VM 終了処理関数である dvmShutdown() は呼ばれないので、dvmShutdown() 経由で呼び出されるはずのあらゆる終了処理が実行されない。メモリやスレッドなどのシステムリソースの解放処理が走ることはなく、それらの解放は完全にOS任せである。valgrind 下で実行すると、メモリリークが大量に検出される。OpenJDK の System.exit(int) が慎重に終了処理をおこなっているのとは大違いだ。やはり Dalvik VM は品質が良くない(「Android で OSGi を使うべきでない理由」も参照のこと)。

What made me aghast was that if System.exit(int) is executed, the shutdown steps of Dalvik VM are not performed at all and the C function "exit(int)" is executed in the context of the Dalvik VM interpreter and the Dalvik VM process terminates there. Because the shutdown function of Dalvik VM, dvmShutdown(), is not called, none of shutdown steps that are supposed to be called via dvmShutdown() is executed. No steps to release memory, threads and other system resources are executed, so freeing such resources is totally left to the OS. Many memory leaks are reported by valgrind. Great difference from OpenJDK whose System.exit(int) performs shutdown processing very carefully. After all, quality of Dalvik VM is not good (See "The reason that OSGi should not be used on Android", too).

 下記のコードは、Android における、System.exit(int) から exit(int) までのソースコードの流れである。

The code below is the flow from System.exit(int) to exit(int) in Android.

java.lang.System.exit(int code)

    public static void exit(int code) {
        Runtime.getRuntime().exit(code);
    }

java.lang.Runtime.exit(int code)

    public void exit(int code) {
        // Security checks
        SecurityManager smgr = System.getSecurityManager();
        if (smgr != null) {
            smgr.checkExit(code);
        }

        // Make sure we don't try this several times
        synchronized(this) {
            if (!shuttingDown) {
                shuttingDown = true;

                Thread[] hooks;
                synchronized (shutdownHooks) {
                    // create a copy of the hooks
                    hooks = new Thread[shutdownHooks.size()];
                    shutdownHooks.toArray(hooks);
                }

                // Start all shutdown hooks concurrently
                for (int i = 0; i < hooks.length; i++) {
                    hooks[i].start();
                }

                // Wait for all shutdown hooks to finish
                for (Thread hook : hooks) {
                    try {
                        hook.join();
                    } catch (InterruptedException ex) {
                        // Ignore, since we are at VM shutdown.
                    }
                }

                // Ensure finalization on exit, if requested
                if (finalizeOnExit) {
                    runFinalization(true);
                }

                // Get out of here finally...
                nativeExit(code, true);
            }
        }
    }

java.lang.Runtime.nativeExit(int code, boolean isExit)

    static void Dalvik_java_lang_Runtime_nativeExit(const u4* args, JValue* pResult)
    {
        int status = args[0];
        bool isExit = (args[1] != 0);

        if (isExit && gDvm.exitHook != NULL) {
            dvmChangeStatus(NULL, THREAD_NATIVE);
            (*gDvm.exitHook)(status);     // not expected to return
            dvmChangeStatus(NULL, THREAD_RUNNING);
            LOGW("JNI exit hook returned\n");
        }
        LOGD("Calling exit(%d)\n", status);
    #if defined(WITH_JIT) && defined(WITH_JIT_TUNING)
        dvmCompilerDumpStats();
    #endif
        exit(status);
    }


Dalvik VM に手を加えて、dvmShutdown() の文脈で何か処理をおこなおうとしても、アプリが System.exit(int) を呼ぶと dvmShutdown() 自体がスキップされるので要注意。 なお、下記のコードは dvmShutdown() の実装である。System.exit(int) で、この内容が全部スキップされるということである。

When you modify Dalvik VM to do something in the context of dvmShutdown(), you have to keep in mind that the entire dvmShutdown() function is skipped if System.exit(int) is called by applications. The code below is the implementation of dvmShutdown(). System.exit(int) skips all of the content.

void dvmShutdown(void)
{
    LOGV("VM shutting down\n");

    if (CALC_CACHE_STATS)
        dvmDumpAtomicCacheStats(gDvm.instanceofCache);

    /*
     * Stop our internal threads.
     */
    dvmGcThreadShutdown();

    if (gDvm.jdwpState != NULL)
        dvmJdwpShutdown(gDvm.jdwpState);
    free(gDvm.jdwpHost);
    gDvm.jdwpHost = NULL;
    free(gDvm.jniTrace);
    gDvm.jniTrace = NULL;
    free(gDvm.stackTraceFile);
    gDvm.stackTraceFile = NULL;

    /* tell signal catcher to shut down if it was started */
    dvmSignalCatcherShutdown();

    /* shut down stdout/stderr conversion */
    dvmStdioConverterShutdown();

#ifdef WITH_JIT
    if (gDvm.executionMode == kExecutionModeJit) {
        /* shut down the compiler thread */
        dvmCompilerShutdown();
    }
#endif

    /*
     * Kill any daemon threads that still exist.  Actively-running threads
     * are likely to crash the process if they continue to execute while
     * the VM shuts down.
     */
    dvmSlayDaemons();

    if (gDvm.verboseShutdown)
        LOGD("VM cleaning up\n");

    dvmDebuggerShutdown();
    dvmReflectShutdown();
    dvmProfilingShutdown();
    dvmJniShutdown();
    dvmStringInternShutdown();
    dvmExceptionShutdown();
    dvmThreadShutdown();
    dvmClassShutdown();
    dvmVerificationShutdown();
    dvmRegisterMapShutdown();
    dvmInstanceofShutdown();
    dvmInlineNativeShutdown();
    dvmGcShutdown();
    dvmAllocTrackerShutdown();
    dvmPropertiesShutdown();

    /* these must happen AFTER dvmClassShutdown has walked through class data */
    dvmNativeShutdown();
    dvmInternalNativeShutdown();

    free(gDvm.bootClassPathStr);
    free(gDvm.classPathStr);

    freeAssertionCtrl();

    /*
     * We want valgrind to report anything we forget to free as "definitely
     * lost".  If there's a pointer in the global chunk, it would be reported
     * as "still reachable".  Erasing the memory fixes this.
     *
     * This must be erased to zero if we want to restart the VM within this
     * process.
     */
    memset(&gDvm, 0xcd, sizeof(gDvm));
}