- 投稿日:2020-01-07T23:39:08+09:00
OpenCV をソースコードからビルドしてXamarin で利用する
はじめに
本記事は下記の続き(リベンジ)です。
Xamarin.Forms(Android) から OpenCV(C/C++) を利用する
前回はOpenCV公式の Android 用ライブラリを Xamarin から使用しました。
今回はソースコードをセルフビルドして Android 用の共有ライブラリ(*.so) を作成します。類型的ですが、ライブラリのセルフビルドには以下のメリットがあります。
モジュール(機能)を絞ることでライブラリのファイルサイズを減らせる
拡張モジュール群(opencv_contrib)をライブラリに含められる
確認環境
- OpenCV 4.2.0
- Windows 10
- CMake 3.16.2
- MinGW 8.2.0-5
- Android SDK 28.0.3
- Android NDK 20.0.5594570
環境構築
まずはビルドに必要なツールを導入していきます。OpenCV の入手は割愛します。
CMake
MinGW
Android SDK / NDK
CMake
ココ の Latest Release (3.16.2) をインストールしました。
MinGW
ココ のインストーラ (mingw-get-setup.exe) を使い、以下の設定でインストールしました。
インストールが完了したら 環境変数 PATH に binフォルダのパスを通しておきます。 ポイント
Android SDK / NDK
最後に Android SDK と NDK をインストールするため、ココ からAndroid SDK Command line tools を取得します。
(Android Studio をインストールされている方は、そちらからインストールすると良いと思います)
次に SDK / NDK をインストールします。今回は C:\dev\android-sdk\tools に解凍しました。
$ cd C:\dev\android-sdk\tools\bin $ .\sdkmanager.bat --update $ .\sdkmanager.bat --list # パッケージの一覧表示表示されたリストを見て SDK / NDK のバージョンを指定します。
今回は(VisualStudio に合わせて)以下のバージョンを選択しました。
$ .\sdkmanager.bat "build-tools;28.0.3" # SDK $ .\sdkmanager.bat "ndk;20.0.5594570" # NDKそれぞれ C:\dev\android-sdk 以下のディレクトリにインストールされます。
後工程の CMake Generate にて、android.toolchain.cmake の設定と NDK 内のフォルダ名が一致しなかったので、フォルダ名を手作業で変更しました。ポイント
変更前:C:\dev\android-sdk\ndk\20.0.5594570\toolchains\llvm\prebuilt\windows
変更後:C:\dev\android-sdk\ndk\20.0.5594570\toolchains\llvm\prebuilt\windows-x86_64
CMake Generate
環境構築が済んだので、いよいよ ビルドに向けた準備 に入ります。長い…
設定1
CMake(GUI) を立ち上げて、ソース / ビルドディレクトリ を指定します。
早速 Configure を押しそうになりますが、落ち着いて以下を "+ Add Entry" します。ポイント
(Androidのアーキテクチャが異なる方は armeabi-v7a や x86_64 を指定してください)
Name Type Value ANDROID_SDK_ROOT FILEPATH C:\dev\android-sdk ANDROID_ABI STRING arm64-v8a 追加したら Configure ボタンを押します。
Configure
以下を選択して、Next を押します。
次画面の toolchain では、インストールした NDK 内のファイルを選択します。
(OpenCV内の android.toolchain.cmake は古いっぽい(?)ので使いません)
- C:/dev/android-sdk/ndk/20.0.5594570/build/cmake/android.toolchain.cmake
設定2
Configure が完了したら、以下を追加で "+ Add Entry" して、再度 Configure を行います。
この辺りの設定はご自身の環境に合わせて ちょちょい と変えると良いと思います。
静的ライブラリを作成される方は、BUILD_SHARED_LIBS = OFF, BUILD_FAT_JAVA_LIB = ON (多分デフォルト設定)にして下さい。
Name Type Value(参考) ANDROID_NATIVE_API_LEVEL STRING 26 ANDROID_STL STRING c++_shared BUILD_PERF_TESTS BOOL OFF BUILD_TESTS BOOL OFF BUILD_ANDROID_EXAMPLES BOOL OFF BUILD_SHARED_LIBS BOOL ON BUILD_FAT_JAVA_LIB BOOL OFF Configure 完了後に、各設定の赤文字がなくなっていると思いますので Generate しましょう。
OpenCV ビルド
ついにビルドします!
コンソールで buildフォルダを開いて make を実行します。
私はこの make でエラーが出て 右往左往 しました…
$ cd C:\dev\opencv\opencv-4.2.0_build $ mingw32-make.exe ~~~ build ~~~ $ mingw32-make.exe installOpenCVのビルド作業は以上です。
C:\dev\opencv\opencv-4.2.0_build\include に 共有ライブラリ (*.so) が格納されてるはずです。
Xamarinからのライブラリ利用
こちらの記事と同じ手順で確認しました。
実機:Google Pixel 3 (Android 10.0 - API 29)Xamarin.Forms(Android) から OpenCV(C/C++) を利用する
参考
How to Build OpenCV 4.X for Native Android Development
Error compiling opencv for android #15457
Generate custom opencv .so for Android [Windows 10]
終わりに
冬休み中に完了できず足が出てしましましたのですが、何とか投稿まで進ることができ、年始に悔いを残さずに済みました。
Xamarin は 今 C# でホットな Blazor / Unity / .NET Core / UNO 等と比べて、傍から見ていて 少し熱が冷めた?落ち着いてきた? 印象を受けますが、まだまだ魅力のある環境だと思っています!
少し出遅れましたが、今年はぼちぼち学習していきたいです!
- 投稿日:2020-01-07T20:06:40+09:00
ExpoのPermissions周りを実機検証してみる
はじめに
Expoでの開発に限らず、iOSアプリでは一部のプライバシーに関わる機能を利用する際、その目的を説明する文言を設定する必要があります。設定した説明文は、ユーザーに権限を確認するためのダイアログに表示されます。
設定が必要な機能については下記のような記事が参考になりました。
iOS のユーザデータにアクセスするための Info.plist への許可設定まとめ
http://neos21.hatenablog.com/entry/2018/06/18/080000[iOS 10] 各種ユーザーデータへアクセスする目的を記述することが必須になるようです
https://dev.classmethod.jp/smartphone/iphone/ios10-privacy-data-purpose-description/Expoにおいてこの説明文(UsageDescription)は
app.jsonのexpo.ios.infoPlistオブジェクトに追加することでカスタマイズできます。
デフォルトだと説明文が存在しない訳ではなく、英語、かつ目的を説明していない(Give ~ permissionという感じの)ため、App Storeに申請する際、下記の記事に書かれているようにリジェクトされてしまいます。【React Native】【Expo】iOSのパーミッション要求ダイアログで審査リジェクトされた話とその対応
https://tech.maricuru.com/entry/2018/07/27/195921私が過去リジェクトされた際には目的がどうのというより、言語を統一しなさいという感じだったと思うので、目的をちゃんと説明しつつアプリが対象としている言語で設定する必要があるようです。
フォトライブラリ(カメラロール)、カメラ、通知、位置情報のパーミッション設定をiOS/Androidの実機で確認してみたので、その流れを説明します。
画面を作成
このようにPermissionを確認するボタンだけを表示するような画面を作ります。
追加するモジュールはこのあたりです
- expo-image-picker
- expo-permissions
- expo-location
- expo-haptics これは全然関係ないけど使ってみたかったので。許可が得られた時に振動をつけてみます。
Permission取得の処理
PermissionButton.jsimport React, { useState } from "react"; import { TouchableOpacity, Text, View, ActivityIndicator, Alert } from "react-native"; import * as Haptics from "expo-haptics"; import { Linking } from "expo"; import * as Permissions from "expo-permissions"; function PermissionButton(props) { const { type, title } = props; const [isLoading, setLoading] = useState(false); const [isGranted, setGranted] = useState(false); return ( <TouchableOpacity style={{ width: "100%", height: 50, marginVertical: 12 }} disabled={isLoading} onPress={async () => { if (isGranted) { props.onPress && props.onPress(); return false; } setLoading(true); const { status } = await Permissions.askAsync(Permissions[type]); setLoading(false); if (status === "granted") { setGranted(true); await Haptics.notificationAsync( Haptics.NotificationFeedbackType.Success ); setTimeout(props.onPress, 500); } else { // ユーザーが意図的にPermissionを不許可にしている場合 // アラートを表示して設定画面に移動する Alert.alert( `${title}が無効になっています`, "設定画面へ移動しますか?", [ { text: "キャンセル", style: "cancel" }, { text: "設定する", onPress: () => { Linking.openURL("app-settings:"); } } ] ); } console.log(type, status); }} > <View style={[ props.style, { position: "relative", width: "100%", height: "100%", justifyContent: "center", alignItems: "center", backgroundColor: isGranted ? "#3cbe8d" : "#3cb3ff", borderRadius: 25 } ]} > {isLoading ? ( <ActivityIndicator color="#ffffff" style={{ position: "absolute", width: "100%", height: "100%", top: 0, left: 15, justifyContent: "center", alignItems: "flex-start" }} /> ) : null} {isGranted ? ( <Text style={{ position: "absolute", width: "100%", height: "100%", lineHeight: 50, top: -2, left: 15, justifyContent: "center", alignItems: "flex-start" }} > 👍 </Text> ) : null} <Text style={{ color: "#ffffff", fontWeight: "bold" }} > {title} </Text> </View> </TouchableOpacity> ); } export default PermissionButton;このように、指定したPermissionをaskするボタンをコンポーネント化しておきました。
画面を作成し、各機能を確認する処理を追加
App.jsimport React from "react"; import { Text, View, Alert } from "react-native"; import PermissionButton from "./PermissionButton"; import * as ImagePicker from "expo-image-picker"; import * as Location from "expo-location"; import { Notifications } from "expo"; export default function App() { return ( <View style={{ flex: 1, padding: 32, backgroundColor: "#fff", alignItems: "center", justifyContent: "center" }} > <Text style={{ fontSize: 24, marginBottom: 16, fontWeight: "bold" }} > パーミッション確認 </Text> <PermissionButton type="CAMERA_ROLL" title="フォトライブラリ(カメラロール)" onPress={async () => { try { const result = await ImagePicker.launchImageLibraryAsync(); console.log(result); } catch (error) { Alert.alert(error.message); } }} /> <PermissionButton type="CAMERA" title="カメラ" onPress={async () => { try { const result = await ImagePicker.launchCameraAsync(); console.log(result); } catch (error) { Alert.alert(error.message); } }} /> <PermissionButton type="NOTIFICATIONS" title="PUSH通知" onPress={async () => { try { const token = await Notifications.getExpoPushTokenAsync(); console.log("token", token); const res = await fetch("https://expo.io/--/api/v2/push/send", { method: "POST", headers: { Accept: "application/json", "Content-type": "application/json" }, body: JSON.stringify({ to: token, title: "通知テスト", body: "ここにbodyが入ります", _displayInForeground: true }) }); console.log(res); } catch (error) { Alert.alert(error.message); } }} /> <PermissionButton type="LOCATION" title="位置情報" onPress={async () => { try { const { coords } = await Location.getCurrentPositionAsync(); Alert.alert( "位置情報の取得に成功しました", `緯度:${coords.latitude}\n経度:${coords.longitude}` ); } catch (error) { Alert.alert(error.message); } }} /> </View> ); }このように画面にボタンを配置して、各機能を確認するだけの処理を実装します。
通知に関しては送信が確認できればいいので、ExpoのAPIを直接叩いています。その際、iOSでもアプリ起動時に通知が表示されるように_displayInForeground: trueをオプションに指定しておきます。
app.jsonの編集app.json{ "expo": { "name": "許可テスト", "slug": "permission-test", "privacy": "public", "sdkVersion": "36.0.0", "platforms": [ "ios", "android", "web" ], "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "splash": { "image": "./assets/splash.png", "resizeMode": "contain", "backgroundColor": "#3cb3ff" }, "updates": { "fallbackToCacheTimeout": 0 }, "assetBundlePatterns": [ "**/*" ], "ios": { "supportsTablet": true, "bundleIdentifier": "バンドル名", "infoPlist": { "NSCameraUsageDescription": "[テスト文言]写真をアップロードするためにカメラを使用します", "NSPhotoLibraryUsageDescription": "[テスト文言]アカウント画像をアップロードするためにフォトライブラリを使用します", "NSLocationAlwaysUsageDescription": "[テスト文言]位置情報をアップロードします", "NSLocationUsageDescription": "[テスト文言]位置情報をアップロードします", "NSLocationWhenInUseUsageDescription": "[テスト文言]位置情報をアップロードします" } }, "android": { "package": "パッケージ名", "permissions": [ "ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION", "CAMERA", "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE", "VIBRATE", ] }, "notification": { "icon": "./assets/notificationicon.png", "color": "#3cb3ff" } } }
app.jsonはこんな感じで設定しました。
expo.ios.infoPlistに各UsageDescriptionを、expo.android.permissionsには必要なだけの機能を記載します。
一応expo.notificationでAndroid用の通知アイコンも設定しておきました。(詳しくはこちらの記事)
expo.android.permissionsのVIBRATEは、expo-hapticsを使うので入れています。検証
Android
実機での検証はAndroidの方が圧倒的に楽なので先にやってみます。
Androidの場合も6.0から一部機能でダイアログが表示されますが、この文言は設定できないので、
適切なexpo.android.permissionsがあれば申請の際に気にする必要はありません。
Expo Clientでの確認で十分な気もしつつ、念の為スタンドアロンアプリとしてビルドし実機で確認します。ビルド$ expo build:androidビルド後に表示されたURLからダウンロードし、
端末を繋げてからAndroid SDKのadb installコマンドでAPKファイルのパスを指定してインストールします。インストール$ adb install permission-test-xxxxxx.apk各Permissionを確認してみます。
通知
通知の許可に関してはダイアログが出ず通りました。
問題なく送信され、通知アイコンの設定も上手くいきました。色とアイコン(鍵)の部分が独自デザインです。
以上、Androidはこんな感じです。
iOS
iOSの場合、
ios.infoPlistの内容はExpo Client上では反映されないため、スタンドアロンアプリとしてビルドしてみないと検証できません。
しかし、スタンドアロンアプリを実機で確認する場合、リリースビルドをApp StoreやTestFlight経由でインストールするしか方法がありません(少なくとも公式には)。今回はTestFlightの内部テストで確認してみたいと思います。
途中までは通常のアプリ申請のフローと同じなので、
App Storeへの申請についての記事の
iOS用にビルド(Apple Developer Programにメンバーシップ登録後)
セクションからアプリ(ipaファイル)をApp Store Connectにアップロード
セクションまでと同じ手順でビルドからアップロードまでを行います。Transporterでアップロードする場合はこんな感じに。
そしてアップロードが完了した後、App Store ConnectのTestFlightタブを見るとこんな感じです。
何らかの処理をしているようなので、しばらく待ちます。数十分から一時間ほどかかります。。。
しばらくしてAppleからメールが届くかと思います。もう一度App Store Connectを見ると、米国の輸出コンプライアンスへの確認が求められます。「!」アイコンから進み質問に答えてから、
テスターに自分を招待して、iOS端末にTestFlightをインストールして確認します。
各Permissionを確認してみます。
「
"アプリ名"が〜を求めています」という見出しは端末の言語設定によってiOSが固定で入れている文言です。
その下の部分に、app.jsonで設定した説明文が表示されています。通知
通知の場合は、もともとOSで決められたものが表示されます。
PUSH通知も問題なく届きました。再確認の方法
iOSの許可ダイアログは一回しか表示されず、「設定」から各アプリ・各機能へのアクセスを不許可にしても再度表示することはできません。
再度ダイアログを表示させるには、「設定」→「一般」→「リセット」→「位置情報とプライバシーをリセット」で全てのアプリのプライバシー設定ごとリセットするか、アプリを再インストールするしか方法は無いようです。TestFlightではアプリをアンインストールしてからこのようにすぐ再インストールすることが可能ですので、App Store経由の場合よりは比較的簡単にリセットできるかと思います。
Androidの場合は「設定」→「アプリと通知」→アプリを選択し、「権限」から各機能の許可を外すことができます。
その場合はiOSと違って、リクエストした際には再び許可ダイアログが表示されます。
- 投稿日:2020-01-07T18:01:41+09:00
CoordinatorLayoutをやってみた
はじめに
今回はCoordinatorLayoutを使用し、下記のようなスクロールでツールバー等を見え隠れさせるアプリを作成してみました。
実際に実装してみた
上記のアプリの実際のコードはこちらになります。
MainActivity.ktclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Adapterの設定 val sampleList = mutableListOf<String>() for (i in 0..20) { sampleList.add(i.toString()) } val adapter = RecyclerAdapter(sampleList) recycler_view.adapter = adapter // 区切り線の表示 recycler_view.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL)) } }activity_main.xml<RelativeLayout ... > <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <androidx.appcompat.widget.Toolbar ... app:layout_scrollFlags="scroll|enterAlwaysCollapsed" /> </com.google.android.material.appbar.AppBarLayout> <androidx.recyclerview.widget.RecyclerView ... app:layout_behavior="@string/appbar_scrolling_view_behavior" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> </RelativeLayout>実装する際のポイント
上記のようなアプリを実装するためには、いくつかのポイントがあります。
1.CoordinatorLayoutでスクロール時に隠すためには、スクロールする部分がNestedScrollViewかRecyclerViewでないといけません。
2.隠したい部分はAppBarLayoutの中に配置し、app:layout_scrollFlagsを使用することで、隠れてくれます。
app:layout_scrollFlagsには色々な種類がありますが、ここでは割愛します。
上記のアプリを図で表して見ると、こんな感じになります。
3.
RecyclerViewにlayout_behaviorがないと、AppBarLayoutにRecyclerViewが重なって表示されてしまいます。4.下記の画像の場合、隠したくないものは
AppBarLayoutより上位の階位に書く必要があります。
おわりに
今回はCoordinatorLayoutを使用し、スクロールでツールバー等を見え隠れさせるアプリを作成してみました。
実装する中で、私が迷ったことや必要なポイントをまとめてみました。
皆さんのお役に立てれば幸いです。
- 投稿日:2020-01-07T17:34:17+09:00
画面部品の配置
初学者ですが、個人的な勉強の為まとめました
画面部品の配置
Androidアプリの画面は、Android SDKで用意された画面部品を配置することで作成する。これが、.xmlファイルに画面部品タグを記述することである
画面部品については、大きくビューグループとビューの2つがあるビューグループ
・ビューグループは、各画面の配置を決めるもので、レイアウト部品とも呼ばれる。以下は、主なレイアウト部品である
①<LinearLayout>・・・一番扱いやすいレイアウトで、画面部品を縦/横方向に並べて配置
②<TableLayout>・・・表形式で画面部品配置
③<GridLayout>・・・グリッド形式で画面部品を配置
④<FrameLayout>・・・画面部品を重ねて配置
⑤<RelativeLayout>・・・画面部品を相対的に配置
⑥<ConstraintLauout>・・・RelativeLayout同様に、画面部品を相対的に配置※RelativeLayoutは、Android2.2まで、プロジェクトを作成した際に生成されるレイアウトXMLの最初に記述されていたものだが、2.3以降はより扱いやすくしたレイアウト部品としてConstraintLayoutが導入され、これが基本レイアウトとなった
ビュー
・ビューは、画面部品そのもので、ウィジェットとも呼ぶ
・以下は、代表的なビュー
①<TextView>・・・文字列の表示
②<EditText>・・・テキストボックス
③<Button>・・・ボタン
④<RadioButton>・・・ラジオボタン
⑤<CheckBox>・・・チェックボックス
⑥<Spinner>・・・ドロップダウンリスト
⑦<ListView>・・・リスト表示
⑧<SeekBar>・・・スライダー
⑨<RatingBar>・・・☆でレート値を表現
⑩<Switch>・・・ON/OFFが表現できるスイッチタグの組み合わせ
・Android画面では、レイアウト部品とビュー部品を階層的に組み合わせて使う
・レイアウト部品は、画面部品の配置を決めるものなので、その配下に画面部品を含んで使う
例)activity_view_sample.xml<LinearLayout> <TextView/> </LinearLayout>・レイアウト部品のような子要素を持つタグは開始タグと終了タグで囲み、ビュー部品は子要素を持たないものが多いので、終了タグを書かず、タグの右カッコの前にスラッシュを入れる、属性のみのタグが基本
よく使われる属性
①android:id
・画面部品のidの設定
・アクティビティ(Javaプログラム)内でこの部品を扱う場合にはIDを記述
・@+id/・・・のように記述し、「・・・」の名前で部品にアクセスできる
例)activity_view_sample.xml<TextView android:id="@+id/tvLabelInput"②android:text
・画面部品が表示されるときの文字列を設定
・表示文字列は直接記述せずにstring.xmlに記述するので、string.xmlに記述された文字列と画面部品とを紐付ける方法が@string/・・・である
例)activity_view_sample.xml<TextView android:text="@string/tv_msg"/>string.xml<string name="tv_msg">お名前を入力してください。</string>③android:layout-width/height
・widthが部品の幅、heightが高さを表す
・すべての画面部品に記述する必要がある
・~dpのような数値、もしくはwrap_contentやmatch_contentなどを使う
・wrap_contentは、必要なサイズに自動調整する
・match_contentは、親部品のサイズいっぱいまで拡張する④android:margin/padding
・margin/paddingともに余白を表す
・marginは画面外側の余白、paddingは部品の内側の余白
- 投稿日:2020-01-07T17:33:52+09:00
Project作成ウィザード
初学者ですが、個人的な勉強の為まとめました
Project作成ウィザード
AndoridStudioプロジェクトの作成には、プロジェクト作成ウィザードを使う
以下が、このウィザードを構成する4つの画面①Create Android Project画面
・以下の、3つの入力欄がある
1.Application name
・アプリ名の入力欄だが、アプリ名というより、プロジェクト名と理解したほうが良い
2.Project location
・プロジェクトファイルを格納するフォルダ
・デフォルトでは、<ユーザーのホームフォルダ>¥AndroidStudioProjects配下にプロジェクト名と同名のフォルダが作られる3.Company domain
・入力内容に応じて自動でPackag nameが記述される
・ドメインを逆順にしたものを起点とし、そこにプロジェクト名を加えたものがルートパッケージになる
例えば、ドメインが「hogehoge.com」,プロジェクト名が「HelloAndroid」の場合は、com.hogehoge.・・・helloandroid②Target Android Devices画面
・どの端末向けのアプリを開発するかを選択する画面
③Add an Activity to Mobile画面
・ある程度ソースコードが記述された13種類のテンプレートから適当なものを選択する画面
④Configure Activity画面
・Androidアプリ開発では、処理をJavaクラス(.Javaファイル)、画面構成をXML(XMLファイル)に記述し、このペアで一つの画面が作られる
・Javaクラスのことをアクティビティと呼び、Activityクラス(または、その子クラス)を継承して作る
・XMLファイルはレイアウトファイルと呼ぶ
・アクティビティと、レイアウトファイルのペアで一つの画面が作られ、このことから通常、関連した名前を付ける
例えば、アクティビティ名が、MainActivityなら、レイアウトファイル名は、activity_main.xmlとなり、これらを自動化してくれるのが、Configure Activity画面
- 投稿日:2020-01-07T17:33:30+09:00
Androidビュー
初学者ですが、個人的な勉強の為まとめました
Androidビュー
Projectツールウィンドウの見え方は変更でき、デフォルトではAndroidビューである
Androidビューでは、開発に必要なファイルのみ表示するようになっており、必要に応じてはProjectビューなど他のビューに切り替える
※Projectビューは、より実際のファイルなどに近い構成になっているAndroidビューのファイル構成
・大きく、manifests、java、resの3フォルダに分かれる
①manifests
・AndroidManifest.xmlファイルが格納されている
・AndroidManifest.xmlファイルは、アプリの実行に必要な設定が記述されている②java
・名前の通り、javaファイルが格納されている
・パッケージ右側に(androidTest),(test)と記述されているのは、Androidアプリをテストするための.Javaファイルの格納先③res
・.xmlファイルや、アプリで使われる画像ファイルなどを格納する
・resフォルダには、以下のサブフォルダがある
①drawable・・・画像を格納
②layout・・・画面構成に関わる.xmlファイルを格納
③mipmap・・・アプリのアイコンを格納
④values・・・アプリで格納する固定文字列(String.xml)、画面のスタイル(styles.xml)、色構成(colors.xml)を表す.xmlファイルなどを格納
- 投稿日:2020-01-07T16:48:05+09:00
[Android] Serviceクラス(bindService)
Serviceとは
ダウンロードなどの処理時に、UI操作を阻害しないようにするため、Activityとは別に動作させることができる。
startService()とbindService()の2種類がある。今回はbindServiceについて記す。・startService
Activityが破棄されても、stopSerivceかstopSelfによって明示的に終了するまで続く。・bindService
バインドしているActivityがなくなると破棄される。使い方
Serviceを継承したクラスを作り、クライアント側から呼び出すことで使用できる。
まずクライアント側クラスは以下の通りである。
Mainctivity.javapublic class MainActivity extends AppCompatActivity { // 起動するServiceのBinder BindService.MyBinder binder; // ServiceConnectionオブジェクト private ServiceConnection conn = new ServiceConnection() { // Service接続成功時のCallback @Override public void onServiceConnected(ComponentName name, IBinder service) { binder = (BindService.MyBinder) service; } // Service異常切断時のCallback @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 起動用Intent final Intent intent = new Intent(MainActivity.this, BindService.class); /*ServiceをBound状態にする。インスタンスがなければ作成を要請する。 *また、Serviceと通信するためのIBinderインターフェースはシステムからコピーされたものが渡される。 */ bindService(intent, conn, Service.BIND_AUTO_CREATE); //バインドを解除する。この時、条件によってはServiceも終了する。 unbindService(conn); }次にService側クラスの基本構造は以下の通りである。
AndroidManifestに登録しなければ使えないので気を付けて頂きたい。
BindService.javapublic class BindService extends Service { // onBind()で返すBinder private MyBinder binder = new MyBinder(); //最初のbindService呼び出しのみ、システムにIBinderインターフェースを渡すために呼ばれる。 @Override public IBinder onBind(Intent intent) { return binder; } /*Serviceのインスタンスがない状態で、クライアントがstartServiceまたはbindServiceを呼んだ時に *Serviceのインスタンス生成で呼ばれる。すでにインスタンスが存在している場合は呼ばれない。 */ @Override public void onCreate() { super.onCreate(); } /*インドしているクライアントが「全て」いなくなったとき。そのためunbindServiceが呼ばれても、 *ほかにバインドしているクライアントが存在した場合、onUnbindは呼ばれない。 */ @Override public boolean onUnbind(Intent intent) { } //バインドされたクライアントがなくなって、onUnbindが呼ばれたあとに呼ばれる @Override public void onDestroy() { } }AndroidManifest.xml
<application <service android:name=".BindService"/>サンプルの実装と結果
本記事では以下の記事のサンプルと同じものを作ってみた。(コードは割愛)
http://javait.hatenablog.com/entry/2015/12/15/235135困ったところ
AndroidManifestへの登録を忘れていたが、それ以外は特になかった。
感想
参考サイトには使わなかったメソッドもあったので、またの機会に使ってみたい。
参考URL
サンプル
http://javait.hatenablog.com/entry/2015/12/15/235135Serviceの詳しい説明
http://ria10.hatenablog.com/entry/20121110/1352537601
- 投稿日:2020-01-07T16:41:52+09:00
androidのアプリをビルドすると、apkサイズが肥大化する
androidのアプリをビルドすると、apkサイズが肥大化する
gitlab ciで、androidアプリをビルドしていたのですが、いつからか「apkサイズが大きくなった気がする」と報告を受けたので、確認すると以前は出ていなかったワーニングが出ていることに気が付いた
android gradle プラグインバージョン:3.5.3
gradle バージョン:5.4.1コマンドラインでのビルドで出る様になったワーニング
> Task :app:stripProdReleaseDebugSymbols Compatible side by side NDK version was not found. Unable to strip library '/builds/jad/jadcenterapp/app/build/intermediates/merged_native_libs/prodRelease/out/lib/armeabi-v7a/libconscrypt_jni.so' due to missing strip tool for ABI 'ARMEABI_V7A'. Packaging it as is. Unable to strip library '/builds/jad/jadcenterapp/app/build/intermediates/merged_native_libs/prodRelease/out/lib/arm64-v8a/libconscrypt_jni.so' due to missing strip tool for ABI 'ARM64_V8A'. Packaging it as is. Unable to strip library '/builds/jad/jadcenterapp/app/build/intermediates/merged_native_libs/prodRelease/out/lib/x86_64/libconscrypt_jni.so' due to missing strip tool for ABI 'X86_64'. Packaging it as is. Unable to strip library '/builds/jad/jadcenterapp/app/build/intermediates/merged_native_libs/prodRelease/out/lib/x86/libconscrypt_jni.so' due to missing strip tool for ABI 'X86'. Packaging it as is.gitlab ci で、androidアプリをビルドする(ワーニング出る状態)の定義
gitlab-ci.ymlimage: openjdk:8-jdk variables: ANDROID_COMPILE_SDK: "28" ANDROID_BUILD_TOOLS: "28.0.3" ANDROID_SDK_TOOLS: "4333796" ARTIFACTS_NAME: "hoge_center_$CI_PIPELINE_ID" ARTIFACTS_DIR: "app/build/outputs" before_script: - apt-get --quiet update --yes - apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1 - wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_TOOLS}.zip - unzip -d android-sdk-linux android-sdk.zip - echo y | android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null - echo y | android-sdk-linux/tools/bin/sdkmanager "platform-tools" >/dev/null - echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null - export ANDROID_HOME=$PWD/android-sdk-linux - export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/ - chmod +x ./gradlew # temporarily disable checking for EPIPE error and use yes to accept all licenses - set +o pipefail - yes | android-sdk-linux/tools/bin/sdkmanager --licenses - set -o pipefail stages: - build apkCreate: stage: build script: - ./gradlew :app:assembleDevRelease - ./gradlew :app:assembleStgRelease - ./gradlew :app:assembleProdRelease artifacts: name: $ARTIFACTS_NAME paths: - $ARTIFACTS_DIR when: manual
ワーニングを確認してみる
1. Compatible side by side NDK version was not found.
「互換性のあるサイドバイサイドNDKバージョンが見つかりませんでした。」(google翻訳)
NDKインストールして無かったから、インストールする様にしてみた。
ndk-bundleのインストールを追加(2行目)
gitlab-ci.yml- echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null - echo y | android-sdk-linux/tools/bin/sdkmanager "ndk-bundle" >/dev/nullすると、、、先ほど出ていたワーニングは出なくなったが、今度は「ARMEABI」が出る様になった
> Task :app:stripProdReleaseDebugSymbols Unable to strip library '/builds/jad/jadandroid/app/build/intermediates/merged_native_libs/prodRelease/out/lib/armeabi/libAndrJFPDFEMB.so' due to missing strip tool for ABI 'ARMEABI'. Packaging it as is.ググって、調べてみると
https://stackoverflow.com/questions/53437653/android-studio-missing-strip-tool「The default installed NDK doesn't seem to have the tools required to strip binaries that have been built with ARMEABI support, so it ends up packaging the whole library, which increases the built file size substantially.
I've found that installing the "NDK (Side by side)" tool from Android Studio -> Tools -> SDK Manager -> SDK Tools takes care of this warning and also reduces the built APK size, especially for React Native projects.」
デフォルトでインストールされたNDKには、ARMEABIサポートを使用してビルドされたバイナリを除去するために必要なツールがないようです。
そのため、ライブラリ全体がパッケージ化され、ビルドされたファイルサイズが大幅に増加します。これが原因でapkのサイズが肥大化してるぽい
2. ARMEABIはNDK r17で削除されたそうなので、ARMEABIネイティブのsoファイルを除外
app/build.gradleandroid { packagingOptions { // exclude ARMEABI native so file, ARMEABI has been removed in NDK r17. exclude "lib/armeabi/**" } }ワーニングが出なくなって、サイズも元に戻り、めでたしめでたし
- 投稿日:2020-01-07T15:43:34+09:00
【初心者向け】FireBaseとUnityの連携方法①【画像付き】
はじめに
僕「バックグラウンドをFireBaseで簡単に任せることが出来ることを知ったからゲームでも使いたい!」
公式HPを見ながら作業環境を構築してると知識不足なのか慣れてないのかよく分からなくて初心者泣かせだった。そんな、同じような方に向けて分かりやすい連携方法を紹介できればなと思います。
公式の連携方法ページ→https://firebase.google.com/docs/unity/setup?hl=ja
FireBaseの機能一覧でとても分かりやすかった→ https://qiita.com/hippoHippo/items/21cf7fb76bcf8591129f作業環境
・Windows
・Unity 2019.2.9f1
を使用して作業を行った結果です。事前準備として、Unityプロジェクトは個々で作っといてください。省略します。
作業一覧
①FireBaseの作業ドキュメントを作成する。
※ドキュメント名に数字を入れた所DataBaseを使用する際に、このドキュメント名が使われるが数字が文字化け?なのか省略されてるのかして数字だけが抜けるので、数字入れるのは避けた方が良い?かもしれません!
![]()
![]()
②アプリ追加
1.作成したドキュメントのトップページに移行したら、アプリ追加からUnityアイコンをクリックする。
2.IOS又はAndroidを選択する。(今回はAndroid)
3.パッケージ名、アプリのニックネーム(省略可)を入力する。★このパッケージ名はUnityのAndroidのPackageNameをコピペする。
※ここのパッケージ名の2つ目のドットの後は個々で作成した、Unityのプロジェクト名が入ります。
③設定ファイルのダウンロード
Androidなら「google-services.json」をダウンロードする。
④FirebaseSDKをダウンロード
zipファイルを解凍。
⑤Unityにインポートする
ここからが公式HP見ながら行った結果、結構戸惑いました。
Unity 2017.x 以降では、.NET 4.x フレームワークを使用できます。Unity プロジェクトで .NET 4.x を使用する場合は、dotnet4/FirebaseAnalytics.unitypackage をインポートします。
と、Unityのバージョン2017以降使っていればdonet4を使用することは分かりました。
上記のようにdotnet4/FirebaseAnalytics.unitypackageをインポートする。公式HPの解説→https://firebase.google.com/docs/unity/setup?hl=ja#add_sdk
⑥Google Play 開発者サービスの要件を確認する
正直ここはあまり理解していないが入力した
適当なスクリプトのStartまたはAwakeにHPにあるコードをコピペ公式HPの解説→https://firebase.google.com/docs/unity/setup?hl=ja#confirm_google_play_version
★⑦Unityで発生するエラーを解決する★ ここ重要
Unityに⑤で行った後にUnityにてエラーが起こるかと思います。
公式でも、そこに関して解説しているが画像付きで詳しく。公式HPの解説→https://firebase.google.com/docs/unity/setup?hl=ja#known_issues
Pluginsフォルダが以下の通りになっていること確認する。
公式HPにもある通り、
doNet45フォルダの外にある2つのピースのやつが⑤で行ったときにdotnet3のものをインポートした際に使用する物である。今回は、dotnet4をインポートしたためにdoNet45フォルダの中身の2つのピースを使用する。
まずは、使用しないdoNet45ファイル外の2つのピースを無効化または削除する。
↓無効化ならこの通りにする。
そして使用する、doNet45ファイル内の2つのピースを有効化する。
↓以下のようにすべて使うの、「Any Platform」を選択
ここでPlatform settingsを左からの一つずつ
Editor settings…UnityEditor上で動く
――― CPU OSともにANYで良いと思います。PC,MAC & Linux standalone settings…PC上でビルドしたものが動く
――― Windowsの場合はこうする。
![]()
※Macのチェックマーク等は外すAndroid settings…Android上でビルドしたものが動く
――― ここでの変更箇所はなし。変更したら、必ずApplyする。
おわりに
ここまでで、環境構築はおしまいです。
次は、RealTimeDataBaseを使ってUnityと繋げてみたいと思います。紹介
今まで学生で作ってきたポートフォリオを是非みてください。→https://keita0122neec.wixsite.com/portfolio
- 投稿日:2020-01-07T14:35:01+09:00
UnityでBuilding Gradle projectが一生終わらない場合の対応
はじめに
いつものようにUnityでAPK書き出しを行なおうとしたらビルドが下記画面から何時間経っても変わらない事象が発生したので対応方法をメモ
解決策
結論から話すと、ウイルス対策ソフトが原因でした
ESET Cyber Security Proのリアルタイムファイルシステム保護とファイアウォールをOFFにするとビルドが完了しましたネットで調査した際に見つけた対策案
gradleのアップデート
brew upgrade gradleでgradle自体をアップデートしますAndroidSDK&NDK をUnityHub経由でインストール
2020年1月7日現在ではAndroidSDKとNDKをAndroidStudioを使うことなくUnityHubから直接インストールできるようになりました
インストール→三点縦リーダーボタン→モジュールを加える ボタンからインストール可能です
プロジェクトをクローンし直す
要はキャッシュ削除ですね、自分も一度はこの方法でエラーメッセージが書き出されるようになったので影響はありそうです(ただもう一度ビルドし直すとビルドが終わらない元の事象に戻りましたが...)
- 投稿日:2020-01-07T11:11:43+09:00
「Gson is deprecated.」らしいのでMoshiを試してみる
はじめに
今までJSONパーサにGsonを使っていたのですが、あのJakeWharton氏曰く「Gson is deprecated.」との事だったので、Moshiを試してみました。
MoshiはSquare社が開発した軽量なJSONパーサで、Gsonの不満点を解消するために作られたそうです。
軽くGsonとの比較も行います。
導入
build.gradleapply plugin: 'kotlin-kapt' ... // Moshi implementation 'com.squareup.moshi:moshi:1.9.2' implementation 'com.squareup.moshi:moshi-kotlin:1.9.2' kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.9.2' // 1.9.Xからこれが無いと "Cannot serialize Kotlin type" エラーが発生する // Retrofit implementation "com.squareup.retrofit2:retrofit:2.7.0" implementation "com.squareup.retrofit2:converter-moshi:2.7.0"シリアライズ
Gson
data class Repos( val id: Int, val name: String, // 変数名とJSONキーが一致しない場合は@SerializedNameアノテーションを付ける @SerializedName("full_name") val fullName: String )Moshi
@JsonClass(generateAdapter = true) // Adapter化に必要 data class Repos( val id: Int, val name: String, // 変数名とJSONキーが一致しない場合は@Jsonアノテーションを付ける @Json(name = "full_name") val fullName: String )fromJSON
Gson
val jsonString = """ { "id":1, "name":"name", "full_name":"fullName" } """.trimIndent() val gson = Gson() val repos = gson.fromJson(jsonString, Repos::class.java)Moshi
val jsonString = """ { "id":1, "name":"name", "full_name":"fullName" } """.trimIndent() val adapter = Moshi.Builder().build().adapter(Repos::class.java) val repos = adapter.fromJson(jsonText)toJSON
Gson
val gson = Gson() val repos = Repos(1, "name", "fullName") val jsonString = gson.toJson(repos)Moshi
val adapter = Moshi.Builder().build().adapter(Repos::class.java) val repos = Repos(1, "name", "fullName") val jsonString = adapter.toJson(repos)Retrofitとの連携
Gson
private val gson = GsonBuilder() .setLenient() .create() return Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create(gson)) .build()Moshi
private val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() return Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(MoshiConverterFactory.create(moshi)) .build()パッと見ではGsonの方がコード量が少なく見えますが、Gsonはnon-nullな変数にnullを入れてしまう可能性があるという問題点があるようで、kotlinのnull安全を正しく利用しようとするならMoshiを使った方が安全なようです。
おわりに
今回の記事を書くにあたって以下のリンクを参考にさせて頂きました。
【Qiita】Kotlinと相性が良いMoshiのkotlin extensionを使う
【Qiita】Kotlin JSON (Moshi) 使い方




































