Android 5.0 (Lollipop) のJavaアプリケーションは32bitと64bitのどちらで動作するか?

Android 5.0 (Lollipop) では64bitプロセッサへの対応が行われており、
Linux Kernelはもちろん多くのプロセスが64bitプロセスとして動作します。
また、DalvikVMに変わって採用されたARTランタイムにおいても64bitプロセッサへの対応は行われています。
さて、それでは64bitプロセッサで動作するAndroid上では全てのJavaアプリケーションが64bitとして動作するのでしょうか?
その答えは「場合によっては32bitプロセスとして動作することもある」となります。

AndroidにおけるJavaアプリの起動

さて、ここからは実際にどういったロジックで32bit/64bitプロセスとして分かれていくのかを、順をおって見ていきたいと思います。

起動までの全体像

まずはおさらい的な内容になりますが、Javaアプリがどのように起動されるのか、
その大まかな流れを見てみたいと思います。

f:id:crimsonwoods:20141106234544p:plain

ざっくりとは上図のような流れになっており、箇条書きにすると下記のようになります。

  1. Intent発行
  2. ActivityManagerからZygoteへUnix domain socketを通じて通信
  3. Zygoteからfork(app_process)
  4. ZygoteからforkされたプロセスがJVMを起動してJavaの動作を開始

さてこの流れを頭にいれた上で、Lollipopでは何が違うのかを見て行きたいと思います。

Zygoteとapp_process

Androidの仕組みとして、全てのJavaアプリケーションプロセスがZygoteからforkされるというのは既にご理解頂けていると思います。
では、このZygoteの正体とは何でしょうか?それはapp_processというプログラムになります。
このapp_processはinit(一番最初に起動されるユーザーランドのプロセスで、初期化のためのいろいろな処理を行います)から起動され、
プロセスが不具合によってクラッシュしない限りは常にバックグランドで動作しています。

このZygoteの起動がAndroid 5.0になって、ちょっと変わっています。
どのように変わったのか、Android 4.4.4と比較してみましょう。
まず、Android 4.4.4のinit.rcです。

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
rootdir/init.rc - platform/system/core - Git at Google

Android 4.4.4ではinit.rcの中に直接Zygoteを起動するための処理が記載されていました。

続いてAndroid 5.0のinit.rcを見てみましょう。

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.${ro.zygote}.rc
import /init.trace.rc
rootdir/init.rc - platform/system/core - Git at Google

こちらでは外部のZygote用のinit.xxx.rcを呼び出すように変更されています。
"ro.zygote"というシステムプロパティに設定されているのは64bitな環境であれば"zygote64_32"といったような値になりますので、
init.zygote64_32.rcというスクリプトがimportされることになります。

それではinit.zygote64_32.rcの中身を見てみましょう。

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
    class main
    socket zygote_secondary stream 660 root system
    onrestart restart zygote
rootdir/init.zygote64_32.rc - platform/system/core - Git at Google

Android 4.4.4ではapp_processのみをサービスとして起動していましたが、Android 5.0ではapp_process32とapp_process64という別々のプログラムをサービスとして起動しています。
このことから、Android 5.0 (Lollipop) では32bitプロセスとして起動する場合は32bitのZygoteを、64bitであれば64bitのZygoteを使うのだろうと予想されます。
(元々ZygoteはLibraryのLoadを省いて起動を高速化したりリソースを効率よく使用するための仕組みなので、上記の予想はそれほど的外れではないと思います)

また、app_processに渡される"--socket-name"オプションに異なる名前が渡されていますが、これは単純に32/64bitそれぞれで異なるUNIX domain socketを用意しておき、
32bitアプリケーションなら32bit版のZygoteを、64bitアプリケーションなら64bit版のZygoteを使うようになるということだと予想されます。

ZygoteとABI

さて、それでは次はZygoteに実際にJVMの起動をお願いする部分を見て行きたいと思います。
先に結論を書いてしまうと、
Zygoteの用意しているsocketに対してアクセスを行うのは、android.os.ProcessクラスにあるProcess#startメソッドになります。

    public static final ProcessStartResult start(final String processClass,
                                  final String niceName,
                                  int uid, int gid, int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] zygoteArgs) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    debugFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
        }
    }
core/java/android/os/Process.java - platform/frameworks/base - Git at Google

Process#startViaZygoteだけ見てもちょっと判りませんが、Process#startViaZygoteの内部でProcess#openZygoteSocketIfNeededを呼び出した後、Process#zygoteSendArgsAndGetResultをによって、Zygoteのsocketへアクセスを行います。
このZygote socketへのアクセスの結果として、ZygoteではプロセスのforとkJVMの起動が行われます。
さて、それではこの時32/64bitのどちらのZygoteにアクセスするかを決めているのは何でしょうか?

    private static ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            try {
                primaryZygoteState = ZygoteState.connect(ZYGOTE_SOCKET);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
            }
        }
        if (primaryZygoteState.matches(abi)) {
            return primaryZygoteState;
        }
        // The primary zygote didn't match. Try the secondary.
        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
            try {
            secondaryZygoteState = ZygoteState.connect(SECONDARY_ZYGOTE_SOCKET);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
            }
        }
        if (secondaryZygoteState.matches(abi)) {
            return secondaryZygoteState;
        }
        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
    }

Process#openZygoteSocketIfNeededの中身を見てみると、Zygoteへの接続を予め確立しておき、32/64bit版それぞれに対してZygoteState.matchesメソッドを呼び出すことでどのsocketを使用するかを決定しています。
では、ZygoteState.matchesを見てみましょう。
ZygoteStateクラスはProcessクラスの内部に定義されています。

        boolean matches(String abi) {
            return abiList.contains(abi);
        }
core/java/android/os/Process.java - platform/frameworks/base - Git at Google

