追記の追記:
うまく動くようになったんはええねんけど、1つ問題発見(汗)
生成される共有ライブラリのサイズがおかしい…2.5倍ぐらいある。例えば、元々全部合わせても4MBぐらいだったのが10MBを越えてくる。
多分Application.mkでのリリースビルド設定回りが軒並み無視されて最適化もデバッグ情報のストリップも全部実行されてない気がする。
externalNativeBuildでオプション指定したりゴニョゴニョすれば良くなるんかもしれんけど、残念やけどこればっかりに時間割けんから元に戻すことにする。
Android Studioのstable版が2.3にアップデートとなりました。
それに合わせてGradle pluginも2.3.0にアップデートを推奨されるのですが、うっかりOKしてしまうと、gradle.buildスクリプト経由でndk-buildを呼び出して行っていたNDKでのビルドが通らなくなってしまいました?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import org.apache.tools.ant.taskdefs.condition.Os ... task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') { println('executing ndkBuild') def ndkBuildingDir = project.plugins.findPlugin('com.android.library').sdkHandler.getNdkFolder().absolutePath def ndkBuildPath = ndkBuildingDir if (Os.isFamily(Os.FAMILY_WINDOWS)) { ndkBuildPath = ndkBuildingDir + '/ndk-build.cmd' } else { ndkBuildPath = ndkBuildingDir + '/ndk-build' } commandLine ndkBuildPath, '-j4', '-C', file('src/main').absolutePath } |
元々こんな風にしてgradle.buildスクリプトからndk-buildを呼び出すようにしていましたが、どうもproject.plugins.findPluginが正しいプラグインインスタンスを返してこない感じ。
ちなみに’com.android.library’の部分はライブラリモジュールの場合で、通常のアプリの場合は’com.android.application’です。
Gradle pluginのソースを斜め読みしてみましたがプライグイン自体のソース部分は2.2.3の頃と大した違いはなさそうな感じ…以前あったようにgradleかpluginのバージョンチェックなどの不具合でしょうかねぇ。
バグっぽいのでそのうち修正されるのかもしれませんが、現時点での主な解決策は次の4つぐらいでしょうか。
- NDKへのパスをbuild.gradleへ直打ちする
- NDKでのビルドを完全にAndroid Studioのプロジェクトとは別に行い、成果物だけをjniLibとしてgradle様にapkへ組み込んでもらう
- Android Studio2.2以降でサポートされた新しいNDK対応へ切り替える
- しばらくはGradle Plugin2.2.3を使う
最初の2つは、そもそもそうしたくないがゆえにbuild.gradleからndk-buildを呼び出していたので問題外として3つめしかないですね。4つめは特に何もしないでも今まで通り動くので記事になりませんし。
しょうがない(´・ω・`)
元々のbuild.gradleの全体はこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
apply plugin: 'com.android.library' import org.apache.tools.ant.taskdefs.condition.Os android { compileSdkVersion versionCompiler buildToolsVersion versionBuildTool compileOptions { sourceCompatibility javaSourceCompatibility targetCompatibility javaTargetCompatibility } defaultConfig { minSdkVersion 16 targetSdkVersion versionTarget versionCode versionCodeNum versionName versionNameString } buildTypes { debug { debuggable true } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { jniLibs.srcDir 'src/main/libs' jni.srcDirs = [] } } } tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn ndkBuild } task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') { println('executing ndkBuild') def ndkBuildingDir = project.plugins.findPlugin('com.android.library').sdkHandler.getNdkFolder().absolutePath def ndkBuildPath = ndkBuildingDir if (Os.isFamily(Os.FAMILY_WINDOWS)) { ndkBuildPath = ndkBuildingDir + '/ndk-build.cmd' } else { ndkBuildPath = ndkBuildingDir + '/ndk-build' } commandLine ndkBuildPath, '-j4', '-C', file('src/main').absolutePath } task ndkClean(type: Exec, description: 'clean JNI libraries') { println('executing ndkBuild clean') def ndkBuildingDir = project.plugins.findPlugin('com.android.library').sdkHandler.getNdkFolder().absolutePath def ndkBuildPath = ndkBuildingDir if (Os.isFamily(Os.FAMILY_WINDOWS)) { ndkBuildPath = ndkBuildingDir + '/ndk-build.cmd' } else { ndkBuildPath = ndkBuildingDir + '/ndk-build' } commandLine ndkBuildPath, 'clean', '-C', file('src/main').absolutePath } clean.dependsOn 'ndkClean' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) ... } |
sourceSetsで”jni.srcDirs = []”と指定、JavaCompileにNDKビルド用のタスクへの依存関係を追加、NDKビルド用のタスクを定義するってのがこのbuild.gradleの味噌でした。
これをこげな風に書き換えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
apply plugin: 'com.android.library' android { compileSdkVersion versionCompiler buildToolsVersion versionBuildTool compileOptions { sourceCompatibility javaSourceCompatibility targetCompatibility javaTargetCompatibility } defaultConfig { minSdkVersion 16 targetSdkVersion versionTarget versionCode versionCodeNum versionName versionNameString buildConfigField "String", "STL_NAME", "\"${project.property("STL_NAME")}\"" ndk { // Specifies the ABI configurations of your native // libraries Gradle should build and package with your APK. abiFilters 'x86', 'armeabi', 'armeabi-v7a' } } buildTypes { debug { debuggable true } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } externalNativeBuild { ndkBuild { path "src/main/jni/Android.mk" } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) ... } |
公式ドキュメントに有るようにexternalNativeBuildを追加します。あとはいらなくなったタスク関係の削除すればおしまいです。
問答無用で対応する全てのABIのバイナリを生成しても問題なければdefaultConfig.ndk下のabiFiltersは省略できます。
Android DevelopersのAndroid StudioユーザーガイドのプロジェクトへのC/C++コードの追加には
注: Gradle を既存の ndk-build プロジェクトにリンクする場合は、cmake {} ではなく ndkBuild {} ブロックを使用して、Android.mk ファイルへの相対パスを指定します。Gradle は、Application.mk ファイルが Android.mk ファイルと同じディレクトリに配置されている場合はそのファイルも対象に含めます。
ということが書いてあります。が、Application.mkでのABI指定は綺麗サッパリ無視されてしまいます。それ以外のApplication.mkで指定したフラグ関係がどの程度認識されているのかはこれからテストしてみますが、いずれにしてもビルドするABIを指定したい場合には上のサンプルのようにabiFiltersで指定するようにしましょう。
あと、何らかの理由で成果物から特定の共有ライブラリあるいはABIまるごと除外したい場合にはbuild.gradleでcopyタスクを生成してそこでexcludeするというのが可能でしたが、新しいNDK対応の場合は
1 2 3 |
packagingOptions { exclude 'libc++_shard.so' } |
という風にpackagingOptionsで除外設定するほうが良さそうです。
最後にもう2つ注意事項が。
1つは新しいNDKサポートではプロダクトフレーバーやビルドバリアント毎に別々のバイナリをビルドできるようになっています。これはこれで便利なのですが…が別な言い方をすると、例えばデバッグ版とリリース版それぞれで別々にネイティブライブラリのビルドが走ることになります。ぶっちゃけビルド時間が伸びるちゅうことです?
自分のオンボロMacMiniでフルビルドに10分程度かかるプロジェクトの場合、新しいNDKサポートにすると15分ぐらいになってしまいました(。・_・。) これは結構痛いです。
もう1つはバイナリが生成される場所です。
普通にndk-buildでビルドする場合にはsrc/main下にjniディレクトリを作ってその中にAndroid.mkやらApplication.mkやらソースコード・ヘッダーファイルを入れます。
その状態でsrc/main上でndk-buildを実行するとsrc/main下にobjsやらlibsやらが生成されてそこにビルドされたバイナリが入っていました。
でも新しいNDKサポートではJavaのソースコードやaidlなどと同様に各サブモジュール下のbuild/intermediates/ndkBuild下にプロダクトフレーーバー/ビルドバリアント毎のobjsディレクトリが生成されてその中にバイナリが入るようになりました。
外部ツールやスクリプトでゴニョゴニョしている場合にはそのままだと動かなくなるかも…かも
という事でお疲れ様でした。
追記:
となことを書いたものの最終的には4つ目の選択肢になるかも…
前に書いた自前のnativeライブラリビルド時にaar内にある既存のnativeライブラリとリンクした〜い^^/の部分が今のAndroid StudioのNDKサポートでは対応出来ないかも(汗)
対象のライブラリモジュールのpackagingOptionsのexcludeで該共有ライブラリを除外設定すれば動きそうなものなんですが、まるっと無視されとる(´・ω・`)
…でもpackagingOptionsのexcludeで共有ライブラリ名だけを指定する代わりにワイルドカードなディレクトリ込みで指定すればいけそうな気も…つまり
1 2 3 |
packagingOptions { exclude 'libc++_shard.so' } |
の代わりに
1 2 3 |
packagingOptions { exclude '**/libc++_shard.so' } |
的な感じなら大丈夫かも?
…とか書いとるうちにとりあえずapkの生成まではいけるようになった\(^o^)/
ちゃんと実際に動くかどうかはこれからやけど。
追記の追記:
普通のプロジェクト…いやちょっと普通でないかもしれんけど…だとうまく動いてそう。
でも、fat-aarが動かんくなってもうた?(複数のモジュール/jar/aarをまとめて1つのaarとして出力するやつな)。
Androidのgradle pluginが出力する中間ファイルの構造が結構変わってしまっとる。不具合の幾つかは簡単に直せそうやけど…うーんどないしたもんかなぁ(。・_・。)
コメント
[…] なぜわざわざ一応と書いたかというと、Android StudioでNDKのビルド〜ぱーとすりぃ〜で書きましたが、今までndk-buildを使って生成したものとは全く違うバイナリが生成されるからです。と […]