20190529のKotlinに関する記事は3件です。

KotlinTestのSpec一覧

久しぶりにKotlin製のテストフレームワークであるKotlinTestを使ってみようとしたんですが、
このフレームワークには色々なSpec(テストの書き方)の形式があるので、使い分けのために全てメモしてみました。

バージョン等

本稿執筆時点では以下のバージョンを使用しています

  • Kotlin
    • 1.3.31
  • KotlinTest
    • 3.3.2

build.gradle

build.gradle
plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
}

repositories {
    mavenCentral()
}

test {
    useJUnitPlatform()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    testImplementation("io.kotlintest:kotlintest-runner-junit5:3.3.2")
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

各Spec一覧

ちなみに各Spec共通(後述のAnnotationSpecはのぞく)ですがKotlinTestの基本的な書き方として、
それぞれ使いたいSpecのクラスを継承して、コンストラクタに渡すラムダかinitの中にテストコードを書いていきます。
サンプルを見てもらえればわかると思います。

String Spec

KotlinTestでもっとも基本になるSpec。テストの概要をStringで書いてそこにラムダを渡すだけのシンプルな形式です。
どのSpecを使うか迷ったらとりあえずこれを使っておけ、という趣旨のことが公式のドキュメントに書いてあったりします。

import io.kotlintest.shouldBe
import io.kotlintest.specs.StringSpec

class StringSpecSample: StringSpec() {

    init {
        "1 + 1 should be 2" {
            (1 + 1) shouldBe 2
        }
    }

}

Fun Spec

FunSpecはtest()メソッドを使用してテストを定義します、
またtest()メソッドの外側にcontext()メソッドでグループ化することもできます。

import io.kotlintest.shouldBe
import io.kotlintest.specs.FunSpec

class FunSpecSample: FunSpec({
    context("足し算") {
        test("1+1は2") {
            (1 + 1) shouldBe 2
        }
        test("2+3は5") {
            (2 + 3) shouldBe 5
        }
    }
})

Should Spec

Fun Specにとてもよく似ている書き方ですが、testの代わりにshouldというメソッドを使用します。
String Specのように文字列のcontextをつかってネストすることができます。
FunSpecと何が違うのかと言われるととても難しいのですが、個人的にはコードの見た目はこちらの方が好みです。

import io.kotlintest.shouldBe
import io.kotlintest.specs.ShouldSpec

class ShouldSpecSample: ShouldSpec({
    "足し算" {
        should("1+1は2") {
            (1 + 1) shouldBe 2
        }

        should("2+3は5")  {
            (2 + 3) shouldBe 5
        }
    }
})

Word Spec

こちらもShould Specと似ていて、文字列のコンテキストとshouldを使って階層をネストしたテストを書けるスタイルです。
Word specで使用するshouldはラムダを渡すinfix関数として定義されているので、コードの見た目はこちらの方がよりKotlinらしいような気がします。

また、Whenというキーワードを使ってさらにネストさせることもできます。
ちなみにこのキーワード、以前のバージョンでは小文字のwhenでKotlinの予約語とかぶるため`when`といちいちバッククォートで囲ってやる必要があった気がするのですが、
さすがに改善されたようです。
もちろん`when`のままでも動きます。

import io.kotlintest.shouldBe
import io.kotlintest.specs.WordSpec

class WordSpecSample: WordSpec({
    "足し算"  should {
        "1 + 1 should be 2" {
            (1 + 1) shouldBe 2
        }
    }

    "計算式" When {
        "足し算"  should {
            "1 + 1 should be 2" {
                (1 + 1) shouldBe 2
            }
        }

        "引き算"  should {
            "5 - 3  should be 2" {
                (5 - 3) shouldBe 2
            }
        }
    }

})

Feature Spec

Feature Specはfeatureとscenarioの2つのキーワードを使ってテストを記述していくスタイルです。
詳しくないのでわかりませんが、cucumberの書き方に似せて作ってあるそうです。

import io.kotlintest.shouldBe
import io.kotlintest.specs.FeatureSpec

class FeatureSpecSample: FeatureSpec({
    feature("足し算") {
        scenario("1 + 1 は 2") {
            (1 + 1) shouldBe 2
        }
    }
})

Behavior Spec

名前からしてなんとなく察しがつきますが、BDDスタイルのテストを書くのに適した書き方です。
Given, When, Thenといったキーワードを使ってテストを記述していきます。
また、Andを使って階層をさらにネストさせることができます。

私の環境だけかもしれませんがIntelliJでテストを実行した時にGivenやWhenで定義したコンテキストが表示されなかったので、
ネストしてもグループ化してもあまり意味がなかったです。

import io.kotlintest.shouldBe
import io.kotlintest.specs.BehaviorSpec

class BehaviorSpecSample: BehaviorSpec({
    Given("計算機") {
        And(" + ボタン") {
            When ("最初の数が1") {
                And("2つ目の数も1") {
                    Then("計算結果は2") {
                        (1 + 1) shouldBe 2
                    }
                }
            }
        }
    }
})

Free Spec

-キーワードを使って任意の階層までテストをネストできるスタイルです。
階層数を任意にできるのとキーワードを多用せずにほぼStringだけでいけるので、見た目はかなりスッキリしそうです。
ただ下記の例のように無駄なネストを増やすと可読性が落ちそうなので、規約でネストの上限を設けるなどした方が良い気もします。

import io.kotlintest.shouldBe
import io.kotlintest.specs.FreeSpec

class FreeSpecSample: FreeSpec({
    "ネストしたテストを書けるので" - {
        "無意味にネストしています" - {
            "さらにネストして" - {
                "もう一段ネストした結果" - {
                    "ようやくここでテストを書きます" {
                        (1 + 1) shouldBe 2
                    }
                }
            }
        }
    }
})

Describe Spec

describe,context,itといったキーワードでテストを記述していくスタイルです。
Rubyのrspecを模したスタイルなので、Rubyの経験が長い人にはおなじみの見た目になりますね。

import io.kotlintest.shouldBe
import io.kotlintest.specs.DescribeSpec

class DescribeSpecSample: DescribeSpec({
    describe("計算機能") {
        context("足し算") {
            it("1 + 1 は2") {
                (1 + 1) shouldBe 2
            }
        }
    }
})

Expect Spec

contextやexpectといったキーワードをつかってネストされたテストを書けるスタイルです。
使うキーワードが違う以外はFun Specと全く同じに見えるのですが、これも何か他の言語や他のテストフレームワークに似せて作ってあるんでしょうか?

import io.kotlintest.shouldBe
import io.kotlintest.specs.ExpectSpec

class ExpectSpecSample: ExpectSpec({
    context("足し算") {
        expect("1+1は2") {
            (1 + 1) shouldBe 2
        }
        expect("2+3は5") {
            (2 + 3) shouldBe 5
        }
    }
})

Annotation Spec

JUnitにそっくりなスタイルです。というかほぼJUnitそのまんまですね。
JUnitに慣れてるから書き方はそのまま使いたいけどKotlinTestの便利matcherを使いたい、とかそんな用途ですかね。

import io.kotlintest.shouldBe
import io.kotlintest.specs.AnnotationSpec

class AnnotationSpecSample: AnnotationSpec() {

    @Test
    fun 一足す一は二() {
        (1 + 1) shouldBe 2
    }
}

まとめ

Specが色々あるのは知っていたんですが、改めて調べてみるとなんと10種類もありました。
これだけあるとどれを使っていいか悩んでしまいますが、公式には書き方以外の機能は同一とのことです。
なので基本形であるString Specをベースに使いつつ、ネストさせたい時には適宜好みのものを使っていく、という感じになるでしょうか。

ただこちらの記事によると、
Specの種類によってはKotlinTestの持つ便利機能がうまく使えないことがあるようなので、そこだけは注意が必要かと思います。

今回作ったサンプルはGitHubに公開してあります。
https://github.com/fisherman08/Kotlin-test-spec-samples

参考にした記事等

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

八丈島のホテルで、運用費用0円の伝票システムアプリ作って、業務改善した話。

はじめまして!
いつも皆さんの面白い記事を読んで、学んだり励みにしたり楽しんだりしてます!ありがとうございます!

このたび東京都八丈島のホテル、リードパーク&リゾート八丈島で伝票システムアプリを作らせてもらいました!

このシステムアプリの全機能はブログの記事で動画を交えて説明しています。
(QiitaじゃTwitter経由でしかアップできないため)

全機能説明ページはこちら

謝辞

ホテルの皆様

今回自分がこんな貴重な機会を得られ、最後まで作ることができたのは、寛容で柔軟なホテル支配人・レストランリーダー・スタッフの皆様のお陰だと本当に思います。

最初は遅延もあったり、レシート2枚出てきたりしてましたが、毎日使用後に多くのフィードバックを得られたので開発がとても捗りました。
「楽しい!」「今までで1番使いやすい」などの声は本当に嬉しいです:laughing:

Firebase(Google)さん

ありがとうFirebaseとGoogleさん!!!
お陰でサーバーレス・運用費0円で開発できました。
今度 Pixel 3a 買います

作ったもの  ---動画---

作ったもの  ---図---

注文受付^B伝票アプリ.png
?この拙い図で表しきれてませんが、僕が作った業務アプリは、

  • スタッフが客席で注文をタブレットで入力 → DBに記入
  • バーカウンターの端末(Fire HD10)がDBの変更を瞬時にリッスン、DBからデータを取って、Bluetooth経由でプリンターに送信 → レシート印刷

というものです。
これをAndroid 上でKotlin & Firebase Realtime Databaseでつくりました。
(技術や機械の選定理由は後ほど)

ちょっと自己紹介

自分は1998年生まれで、神戸大学の経済学部に通っていて現在は休学中です。(戻らないと思う。)
もともと文系でプログラミングとは無縁でしたが、半年ほど前に Kotlin のチュートリアル本から学び始めて今に至ります。Kotlin大好き。
次は Flutter & Firebase でなにか作って公開しようとおもいます。

この後は東京に行ってどこかで働きたいと思ってます!
(ご縁があればよろしくお願いします:raising_hand_tone1:

このアプリを作ることになった経緯

もともとこのホテルのレストランで住み込みバイトで働いていて、空いた時間に個人開発でアプリを作ろうと思ってました。

r01.jpg
ホテルのレストラン: 公式ホームページより

このホテルは最大200名様分の客席があり、レストランは

  • 全149席
  • 個室宴会場(40席) ある大きいレストランです。

ここで働いているうちに色々な改善点があるなと思うようになってきました。
その内の一つ(かつ最大)が、 ドリンクの注文 → 席に運ぶ までの流れと、伝票計算の処理です。

レストランの業務は開店・閉店作業を除くと主に、以下の作業です。

  1. お客様を席まで案内 → 最初のドリンク注文をとる
  2. 各お客様のペースに合わせて食事配膳(和食のコース料理で、約7種類)&空いた皿を下げる
  3. ドリンクの追加注文を受けるたび、作業中断でドリンクを作り、持っていく。

この①、③のドリンク作業がネックでした。

従来はドリンクの注文の度、
従来のホテルレストラン図.png

このように、1人のスタッフが一連の作業を全て行っていました。
夕食の時間帯は3つあって(18時~/18時30分~/19時~)、それぞれの時間帯で約20~50名ほどが一気に来られます。スタッフは1席ごとの注文で1往復するので、最初のドリンクを早く捌かないとその後の全ての作業が遅れてしまいます。
追加注文の際も、作業が中断されるので効率が落ちてしまいます。

そしてお客が全員帰ると、各伝票に各ドリンクの単価を記入して、電卓で合計を計算、記入していました。(多いときは100人分ほど)

この作業をなくしたいと思い、試作品を作って、支配人・ホテルリーダーに見せると即決で「試してみよう」となり、開発が決まりました!

導入後の作業フロー

新しいホテルレストラン図.png

各スタッフが端末(Fire 7)をポケットに入れ、バーカウンターに通信用の端末1台と、サーマルプリンター1台を置いています。

注文を受けるスタッフは、バーカウンターに行く必要はなく、注文入力後、次の席に行ってまた注文を受けられます。
バーカウンターのスタッフは次々と印刷されるレシートを見て、ドリンクを作り、お盆にレシートと一緒にドリンクを置いていきます。
そして手の空いているスタッフが、そのお盆を持ち、レシートに記されている席まで運びます。(レシートはどんどん重ねて挟んでいく。)

このように、作業の分業化ができるようになりました!!
さらに単価もレシートに記載されていおり、最後に1枚1枚伝票を計算する必要もなくなりました

使用した技術・機器の選定理由

ハード面

Why Fire7 ?

自分がここのホテルに来る前、他の伝票サービスを導入する試みがあったらしく、もともとFire 7 はありました。(結局導入には至らなかったそうです。)
それに、ホテルのソムリエエプロンのポッケに丁度フィットします:v:

Why(サーマルプリンター) StarPrnt TSP650||?

スター精密は安心できるし、SDKもしっかりしていて、尚且つ安価なのが決めてでした。(4万円弱)
このモデルはBluetooth通信に対応していて、印刷速度も1番早かったのでこれに決めました。

ソフト面

Why Android?

もともとFire端末があり、自分もAndridを学び始めたばっかだったから。
(Fire OSは、独自といってますがほぼAndroid。)
この偶然はラッキーでした。

Why Kotlin?

自分が学び始めたのがKotlinからだったから。
(Javaと100%相互互換なので結果的にJavaも段々わかってきた。)

Why Firebase?

最初はAWSかAzureと思っていたのですが、Firebaseは無料枠(しかも大容量)があり、それにDBの性能が最高でこれだけで十分だったからです。(とくにリアルタイム同期とオフライン処理)
Firebaseの無料枠が、AWS(Amazon)・Azure(Microsoft)との競争の中で勝つための策だとしたらAWS, Azureにも感謝です。

?100名ほどのディナー1日で、この容量なので、まだまだまだ余裕があります。
2019-05-29.png

Why Firebase RealtimeDB?

これは、FireStoreと悩んだのですが結局RealtimeDBにしました。理由は、

  • Realtime DBは Key-Valueなので、高速。
  • FireStoreの特徴の強力なクエリは必要なかった。
  • ベータ版なのでちょっと気が引ける。

でしたが、よく考えると お客130人前後の注文情報なんて500Bぐらいで済むので、遅延とかはそこまで変わらないと思います。

それに最近知ったのですが、FireStoreの方はGoogleのインフラをフル活用していて、今後GoogleはFirestore推しでいくらしいです。
ですが、β版というのが気になりました。

次の個人開発からはFirestoreを使います。

※追記
@chronicle さんのご指摘で、見落としに気付きました。ありがとうございます!
Firestoreは現在正規版で、GCPのサービス品質保証契約(SLA)も適用されてます! どんどん使いましょう!

課題解決

ここからは、開発の過程でつまづいた所、その解決策を書いていきます。

レシート1枚で全ての情報を表す。

分業化するにあたって、注文を受ける人ドリンクを作る人運ぶ人はレシート1枚を通して情報伝達を行う必要があります。必要な情報は、
ドリンクを作るのに

  • ドリンク名(もしくは商品名)
  • その飲み方(お湯割りやロックなど)
  • あればオプション(「常温で」、「レモンつけて」など)

テーブルまで運ぶのに

  • テーブル番号

会計用に

  • そのテーブルの全注文履歴と合計金額
  • 各ドリンク(商品)の単価
  • お部屋番号(会計は部屋付けなので)

以上の情報を1枚のレシートに入れなければなりません。
最初はStarPrntのSDKを使えば、なんか良い感じに自動で割り振ってくれると思ってました(希望的観測)。

が、実際は自分でテンプレートを作ったり、行数・フォントサイズを調整する必要がありました。

これはこれでめっちゃ楽しかったです。まず普段のレシートに目が行くようになって、「あ~これ手抜いてるな」とか「すげぇ!どうやって!?」みたいに思うところが増えました。

コードを貼るとめちゃ長くなるので、別記事でレシートの作り方はまとめます。

少し言うと、縦横を揃えるためにすべての文字を全角にして、計算して空白を適切な数入れたり、1行に収められるようにしたりしました。?
(半角のサイズは全角の半分でないため、混ざっているとややこしい。)

   var order = Transliterator.getInstance("Halfwidth-Fullwidth").transliterate(orderList[x])

   if (order.count() >= 18){
         order = order.substring(0, 18)
    }

   val size = order.count()
   val rest = 22 - size
   data = (order + " ".repeat(rest - 3) + qList[x] + "\n")    

超はしょって、試行錯誤の末こうすることで解決しました。
レシートの.jpg

以下の点に気を付けました。

  • お客様にとって重要な情報は大きく太く(ここはお客様の年齢層も高く、見やすいレイアウトを心がけた)
  • お客にとって重要でない情報(日時、作り方、端末番号)は、小さく明記
  • 注文ごとをブロックで分け、どの注文を作れば良いのか、わかるようにした。(常に1番下のブロックのドリンクを作る)

これで、お客は何を頼んで合計は何円かわかるし、ドリンクを作る人も何をどのように作るのか、運ぶ人もどこに運ぶのかを一目でわかるようになりました!

注文ごとをブロックで分けるにはDBの設計を見直す必要がありました。
DB設計で気をつけたことは後述しますが、これは柔軟なNoSQLモデルだからこそ簡単にできたのだと思います。

プリンターの排他制御

これはめちゃくちゃ悩みました。まず、排他制御自体を知らなかったので探そうにもGoogleは教えてくれず、、、だったのですがMENTAというサービスでメンターの方に相談するとすぐに解決しました。

Kotlin(Java)ではとっても簡単で、

@Syncronized
fun a (){
//処理
}

?このように関数の上に @Syncronizedを入力するだけで排他制御が実装できます。

あとはコードの設計を見直して、排他制御を実装した関数内で
DBからデータの読み取り → レシート情報作り → 印刷 → 初期化
の処理を行うようにします。
13台同時に注文してもきちんと印刷されました:relaxed:

DBの設計

Firebase RealtimeDBでは、リレイショナルなモデルではなく、データをJSONツリー型で保持するNoSQLモデルです。

自分はリレイショナルなDBをほとんど触ったことがなかったので、逆に変な違和感とかはなかったです。
このモデルで大事なことは2つで、

  • 余計なデータまで読み込まないように、深くネストしない
  • 効率よく必要なデータを見つけるための、平坦化(非正規化)

です。さらにこのアプリでは、DB設計を考えるにあたって

  • 各注文ごとをブロックで分ける(理由は上記)
  • 簡単にレシート印刷できるようにする

という事を考えながら設計し、以下のようにしました?!
(11番テーブルの注文のみ)

db設計.png

RealtimeDBでは、最大34回ネストできるのですが、ここでは5回に抑えることができました。

そしてどのデータもテーブル番号 (Table Number) (画像では TN 11)と紐づけることで、注文2回目以降は、テーブル番号入力後、すぐにメニュー画面へ遷移でき、途中でデータの削除や数量変更などもできます。

また、一番上の"Checker"ノードを作ることで、、効率的にレシートを印刷できます。
色々なクラスからレシートを印刷する必要があるのですが(注文履歴変更後や、確認用など)、それも以下のコードで実装できます。?

val table = "11" //実際のコードでは、この値をintentで渡している。

//実際には、トランザクション処理
val mDatabase = Firebase.getInstance().getReference("Checker")
mDatabase.child("TN $table").child("check").setValue(false)
mDatabase.child("TN $table").child("check").setValue(true)

それに、プリンター横の端末は、"Checker"ノードにリスナーを設置し、"check"の値がfalse → trueになるのを監視するだけでいいので、こうしています?

 val check: Boolean = p0.child("check").value.toString().toBoolean()
 val tablet = p0.child("tablet").value.toString()

 if (!oldValue && check){    //oldValueはリスナー外で定義している
       val tableNum = p0.key.toString()
       readData(tableNum, tablet)  //テーブルの情報を全部読み取り、レシート印刷
  }

 oldValue = check

時々レシートが2枚印刷される問題は、この実装で解決しました。

直感的なUI

前に導入しようとしていた伝票システムは、全体的に単色で、メニューの選択画面が文字のリストで、とても見にくかったとのことでした。

なので自分は極力文字を少なく・画像やベクター図を多く、見やすい色やフォントサイズを心がけました。
また、1つの画面で全ての操作を終えるのはなく、各画面で行う操作は1つにしました。

スタッフは画面毎の単純な質問に答えるように操作していきます。

(例えば、日本酒を選択するとおちょこの数を質問し、ワインだとグラスの数、焼酎だと飲み方、ソフトドリンクだと、氷の有無アイスorホットを質問します。)

?下は、焼酎のボトルを注文するときの画面遷移
UI一覧.jpg

流れるように素早い操作が可能なように心がけましたが、そうすると一方でミスが増える可能性がでてきます。
なので、できるだけミスを無くすために、また、お客様の急な変更に対応できるように(「やっぱビール3つで!」みたいな)、
注文が3種類以上の時は自動的に【注文内容は大丈夫?】画面に遷移するようにしました!
ここの画面から、商品の追加や数量変更・削除も可能です。

コツコツ改善 ①SoftKeyboardのフォーカス調節

あとは、小さなことなんですが、注文の最後の画面では、EditTextのフォーカスをデフォルトで外しているのですが、メモを残すボタンタップ時にソフトキーボードがでてくるようにしました!

キーボード.png

このコード?で、ソフトキーボードを出す実装ができます。

fun showSoftKeyboard(view:View) {
  if (view.requestFocus())
  {
    val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
  }
}

:参考ページ 【How to Hide and Show Soft Keyboard in Android】

コツコツ改善 ②ホーム画面整理

これは、システム関係ないのですが、もし誤操作でホーム画面に戻ってしまっても、一目でどれが伝票システムアプリかわかるようにしました。(完全人力)
ホーム画面.jpg

終わりに

ハード機器の選定から、DB設計、UIまで、全部自分1人でやると色々わかってきますが、1番思うことは、自分のレベルの低さです。(本当に)

そして、フィードバックの大切さ。個人開発のアプリでは今回のような直接的なフィードバックがない分、色々なLogや、ユーザーのデータが相当すると思うので、次に活かしたいです。

あとはメンターの大切さ。今回自分は登録だけして使いませんでしたが、英語版ではCodeMentor、日本語版ではMENTAなどがあるので、積極的に利用していきたいです。

最後まで読んで頂いてとても嬉しいです!
ありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidStudio モジュール内のC++を参照する(Java / kotlin)

Androidでライブラリー作成(AAR形式)はモジュールを作成することで実現します。
ただ、 モジュール内のC++ソース参照はめんどくさいので手順を残します。

■Setp1:プロジェクト作成

2019-05-29 (11).png
「File」「New」「New Project」でプロジェクトを作成します。
2019-05-29 (12).png
C++を使用するので。「Native C++」を選択し「Next」をおします。
2019-05-29 (13).png
今回はNameは「Test02」で
Save Locationは開発するディレクトリを指定
Languageは今回はJavaで(Kotlinでも大丈夫です)設定したら「Next」を
2019-05-29 (14).png
あとは、そのまま「Finish」を
2019-05-29 (15).png
こんな画面になればプロジェクト作成できています。

■Setp2;モジュール作成

2019-05-29 (17).png
Setp1で作ったプロジェクトにモジュールを追加します。
「File」「New」「New Modile」を選択してください。
2019-05-29 (18).png
今回は「Android Library」を選択し、「Next」を
2019-05-29 (19).png
Application/Library nameは今回は「test02module」にしています。
「Finish」を押せば
2019-05-29 (20).png
こんな感じになり、「test02module」がプロジェクトに追加されたことがわかります。

■Setp3:モジュールにC++のソースを追加。

まず、C++のソースを置くディレクトリを作成します。
[test02]
+---[test02modile]
  +---[src]
    +---[main]
      +---[cpp] (このディレクトリを作成)
そこに「CMakeList.txt」「subTest.cpp」「subTest.h」 を作成します。

CMakeList.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        native-module

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        subTest.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

set( LIB_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../.. )
set( OUTPUT_DIR ${LIB_ROOT}/lib/${ANDROID_ABI} )
set_target_properties( native-module
        PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR} )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        native-module

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})
subTest.cpp
#include "subTest.h"