実に完結で、内部に保持しているABIのリストに該当するものがあるかどうかを見ているわけですね。
ではこの内部のabiListなるものはどうやって生成されるのか?ですが、これはZygoteState#connectの呼び出し時に解決されます。

        public static ZygoteState connect(String socketAddress) throws IOException {
            DataInputStream zygoteInputStream = null;
            BufferedWriter zygoteWriter = null;
            final LocalSocket zygoteSocket = new LocalSocket();
            try {
                zygoteSocket.connect(new LocalSocketAddress(socketAddress,
                        LocalSocketAddress.Namespace.RESERVED));
                zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
                zygoteWriter = new BufferedWriter(new OutputStreamWriter(
                        zygoteSocket.getOutputStream()), 256);
            } catch (IOException ex) {
                try {
                    zygoteSocket.close();
                } catch (IOException ignore) {
                }
                throw ex;
            }
            String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
            Log.i("Zygote", "Process: zygote socket opened, supported ABIS: " + abiListString);
            return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
                    Arrays.asList(abiListString.split(",")));
        }
    private static String getAbiList(BufferedWriter writer, DataInputStream inputStream)
            throws IOException {
        // Each query starts with the argument count (1 in this case)
        writer.write("1");
        // ... followed by a new-line.
        writer.newLine();
        // ... followed by our only argument.
        writer.write("--query-abi-list");
        writer.newLine();
        writer.flush();
        // The response is a length prefixed stream of ASCII bytes.
        int numBytes = inputStream.readInt();
        byte[] bytes = new byte[numBytes];
        inputStream.readFully(bytes);
        return new String(bytes, StandardCharsets.US_ASCII);
    }
core/java/android/os/Process.java - platform/frameworks/base - Git at Google

ZygoteState#connectではProcess#getAbiListを呼び出して、動的にZygoteから対応しているABI(Application Binary Interface)のリストを取得しています。
ABIについての詳細はNDKに同梱されているCPU-ARCH-ABISのドキュメントを読んで頂ければよいかと思います。
さて、ここまでをまとめてみましょう。

  1. Zygoteは常時32bit版と64bit版が同時に動いている
  2. 32bit Javaアプリケーションは32bit版のZygoteを使用する(推測)
  3. 64bit Javaアプリケーションは64bit版のZygoteを使用する(推測)
  4. ZygoteへのJVMの起動依頼はProcess#startメソッドで行われる
  5. Process#startメソッドは渡されたABIから、32bit/64bit版のそれぞれのZygoteのうちABIに対応している方の接続を利用する。

あとは、「Process#startメソッドに渡されるABIがどのようにして決定されるのか?」という点と、「Zygoteが対応しているABIはどうやって決定されるのか?」が判れば、Javaアプリケーションが32/64bitのどちらで動作するのかが判るということになります。

ZygoteとABI

「Zygoteが対応しているABIはどうやって決定されるのか?」については、Zygoteの元になっているapp_processのソースを読むことで知ることができます。
app_processが呼び出されると、SystemPropertyからABIのリストを取得し、それをそのままZygoteInitに渡すようになっています。
Zygoteの内部ではZygoteInitに渡されたパラメータを保持しておき、Zygote socketを経由して問い合わせがあると、保持していたパラメータを返すという構造になっています。

    Vector<String8> args;
    if (!className.isEmpty()) {
        // We're not in zygote mode, the only argument we need to pass
        // to RuntimeInit is the application argument.
        //
        // The Remainder of args get passed to startup class main(). Make
        // copies of them before we overwrite them with the process name.
        args.add(application ? String8("application") : String8("tool"));
        runtime.setClassNameAndArgs(className, argc - i, argv + i);
    } else {
        // We're in zygote mode.
        maybeCreateDalvikCache();
        if (startSystemServer) {
            args.add(String8("start-system-server"));
        }
        char prop[PROP_VALUE_MAX];
        if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) {
            LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.",
                ABI_LIST_PROPERTY);
            return 11;
        }
        String8 abiFlag("--abi-list=");
        abiFlag.append(prop);
        args.add(abiFlag);
        // In zygote mode, pass all remaining arguments to the zygote
        // main() method.
        for (; i < argc; ++i) {
            args.add(String8(argv[i]));
        }
    }
    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string());
        set_process_name(niceName.string());
    }
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
cmds/app_process/app_main.cpp - platform/frameworks/base - Git at Google

ちょっと長いですが、肝心なのはproperty_getを呼び出している箇所以降になります。
property_getでSystemPropertyから取得したABIのリストが使用されるわけですが、ではこのSystemPropertyのキーはどうなっているのか?というと、下記のように定義されています。

#if defined(__LP64__)
static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist64";
static const char ZYGOTE_NICE_NAME[] = "zygote64";
#else
static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist32";
static const char ZYGOTE_NICE_NAME[] = "zygote";
#endif
cmds/app_process/app_main.cpp - platform/frameworks/base - Git at Google

わかりやすいですね。32/64bit版それぞれにABIのリストをもっているということになります。

各アプリケーションのABI

それでは最後の、「Process#startメソッドに渡されるABIがどのようにして決定されるのか?」についてを見て行きたいと思います。
Process#startメソッドが呼び出されているのはcom.android.server.am.ActivityManagerServiceのActivityManagerService.startProcessLockedメソッドになります。

            String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
            if (requiredAbi == null) {
                requiredAbi = Build.SUPPORTED_ABIS[0];
            }
            String instructionSet = null;
            if (app.info.primaryCpuAbi != null) {
                instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi);
            }
            // Start the process.  It will either succeed and return a result containing
            // the PID of the new process, or else throw a RuntimeException.
            boolean isActivityProcess = (entryPoint == null);
            if (entryPoint == null) entryPoint = "android.app.ActivityThread";
            checkTime(startTime, "startProcess: asking zygote to start proc");
            Process.ProcessStartResult startResult = Process.start(entryPoint,
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                    app.info.dataDir, entryPointArgs);
services/core/java/com/android/server/am/ActivityManagerService.java - platform/frameworks/base - Git at Google

abiOverrideが指定されている場合はそのABIを、そうでなければApplicationInfoクラスのprimaryCpuAbiをABIとして渡しています。
現状ではabiOverrideはInstrumentationの場合にだけ使用されるオプションになるため、通常のアプリケーションの起動時にはabiOverrideはnullになります。
このことから、Process#startに渡されるのはアプリケーションごとのABIに関する情報で、その情報はProcessRecordクラスに保持されているということが判ります。
では、ProcessRecordはどのように生成され、primaryCpuAbiがどのように設定されるのでしょうか?
ProcessRecordを生成するのはActivityManagerService.newProcessRecordLockedメソッドになります。

    final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
            boolean isolated, int isolatedUid) {
        String proc = customProcess != null ? customProcess : info.processName;
        BatteryStatsImpl.Uid.Proc ps = null;
        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
        int uid = info.uid;
        if (isolated) {
            if (isolatedUid == 0) {
                int userId = UserHandle.getUserId(uid);
                int stepsLeft = Process.LAST_ISOLATED_UID - Process.FIRST_ISOLATED_UID + 1;
                while (true) {
                    if (mNextIsolatedProcessUid < Process.FIRST_ISOLATED_UID
                            || mNextIsolatedProcessUid > Process.LAST_ISOLATED_UID) {
                        mNextIsolatedProcessUid = Process.FIRST_ISOLATED_UID;
                    }
                    uid = UserHandle.getUid(userId, mNextIsolatedProcessUid);
                    mNextIsolatedProcessUid++;
                    if (mIsolatedProcesses.indexOfKey(uid) < 0) {
                        // No process for this uid, use it.
                        break;
                    }
                    stepsLeft--;
                    if (stepsLeft <= 0) {
                        return null;
                    }
                }
            } else {
                // Special case for startIsolatedProcess (internal only), where
                // the uid of the isolated process is specified by the caller.
                uid = isolatedUid;
            }
        }
        return new ProcessRecord(stats, info, proc, uid);
    }
