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アプリがどのように起動されるのか、
その大まかな流れを見てみたいと思います。
ざっくりとは上図のような流れになっており、箇条書きにすると下記のようになります。
- Intent発行
- ActivityManagerからZygoteへUnix domain socketを通じて通信
- Zygoteからfork(app_process)
- 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 netdrootdir/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.rcrootdir/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 zygoterootdir/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のドキュメントを読んで頂ければよいかと思います。
さて、ここまでをまとめてみましょう。
- Zygoteは常時32bit版と64bit版が同時に動いている
- 32bit Javaアプリケーションは32bit版のZygoteを使用する(推測)
- 64bit Javaアプリケーションは64bit版のZygoteを使用する(推測)
- ZygoteへのJVMの起動依頼はProcess#startメソッドで行われる
- 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"; #endifcmds/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で導入されたセキュリティ関連の変更
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 (スマートロック)
デバイスをアンロックするためのより柔軟な安全な仕組みを提供します。
例えば、NFCやBluetoothを通じて信頼された他のデバイスや顔認証による自動的なアンロックを許可することができます。
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 (HTTPSとTLS/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オプションが必要になります
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にはAndroidのAPIバージョンの数字が入ります)と記載することで、バージョンを設定します。
- アプリケーション全体に渡る設定は「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を行います。
- Androidのソースツリーを取得
- externalの下に「androidmono」という名前でディレクトリを作成
- external/androidmonoの中でgit cloneなりtarを展開するなりしてMonoのソースを展開
- 展開したMonoのディレクトリを「mono」という名前に変更
- Android.mkとApplication.mkを作成
- パッチを適用する
- ビルドに必要なヘッダファイル群を展開する
という感じです。
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
- androidのソースコードをもってきます。 -> ここ「Downloading the Source Tree | Android Open Source」を参考に。
- androidのソースコードをbuildします。
- externalの下にlibusbというディレクトリを切ります。
- libusbのソースを展開します。現在の最新版は「libusb-1.0.9」です。
- "external/libusb"の直下に「Android.mk」を作成します。
- "external/libusb/libusb"にも「Android.mk」を作成します。
- "external/libusb/libusb/io.c"を修正します。
- 「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される可能性があるので気をつけてくださいね☆)
内部で設定項目を取得するのは"
デフォルト値やキーは基本的にリソースに追い出して使う前提なので、
"String getString(int id)"でリソースから文字列を取得できるようにしています。
こんな感じのラッパーを書いておけば設定項目へのアクセスが楽になるかなーと思います。
Androidのセンサーで気をつけるべきこと
「気をつけるべきこと」とか書きましたが、
ありていに言えば「自分がはまったのでくやしい!ぐぬぬ!」ってことですね。
ということでAndroidのセンサーから値を取得する上で、気をつけなきゃいけないこと書いておきます。
- SensorManager取得時に指定したセンサーのタイプを信用するな。
- 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) { } };
↑こういうことしちゃダメですよ!