subTest::subTest() {
}

void subTest::Hoge(int in_no) {
    int a = in_no;
}
subTest.h
#pragma once

class subTest{
public:
    subTest();
public:
    void Hoge(int in_no);
};

この三ファイルを追加したらbuild.gradle(Module:test02module)を編集します。
2019-05-29 (21).png

build.gradle
android {
   :
   :
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
}

を追加します。
これでいったんSyncさせると
2019-05-29 (23).png
ファイルがModuleに追加されたことがわかります。

■Setp4:アプリのCからモジュールのCを呼び出す。

アプリ側のbuild.gradle(Module:app)を編集します。
2019-05-29 (27).png

android {
  :
  :
    sourceSets {
        main {
            jniLibs.srcDir '../test02module/src/lib'
            jni.srcDirs = []
        }
    }
}

これで、C++のライブラリsoファイルを参照できるようにします。
アプリ側の「CMakeKists.txt」を編集します。
2019-05-29 (28).png

CMakeKists.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp)

include_directories(../../../../test02module/src/main/cpp)

add_library( native-module
        SHARED
        IMPORTED )
set_target_properties( native-module
        PROPERTIES IMPORTED_LOCATION
        ../../../../../test02module/src/lib/${ANDROID_ABI}/libnative-module.so)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        native-lib
        native-module
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

これで、参照可能になったので。 アプリのCのソースをnative-lib.cpp

native-lib.cpp
#include <jni.h>
#include <string>

#include "subTest.h"

extern "C" JNIEXPORT jstring JNICALL
Java_l_toox_test02_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    subTest ss;
    ss.Hoge(32);
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

で参照できます。

■Step5:コンパイルエラー

そのままコンパイルするとエラーになるときがあります。
それはsoファイルが作成されてないかもしれないので
2019-05-29 (26).png
「test02modile」を選択したあとに「Build」を選択すると「Make Moduke 'test02module'」があらわれるので実行してから、アプリを実行すると問題ないです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む