https://android.googlesource.com/platform/frameworks/base/+/android-5.0.0_r2/services/core/java/com/android/server/am/ActivityManagerService.java

ここではProcessRecordを作っているだけで、パラメータとして受け取っとApplicationInfoをそのまま使いまわしているように見えます。
ではこのApplicationInfoはどこからやってくるのでしょうか?
正解はcom.android.server.pm.PackageManagerServiceクラスです。
ApplicationInfoはPackageManagerServiceで生成され、ActivityManagerServiceからその情報を参照する形になっています。
ActivityManagerService内部でPackageManagerService.queryIntentAcitivitiesなどを呼び出すことで、ApplicationInfoを取得することができるようになっています。

さて、ではApplicationInfoのprimaryCpuAbiが設定されるのは、PackageManagerServiceのどこでしょうか?
答えはPackageManagerService.scanPackageDirtyLIメソッドの中になります。

                pkg.applicationInfo.primaryCpuAbi = null;
                pkg.applicationInfo.secondaryCpuAbi = null;
                if (isMultiArch(pkg.applicationInfo)) {
                    // Warn if we've set an abiOverride for multi-lib packages..
                    // By definition, we need to copy both 32 and 64 bit libraries for
                    // such packages.
                    if (pkg.cpuAbiOverride != null
                            && !NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(pkg.cpuAbiOverride)) {
                        Slog.w(TAG, "Ignoring abiOverride for multi arch application.");
                    }
                    int abi32 = PackageManager.NO_NATIVE_LIBRARIES;
                    int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
                    if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                        if (isAsec) {
                            abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);
                        } else {
                            abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                                    nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
                                    useIsaSpecificSubdirs);
                        }
                    }
                    maybeThrowExceptionForMultiArchCopy(
                            "Error unpackaging 32 bit native libs for multiarch app.", abi32);
                    if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                        if (isAsec) {
                            abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
                        } else {
                            abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                                    nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
                                    useIsaSpecificSubdirs);
                        }
                    }
                    maybeThrowExceptionForMultiArchCopy(
                            "Error unpackaging 64 bit native libs for multiarch app.", abi64);
                    if (abi64 >= 0) {
                        pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
                    }
                    if (abi32 >= 0) {
                        final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
                        if (abi64 >= 0) {
                            pkg.applicationInfo.secondaryCpuAbi = abi;
                        } else {
                            pkg.applicationInfo.primaryCpuAbi = abi;
                        }
                    }
                } else {
                    String[] abiList = (cpuAbiOverride != null) ?
                            new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
                    // Enable gross and lame hacks for apps that are built with old
                    // SDK tools. We must scan their APKs for renderscript bitcode and
                    // not launch them if it's present. Don't bother checking on devices
                    // that don't have 64 bit support.
                    boolean needsRenderScriptOverride = false;
                    if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null &&
                            NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
                        abiList = Build.SUPPORTED_32_BIT_ABIS;
                        needsRenderScriptOverride = true;
                    }
                    final int copyRet;
                    if (isAsec) {
                        copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
                    } else {
                        copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                                nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
                    }
                    if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                                "Error unpackaging native libs for app, errorCode=" + copyRet);
                    }
                    if (copyRet >= 0) {
                        pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
                    } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
                        pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
                    } else if (needsRenderScriptOverride) {
                        pkg.applicationInfo.primaryCpuAbi = abiList[0];
                    }
                }
services/core/java/com/android/server/pm/PackageManagerService.java - platform/frameworks/base - Git at Google

少々長いですが、このコードの中でやっているのは、APKがマルチアーキテクチャ構成かどうかをチェックして、マルチアーキテクチャ構成であれば32/64bitのネイティブライブラリを含んでいるかをそれぞれチェックし、primaryCpuAbiとsecondaryCpuAbiを設定するということです。
マルチアーキテクチャ構成の場合、64bitがサポートされている環境下で64bit版のネイティブライブラリを持っている場合はprimaryCpuAbiに64bit用のABIを設定、32bit版のネイティブライブラリを持っている場合はsecondaryCpuAbiに32bit用のABIを設定します。64bit版のネイティブライブラリを持っていない場合で、32bitがサポートされている環境下ではprimaryCpuAbiに32bit用のABIが設定されます。
マルチアーキテクチャ構成でない場合は、RenderScriptを使用するかどうかと、ネイティブライブラリの有無によって自動的に適切なABIが選択されます。

まとめ

Android 5.0 Lollipopにおいて、Javaアプリケーションが32/64bitのどちらで動作するかは、プラットフォームがサポートしているABIと、APKがマルチアーキテクチャ構成をとっているかどうかによります。

プラットフォームが64bitをサポートしている場合

  • マルチアーキテクチャ構成で64bitのネイティブライブラリを含むと64bit
  • マルチアーキテクチャ構成で32bitのネイティブライブラリしか含まない場合は32bit
  • マルチアーキテクチャ構成でない場合は含まれているネイティブライブラリに合わせた方

プラットフォームが64bitをサポートしていない場合

  • どの構成でも32bit

ということになります。

Android 5.0 Lollipopで導入されたセキュリティ関連の変更

Material Designとかそういうのはきっと他の誰かがさくっと書いていると思うので、
あまり一般のエンジニアからは注目されないセキュリティのお話なんかを書いてみようかなと思います。

ここ最近のAndroidにおけるSecurity関連の変更については、下記のリンク先にまとまっています。

Security Enhancements in Android 5.0 | Android Developers
Security Enhancements in Android 4.4 | Android Developers
Security Enhancements in Android 4.3 | Android Developers
Security Enhancements in Android 4.2 | Android Developers

今回はAndroid 5.0 (Lollipop)での変更点に絞ってざっくりと日本語訳(意訳)したいと思います。

Encrypted by default (暗号化のデフォルトON)

Android 5.0として工場出荷されたデバイスは、出荷時点から完全な内蔵ストレージの暗号化が有効化されます。
Android 5.0に更新したデバイスについては従来通り設定画面から暗号化を有効化できます。

Improved full disk encryption (内蔵ストレージの完全な暗号化に関する改善)

scryptを使ったブルートフォースアタックからパスワードを保護します。
また、デバイスによって機能がサポートされている場合は、ハードウェアキーストアを使用して暗号化に使用される鍵を格納することで、デバイス外からの攻撃を防ぎます。

Android sandbox reinforced with SELinux (SELinuxによるAndroidサンドボックス環境の強化)

従来よりも多くの領域においてSELinuxによるエンフォーシングモード(ポリシーベースの強制アクセス制御)が要求されるようになりました。

Smart Lock (スマートロック)

デバイスをアンロックするためのより柔軟な安全な仕組みを提供します。
例えば、NFCBluetoothを通じて信頼された他のデバイスや顔認証による自動的なアンロックを許可することができます。

Multi user, restricted profile, and guest modes for phones & tablets (複数ユーザー、制限されたプロフィル、ゲストモード)

タブレットではないデバイスに対しても複数ユーザー対応が提供されます。また、複数ユーザーにはゲストモードも含まれ、これはあなたのデータやアプリに対するアクセスを許可すること無く、あなたへのデバイスに対する一時的で簡単なアクセスを提供するものです。

Updates to WebView without OTA (OTA以外でのWebViewの更新)

OTA (On-The-Air) アップデートによる更新によらないWebViewの更新が可能になります。

Updated cryptography for HTTPS and TLS/SSL (HTTPSTLS/SSLにおける暗号化機能の更新)

TLSv1.2とTLSv1.1が有効化されます。Forward Secrecyは推奨されています。AES-GCMが有効化されます。また、強度の低い暗号化に関するアルゴリズム (MD5, 3DESなど) は無効化されます。
API Levelでサポートされるアルゴリズムの詳細は「SSLSocket | Android Developers」を参照してください。

non-PIE linker support removed (位置独立でない実行バイナリに対するリンカーのサポートを停止)

全ての動的にリンクされる実行バイナリについて、PIE (Position Independent Executable)をサポートする必要があります。

# JNIのライブラリなどを作るときは-fpieとか-fPIEオプションが必要になります

FORTIFY_SOURCE improvements (FORTIFY_SOURCEオプションの改善)

libcの提供するいくつかの関数 (stpcpy, stpncpy, read, recvfrom, FD_CLR, FD_SET, FD_ISSET) についてFORTIFY_SOURCEによる保護が実装されました。
これによりメモリ破壊による脆弱性から保護されます。

Security Fixes (その他のセキュリティ上の問題の修正)

複数Android特有の脆弱性が修正されています。これらの脆弱性に関する情報はOpen Handset Allianceのメンバーに提供され、AOSP (Android Open Source Project) に修正が適用されています。



ということで、ざっと書いてみました。
個々の機能についてはまた時間のあるときにでも。

Android NDKについて、年末だしいろいろ棚卸しするよ

需要があるか判らないけど、自分のためにも書いておきます。

NDKとは?

Androidでの開発において、プロセッサネイティブなコードを使用して処理の高速化などを行う場合、
Androidが公式に提供しているNDK(Native Development Kit)を使用します。
2012年12月31日現在での最新バージョンはr8dというものになります。

ダウンロードはこちらから行えます

NDKで出来る事

  • CやC++でコードを書くことができます。
  • コンパイラとしてgcc-4.4.3, gcc-4.6, gcc-4.7, clang-3.1を利用できます。
  • プロセッサのアーキテクチャに依存した命令を書くことができます(NEONとか)。
  • JNIを通して、DalvikVMからコードを相互に呼び出すことができます。
  • armeabi, armeabi-v7a, x86, mips用それぞれのコードを生成することができます。
  • NativeActivityを使用してGUIアプリケーションを記述できます。
  • Linuxカーネルのシステムコールを利用したコードを記述できます(forkしたりできます)。
  • Androidの標準のものやサードパーティの既存のライブラリをStatic LibraryまたはShared Libraryとして利用できます。
  • DalvikVMのヒープサイズ制限の外側でメモリを使用できます。

NDKについて知っておくと、そのうち使える日が来るかもしれない知識

NDKのコマンドライン編

  • NDKによるビルドは、NDKパッケージ中に存在する「ndk-build」を使って行います。「ndk-build」にはGNU makeに渡すのと同じコマンドライン引数を指定できるので、並列ビルド用の「-j2」などを指定することができます。
  • buildが上手く行かない場合には「$ ndk-build -n」などとして、実際に使用されるコマンドライン文字列を出力させながら問題点を見つけることができます。
  • NDKのパッケージ中にはビルド済みの「gdbserver」が同梱されています。gdbserverを利用することで、ネイティブな部分に対してデバッガを利用した解析を行うことができます。gdbserverの操作方法についての解説は省きます。
  • まさか手作業で書いてる人はいないと思いますが、JNI用のヘッダファイルを自動生成するのには「javah」コマンドを利用できます。javahはJDKに入っているコマンドラインツールです。

ファイル構成編

  • NDKを利用する場合でも、Androidのプラットフォームのバージョンに依存する部分が存在します。NDKでの開発の際は、「default.properties」または「project.properties」に"target=android-XX"(XXにはAndroidAPIバージョンの数字が入ります)と記載することで、バージョンを設定します。
  • アプリケーション全体に渡る設定は「Application.mk」に記述します。「APP_ABI」や「APP_OPTIM」などが該当します。詳細については、NDKパッケージ中の「docs/APPLICATION-MK.html」に記載されています。
  • モジュールをビルドするためのMakeファイルの代わりになるのが「Android.mk」です。Androidのビルドシステムが用意しているマクロを利用してビルドを行う形になります。通常のMakefileと同様に「include」したり、シェルの機能を呼び出すこともできます。Static Library, Shared Library, Executable Binaryの3種類それぞれごとに、モジュールという形でビルド方法を記述していきます。詳細については、NDKパッケージ中の「docs/ANDROID-MK.html」に記載されています。

C編

  • C言語消えてくれるともっと幸せになれるのですが・・・。ちなみに「-std=c99」をつければC99が使えます。

C++

  • C++のランタイムライブラリには「system」という名前の標準のもの(例外やRTTIなどの機能が制限されています)と、「GAbi++」というもの、その他にSTL系のライブラリが存在します。
  • STLライブラリにはSTLport系の「stlport_static」、「stlport_shared」、GNU STLの「gnustl_static」, 「gnustl_shared」があります。詳細はNDKのインストールディレクトリ中の「docs/CPLUSPLUS-SUPPORT.html」に記載されています。それぞれ例外のサポートなどが異なるので注意が必要です。
  • 上記のライブラリの指定は、「Application.mk」の「APP_STL」の値として記述します。
  • 例外を使用する場合は、APP_STLに例外をサポートしているライブラリを選択した上で、「Application.mk」の「APP_CPPFLAGS」に"-fexceptions"を付け加えます(Android.mkの中で個別に指定しても構いません)。
  • RTTIを使用する場合は、APP_STLにRTTIをサポートしているライブラリを選択した上で、「Application.mk」の「APP_CPPFLAGS」に"-frtti"を付け加えます(Android.mkの中で個別に指定しても構いません)。
  • C++11を使用する場合は、「Application.mk」の「APP_CPPFLAGS」に"-std=c++0x"を付け加えます(Android.mkの中で個別に指定しても構いません)。GNU拡張版のC++11を使用する場合は"-std=gnu++0x"を指定してください。

JNI編

  • 一度に作成できるローカルリファレンスの最大数はせいぜい256個程度までのようです。
  • pthread_createしたスレッド内部でFindClassなどを使用すると、なぜかNoClassDefFoundになるので、pthread_createする前にFindClassなどをする必要があります。

その他

  • Androidが使用しているlibcは「bionic」というもので、Linuxのlibcとは別物になります。従って、LinuxではサポートされているのにAndroidではサポートされていない関数が存在します。
  • gcc4.7を使用する場合は、「Application.mk」の「NDK_TOOLCHAIN_VERSION」に"4.7"を指定します。
  • 大抵の環境では、メモリを食い過ぎるとネイティブプロセスであってもSIGKILLで殺されます。
  • プロセス間通信やプロセスの複製、デバイスドライバに対するioctlの発行など、いろんなことができます。
  • Android標準で提供されてるShared Libraryを、NDK環境でリンクして使いたい場合は、「Android.mk」の「LOCAL_LDFLAGS」などに上手いこと書くか、ダミーのモジュールを定義して、「LOCAL_SHARED_LIBRARIES」などに指定した上で、実機などから吸い出したライブラリを「obj/local/xxx」(xxxにはarmeabiなどが入ります)などに配置します。

Android用にMonoをbuildする

AndroidスタンドアロンのMonoを使いたかったのでMonoをbuildしました。
その手順を忘れないように書いておきます。

対象となるMonoのバージョンは2.10.5です。
2.10.5の理由は手元のUbuntuのapt-getでinstallされたのが2.10.5だったからです。

以下の手順でbuildを行います。

  1. Androidのソースツリーを取得
  2. externalの下に「androidmono」という名前でディレクトリを作成
  3. external/androidmonoの中でgit cloneなりtarを展開するなりしてMonoのソースを展開
  4. 展開したMonoのディレクトリを「mono」という名前に変更
  5. Android.mkとApplication.mkを作成
  6. パッチを適用する
  7. ビルドに必要なヘッダファイル群を展開する

という感じです。

Android.mkの中身はこんな感じですね。

LOCAL_PATH := $(call my-dir)
MONO_PATH := $(LOCAL_PATH)/mono

MONO_ASSEMBLY_DIR := \"/data/local/mono/lib/\"
MONO_CONFIG_DIR   := \"/data/local/mono/bin/\"
MONO_BINARY_DIR   := \"/data/local/mono/bin/\"

MONO_CFLAGS := -DMONO_ASSEMBLIES=$(MONO_ASSEMBLY_DIR) \
               -DMONO_CFG_DIR=$(MONO_CONFIG_DIR) \
               -DMONO_BINDIR=$(MONO_BINARY_DIR) \
               -DHAVE_CONFIG_H -DARM_FPU_VFP -D_REENTRANT \
               -DPAGE_SIZE=4096 -DS_IWRITE=S_IWUSR \
               -DPLATFORM_ANDROID -D__linux__ -DREDIRECT_MALLOC=GC_malloc \
               -DHAVE_BOEHM_GC -DHAVE_GC_H -DUSE_INCLUDED_LIBGC -DHAVE_GC_GCJ_MALLOC -DHAVE_GC_ENABLE \
               -D_GNU_SOURCE -DUSE_MMAP -DUSE_MUNMAP -D_FILE_OFFSET_BITS=64 -DNO_UNALIGNED_ACCESS \
               -D__environ=environ

MONO_LIBGC_CFLAGS := -DPACKAGE_NAME=\"libgc-mono\" -DPACKAGE_VERSION=\"6.6\" -DPACKAGE_STRING=\"libgc-mono\ 6.6\" \
                     -DGC_LINUX_THREADS=1 -D_REENTRANT=1 \
                     -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 \
                     -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1 \
                     -DLT_OBJDIR=\".libs/\" -DSILENT=1 -DNO_SIGNALS=1 -DNO_EXECUTE_PERMISSION=1 \
                     -DGC_GCJ_SUPPORT=1 -DJAVA_FINALIZATION=1 -DATOMIC_UNCOLLECTABLE=1 -D_IN_LIBGC=1 \

MONO_INCLUDES := $(MONO_PATH)/ \
                 $(MONO_PATH)/mono/ \
                 $(MONO_PATH)/eglib/src/ \
                 $(MONO_PATH)/mono/mini/ \
                 $(MONO_PATH)/libgc/include \
                 $(LOCAL_PATH)/configs \
                 $(LOCAL_PATH)/configs/eglib \
                 $(LOCAL_PATH)/configs/mono/mini \
                 $(LOCAL_PATH)/configs/mono/arch/arm \
                 bionic/linker

MONO_LDLIBS := -ldl

# build 'libeglib'
include $(CLEAR_VARS)

LOCAL_MODULE := eglib
LOCAL_MODULE_TAGS := eng
LOCAL_PATH := $(MONO_PATH)/eglib/src
LOCAL_SRC_FILES := garray.c gerror.c ghashtable.c gmem.c goutput.c \
    gstr.c gslist.c gstring.c gptrarray.c glist.c gqueue.c gpath.c \
    gshell.c gspawn.c gfile.c gfile-posix.c gpattern.c gmarkup.c \
    gutf8.c gunicode.c gdate-unix.c gdir-unix.c gfile-unix.c \
    gmisc-unix.c gmodule-unix.c gtimer-unix.c
LOCAL_CFLAGS := $(MONO_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)

include $(BUILD_STATIC_LIBRARY)

# build 'libmonoutils'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := monoutils
LOCAL_MODULE_TAGS := eng
LOCAL_PATH := $(MONO_PATH)/mono/utils
LOCAL_SRC_FILES := mono-semaphore.c mono-md5.c \
    mono-sha1.c mono-logger.c mono-codeman.c dlmalloc.h dlmalloc.c \
    mono-counters.c mono-compiler.h mono-dl.c mono-dl.h \
    mono-internal-hash.c mono-internal-hash.h \
    mono-io-portability.c mono-io-portability.h monobitset.c \
    mono-math.c mono-mmap.c mono-mmap.h mono-proclib.c \
    mono-proclib.h mono-time.c mono-time.h strtod.h strtod.c \
    strenc.h strenc.c mono-uri.c mono-poll.c mono-path.c \
    mono-stdlib.c mono-property-hash.h mono-property-hash.c \
    mono-value-hash.h mono-value-hash.c freebsd-elf_common.h \
    freebsd-elf32.h freebsd-elf64.h freebsd-dwarf.h dtrace.h \
    mono-filemap.c mono-networkinterfaces.c mono-error.c \
    mono-publib.c
LOCAL_CFLAGS := $(MONO_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -ldl

include $(BUILD_STATIC_LIBRARY)

# build 'libmonoruntime'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := monoruntime
LOCAL_PATH := $(MONO_PATH)/mono/metadata
LOCAL_SRC_FILES := runtime.c \
    mono-basic-block.c mono-wsq.c mono-hash.c reflection.c object.c object-internals.h \
    icall.c icall-def.h char-conversions.h decimal.c decimal.h \
    boehm-gc.c sgen-gc.c sgen-gc.h gc.c gc-internal.h method-builder.h method-builder.c \
    mono-mlist.c mono-mlist.h tabledefs.h threads-types.h threadpool-internals.h \
    file-io.c file-io.h socket-io.c socket-io.h exception.c exception.h \
    debug-mono-symfile.h debug-mono-symfile.c mono-debug.h mono-debug.c \
    mono-debug-debugger.h mono-debug-debugger.c profiler-private.h attach.h attach.c \
    rand.h rand.c security.c security.h security-core-clr.c security-core-clr.h \
    string-icalls.c string-icalls.h sysmath.h sysmath.c process.c process.h \
    environment.c environment.h locales.c locales.h normalization-tables.h \
    filewatcher.c filewatcher.h culture-info.h culture-info-tables.h \
    security-manager.c security-manager.h console-io.h console-unix.c \
    coree.c coree.h domain.c domain-internals.h opcodes.c cil-coff.h \
    metadata.c metadata-internals.h number-formatter.h verify.c verify-internals.h \
    mono-endian.c mono-endian.h mono-config.c loader.c \
    class.c class-internals.h wrapper-types.h mempool.c \
    mono-perfcounters.c mono-perfcounters.h mono-perfcounters-def.h \
    debug-helpers.c mempool-internals.h metadata-verify.c \
    mono-cq.c
LOCAL_CFLAGS := $(MONO_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)

include $(BUILD_STATIC_LIBRARY)

# build 'libmonoruntimearm'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := monoruntimearm
LOCAL_MODULE_TAGS := eng
LOCAL_PATH := $(MONO_PATH)/mono/metadata
LOCAL_SRC_FILES := \
    monitor.c marshal.c threadpool.c threads.c appdomain.c \
    profiler.c cominterop.c assembly.c image.c null-gc.c
LOCAL_CFLAGS := $(MONO_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)

include $(BUILD_STATIC_LIBRARY)

# build 'libwapi'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := wapi
LOCAL_MODULE_TAGS := eng
LOCAL_PATH := $(MONO_PATH)/mono/io-layer
LOCAL_SRC_FILES := access.h atomic.c atomic.h \
    collection.h context.c context.h critical-sections.c \
    critical-sections.h critical-section-private.h error.c error.h \
    events.c events.h event-private.h handles.h \
    handles-private.h io.h io-portability.c io-portability.h \
    io-private.h io-layer.h macros.h messages.c messages.h misc.c \
    misc-private.h mutexes.c mutexes.h mutex-private.h \
    mono-mutex.c mono-mutex.h mono-spinlock.h processes.c \
    processes.h process-private.h security.c security.h \
    semaphores.c semaphores.h semaphore-private.h shared.c \
    shared.h sockets.c sockets.h socket-private.h \
    socket-wrappers.h status.h system.c system.h \
    threads.h thread-private.h timefuncs.c timefuncs.h \
    timefuncs-private.h types.h uglify.h versioninfo.c \
    versioninfo.h wait.c wait.h wapi_glob.h wapi_glob.c locking.c posix.c
LOCAL_CFLAGS := $(MONO_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)

include $(BUILD_STATIC_LIBRARY)

# build 'libwapiarm'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := wapiarm
LOCAL_MODULE_TAG := eng
LOCAL_PATH := $(MONO_PATH)/mono/io-layer
LOCAL_SRC_FILES := collection.c io.c wthreads.c handles.c
LOCAL_CFLAGS := $(MONO_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)

include $(BUILD_STATIC_LIBRARY)

# build 'libgc'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := gc
LOCAL_MODULE_TAG := eng
LOCAL_PATH := $(MONO_PATH)/libgc
LOCAL_SRC_FILES := allchblk.c blacklst.c \
    checksums.c dyn_load.c \
    headers.c mark.c \
    new_hblk.c pcr_interface.c \
    ptr_chck.c real_malloc.c solaris_pthreads.c \
    solaris_threads.c specific.c stubborn.c backgraph.c \
    win32_threads.c pthread_stop_world.c \
    darwin_stop_world.c mach_dep.c
LOCAL_CFLAGS := $(MONO_CFLAGS) $(MONO_LIBGC_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)

include $(BUILD_STATIC_LIBRARY)

# build 'libgcarm'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := gcarm
LOCAL_MODULE_TAG := eng
LOCAL_PATH := $(MONO_PATH)/libgc
LOCAL_SRC_FILES := alloc.c dbg_mlc.c finalize.c gc_dlopen.c gcj_mlc.c malloc.c \
    mallocx.c  mark_rts.c obj_map.c os_dep.c misc.c reclaim.c pthread_support.c typd_mlc.c
LOCAL_CFLAGS := $(MONO_CFLAGS) $(MONO_LIBGC_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)

include $(BUILD_STATIC_LIBRARY)

# build 'libmini'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := mini
LOCAL_MODULE_TAG := eng
LOCAL_PATH := $(MONO_PATH)/mono/mini
LOCAL_SRC_FILES := mini.c method-to-ir.c \
    xdebug.c decompose.c \
    jit-icalls.c trace.c \
    mini-arch.h dominators.c cfold.c regalloc.c \
    helpers.c liveness.c ssa.c abcremoval.c ssapre.c \
    local-propagation.c driver.c debug-mini.c \
    linear-scan.c aot-runtime.c \
    graph.c mini-exceptions.c mini-codegen.c mini-trampolines.c \
    declsec.c wapihandles.c branch-opts.c \
    mini-generic-sharing.c regalloc2.c simd-intrinsics.c \
    unwind.h unwind.c mini-posix.c \
    mini-arm.c exceptions-arm.c tramp-arm.c image-writer.c \
    dwarfwriter.c mini-gc.c
LOCAL_CFLAGS := $(MONO_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)

include $(BUILD_STATIC_LIBRARY)

# build 'libminiarm'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := miniarm
LOCAL_MODULE_TAG := eng
LOCAL_PATH := $(MONO_PATH)/mono/mini
LOCAL_SRC_FILES := aot-compiler.c debugger-agent.c
LOCAL_CFLAGS := $(MONO_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)

include $(BUILD_STATIC_LIBRARY)

# build 'libmono'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := libmono
LOCAL_MODULE_TAGS := eng
LOCAL_PATH := $(MONO_PATH)/mono/mini
LOCAL_SRC_FILES := mini.c method-to-ir.c \
    xdebug.c decompose.c \
    jit-icalls.c trace.c \
    mini-arch.h dominators.c cfold.c regalloc.c \
    helpers.c liveness.c ssa.c abcremoval.c ssapre.c \
    local-propagation.c driver.c debug-mini.c \
    linear-scan.c aot-runtime.c \
    graph.c mini-exceptions.c mini-codegen.c mini-trampolines.c \
    declsec.c wapihandles.c branch-opts.c \
    mini-generic-sharing.c regalloc2.c simd-intrinsics.c \
    unwind.h unwind.c mini-posix.c \
    mini-arm.c exceptions-arm.c tramp-arm.c image-writer.c \
    dwarfwriter.c mini-gc.c
LOCAL_PRELINK_MODULE := false
LOCAL_CFLAGS := $(MONO_CFLAGS) -fPIC
LOCAL_C_INCLUDES := $(MONO_INCLUDES)
LOCAL_WHOLE_STATIC_LIBRARIES := monoutils gcarm monoruntime
LOCAL_STATIC_LIBRARIES := wapiarm monoruntimearm eglib gc wapi mini miniarm
LOCAL_LDFLAGS := -Wall -L$(SYSROOT)/usr/lib -llog -ldl -lm -lc
#LOCAL_STRIP_MODULE := false

include $(BUILD_SHARED_LIBRARY)

# build 'mono'
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_MODULE := mono
LOCAL_MODULE_TAGS := eng
LOCAL_PATH := $(MONO_PATH)/mono/mini
LOCAL_SRC_FILES := main.c
LOCAL_CFLAGS := $(MONO_CFLAGS)
LOCAL_C_INCLUDES := $(MONO_INCLUDES)
LOCAL_SHARED_LIBRARIES := libmono
LOCAL_LDFLAGS := -llog -ldl -lm -lc

include $(BUILD_EXECUTABLE)

長いですね・・・。あとでgithubかなんかに突っ込んで終わりにしたいですね。

Application.mkは適当に

APP_ABI := armeabi armeabi-v7a

とか書いておけば良いです。

あてるパッチはこんな感じです。

diff --git a/libgc/dyn_load.c b/libgc/dyn_load.c
index e4be3c7..d8ce7ec 100644
--- a/libgc/dyn_load.c
+++ b/libgc/dyn_load.c
@@ -99,7 +99,9 @@
 # if !defined(OPENBSD)
 #   include <elf.h>
 # endif
+# if defined(HAVE_LINK_H)
 #   include <link.h>
+# endif
 #endif

 /* Newer versions of GNU/Linux define this macro.  We
diff --git a/mono/mini/aot-runtime.c b/mono/mini/aot-runtime.c
index a3c5746..867e437 100644
--- a/mono/mini/aot-runtime.c
+++ b/mono/mini/aot-runtime.c
@@ -38,8 +38,12 @@
 #endif

 #ifdef HAVE_DL_ITERATE_PHDR
+#ifdef PLATFORM_ANDROID
+#include <linker.h>
+#else
 #include <link.h>
 #endif
+#endif

 #include <mono/metadata/tabledefs.h>
 #include <mono/metadata/class.h>
@@ -59,6 +63,21 @@
 #include "mono/utils/mono-compiler.h"
 #include <mono/utils/mono-counters.h>

+#if defined(PLATFORM_ANDROID)
+#ifdef PSR_c
+#undef PSR_c
+#endif
+#ifdef PSR_x
+#undef PSR_x
+#endif
+#ifdef PSR_s
+#undef PSR_s
+#endif
+#ifdef PSR_f
+#undef PSR_f
+#endif
+#endif
+
 #include "mini.h"
 #include "version.h"

diff --git a/mono/mini/mini-posix.c b/mono/mini/mini-posix.c
index c15b098..92aa901 100644
--- a/mono/mini/mini-posix.c
+++ b/mono/mini/mini-posix.c
@@ -631,7 +631,7 @@ mono_runtime_syscall_fork ()
    return (pid_t) syscall (SYS_fork);
 #else
    g_assert_not_reached ();
-   return;
+   return -1;
 #endif
 }

これもあとでどっかにファイルおくだけにしたいですね・・・。

さて残りはヘッダなんですが、ソースをそのまま貼り付けるとえらいことになるので、
あとでgithubにでも突っ込んでおきます。
突っ込んだらこの記事も更新するつもりです。

libusbをAndroid用にポーティングする

お仕事で必要があってlibusbをAndroidに持っていくということをやりました。
この記事はlibusbをAndroid用にbuildする方法の備忘録です。

まず、Androidをlibusbにportingするのは先人が既にやっているので、そこを参考にします。
参考URL: http://android.serverbox.ch/?p=151

  1. androidソースコードをもってきます。 -> ここ「Downloading the Source Tree | Android Open Source」を参考に。
  2. androidソースコードをbuildします。
  3. externalの下にlibusbというディレクトリを切ります。
  4. libusbのソースを展開します。現在の最新版は「libusb-1.0.9」です。
  5. "external/libusb"の直下に「Android.mk」を作成します。
  6. "external/libusb/libusb"にも「Android.mk」を作成します。
  7. "external/libusb/libusb/io.c"を修正します。
  8. 「mm」でlibusbをbuildします。

androidのbuildは、もってきたsource codeのトップで

$ source build/envsetup.sh
$ lunch
$ make

とかやってください。

libusbの直下に作成するAndroid.mkはこんな感じです。

LOCAL_PATH := $(call my-dir)
subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, libusb))
include $(subdirs)

元にしているページのAndroid.mkは"subdirs"の頭の"s"が取れてしまっているので注意しましょう。

"external/libusb/libusb"の下に作成するAndroid.mkはこんな感じです。

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_CFLAGS:=-DLIBUSB_DESCRIBE

LOCAL_SRC_FILES:= \
  core.c \
  descriptor.c \
  io.c \
  sync.c \
  os/linux_usbfs.c \
  os/threads_posix.c

LOCAL_C_INCLUDES += \
external/libusb/ \
external/libusb/libusb/ \
external/libusb/libusb/os

LOCAL_MODULE:= libusb
LOCAL_MODULE_TAGS:=eng
LOCAL_PRELINK_MODULE:=false

include $(BUILD_SHARED_LIBRARY)

"libusb-1.0.9"以外のバージョンでは何らかの修正が必要になるかもしれません。
その場合は適宜修正して下さい。
あと、この方法でビルドはできると思いますが、ビルド後のバイナリの動作を保証するものではありません。

AndroidのPreferenceを簡単に使いたい

AndroidでPreference関係の処理書くのすごーく面倒なんですよねー。
もう面倒すぎて鼻血でそうな感じですよね。
なので、うまいこと使える部品使って使い回したいなーっと。

そういうことで最近はこんな感じのコード書いてます。

public class Preferences {
	private static SharedPreferences prefs;
	private static Resources res;
	
	public static void create(Context context) {
		if (null == prefs || null == res) {
			prefs = PreferenceManager.getDefaultSharedPreferences(context);
			res = context.getResources();
		}
	}
	
	public static String getHoge() {
		return get(R.string.pref_key_hoge, getString(R.string.pref_defval_hoge));
	}
	
	private static String getString(int id) {
		return res.getString(id);
	}
	
	@SuppressWarnings("unchecked")
	private static <T> T get(int id, T defVal) {
		final Map<String, ?> map = prefs.getAll();
		final String key = getString(id);
		if (!map.containsKey(key)) {
			return defVal;
		}
		return (T)map.get(key);
	}
}

Preferences#createをActivityのonCreateとかonStartとかonResumeとかで適宜呼び出します。
AndroidのSingletonはアプリケーションのlife cycleにあわせて自動的にunloadされる可能性があるので気をつけてくださいね☆)

内部で設定項目を取得するのは" T get(int id, T defVal)"を呼び出します。
デフォルト値やキーは基本的にリソースに追い出して使う前提なので、
"String getString(int id)"でリソースから文字列を取得できるようにしています。

こんな感じのラッパーを書いておけば設定項目へのアクセスが楽になるかなーと思います。

Androidのセンサーで気をつけるべきこと

「気をつけるべきこと」とか書きましたが、
ありていに言えば「自分がはまったのでくやしい!ぐぬぬ!」ってことですね。

ということでAndroidのセンサーから値を取得する上で、気をつけなきゃいけないこと書いておきます。


  1. SensorManager取得時に指定したセンサーのタイプを信用するな。
  2. SensorのResolutionはよくわからない。


具体的にどういうことかコードを見ていきましょう。

Androidでセンサーの値を取るときはこんな感じのコードを書きます。

@Override
void onCreate(Bundle savedInstanceState) {
    ...
    final SensorManager mgr = (SensorManager)getSystemService(SENSOR_SERVICE);
    final Sensor acc = mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    mgr.registerListener(listener, acc, SensorManager.SENSOR_DELAY_NORMAL);
}

private final SensorEventListener listener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent event) {
        ...
    }
		
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
};

このとき、SensorManagerに対して指定したセンサータイプのセンサのイベントが取れるようになります。
きっと取れると思います。取れるはずなんです。本当なら。
でも実はうまくいかないことがよくあります。
で、よくよく調べてみると、Android端末のいくつかでは指定したセンサータイプと、
getDefaultSensorで実際に取得できるセンサーの物理的な種類が一致していないことがわかりました。
そりゃうまく動かないわけですよ。
だって加速度センサの値が欲しいのに実際には磁気センサを取得してたりするんですから。


これをどうやって回避するかというと、実際に取得したいセンサに関連する他の論理センサも動作させるということを行います。
具体的なコードで説明をすると、

final Sensor acc = mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mgr.registerListener(listener, acc, SensorManager.SENSOR_DELAY_NORMAL);

final Sensor ori = mgr.getDefaultSensor(Sensor.TYPE_ORIENTATION);
mgr.registerListener(listener, ori, SensorManager.SENSOR_DELAY_NORMAL);

final Sensor mag = mgr.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mgr.registerListener(listener, mag, SensorManager.SENSOR_DELAY_NORMAL);

こんなふうにして関連するセンサを全部使用します。
後はイベントを受ける側で、どのイベントが来たかを振り分けてあげればうまくいきます。
(どうして加速度センサと磁気センサと傾きセンサが関連するかの説明は省略します)

private final SensorEventListener listener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent event) {
        switch(event.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            ...
            break;
        }
    }
		
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
};

次にSensorのResolutionですが、名前からして測定値を物理量に変換するのに使用するものだと思うじゃないですか普通は(たぶん普通の感覚だよね?)。
でもこれ、実は実装によってはただの固定値であまり意味のある値が入っていません。
Resolutionを使わなくても、普通に値を使えば物理量に単位変換された値が入っています。
Resolutionの単位も実装依存のため、何のために存在するかいまいちわかりません。
なので間違っても↓みたいなコードは書いちゃだめですよ!ダメ、ゼッタイ!

private final SensorEventListener listener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent event) {
        final float res = event.sensor.getResolution();
        switch(event.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            final float accX = event.value[0] * res;
            // do something
            ...
            break;
        }
    }
		
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
};

↑こういうことしちゃダメですよ!