Android
Jetpack Compose を使用して Zylix でネイティブ Android アプリケーションを構築します。このガイドでは、Android Studio のセットアップ、JNI 統合、Google Play デプロイについて説明します。
前提条件
始める前に、以下がインストールされていることを確認してください:
- Android Studio Hedgehog (2023.1.1) 以降
- Android NDK r25 以降
- Zig 0.11.0 以降
- JDK 17 以降
- Kotlin と Jetpack Compose の基本知識
# Zig インストールの確認
zig version
# Java インストールの確認
java -versionアーキテクチャ概要
flowchart TB
subgraph Android["Android アプリ"]
subgraph Kotlin["Kotlin/Compose 層"]
UI["Composable UI"]
ViewModel["ViewModel"]
JNI["JNI バインディング"]
end
subgraph Native["ネイティブ層 (libzylix.so)"]
VDOM["Virtual DOM"]
State["状態管理"]
Events["イベントハンドラ"]
end
UI --> ViewModel
ViewModel --> JNI
JNI --> Native
end
プロジェクトセットアップ
ステップ 1: Android プロジェクトの作成
- Android Studio を開く → New Project
- Empty Activity (Compose) を選択
- 設定:
- Name:
ZylixApp - Package:
com.example.zylixapp - Minimum SDK: API 26 (Android 8.0)
- Name:
ステップ 2: Zylix 共有ライブラリのビルド
Android 用に libzylix.so をビルド:
cd core
# arm64-v8a 用にビルド(最も一般的)
zig build -Dtarget=aarch64-linux-android -Doptimize=ReleaseFast
# armeabi-v7a 用にビルド(古いデバイス)
zig build -Dtarget=arm-linux-androideabi -Doptimize=ReleaseFast
# x86_64 用にビルド(エミュレータ)
zig build -Dtarget=x86_64-linux-android -Doptimize=ReleaseFastライブラリを Android プロジェクトにコピー:
mkdir -p app/src/main/jniLibs/{arm64-v8a,armeabi-v7a,x86_64}
cp zig-out/lib/libzylix-arm64.so app/src/main/jniLibs/arm64-v8a/libzylix.so
cp zig-out/lib/libzylix-arm.so app/src/main/jniLibs/armeabi-v7a/libzylix.so
cp zig-out/lib/libzylix-x64.so app/src/main/jniLibs/x86_64/libzylix.soKotlin 統合
データクラス
ZylixState.kt を作成:
package com.example.zylixapp
data class ZylixState(
val counter: Int,
val todoCount: Int,
val version: Int
)
// イベントタイプ定数
object ZylixEvents {
const val INCREMENT = 1
const val DECREMENT = 2
const val RESET = 3
const val TODO_ADD = 10
}JNI ライブラリクラス
ZylixLib.kt を作成:
package com.example.zylixapp
object ZylixLib {
init {
System.loadLibrary("zylixjni")
}
external fun init(): Int
external fun deinit(): Int
external fun dispatch(eventType: Int, payload: ByteArray?, len: Int): Int
external fun getState(): ZylixState?
// 便利メソッド
fun dispatch(eventType: Int) = dispatch(eventType, null, 0)
fun dispatch(eventType: Int, payload: String) {
val bytes = payload.toByteArray(Charsets.UTF_8)
dispatch(eventType, bytes, bytes.size)
}
}ViewModel
ZylixViewModel.kt を作成:
package com.example.zylixapp
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class ZylixViewModel : ViewModel() {
private val _state = MutableStateFlow(ZylixState(0, 0, 0))
val state: StateFlow<ZylixState> = _state.asStateFlow()
init {
val result = ZylixLib.init()
if (result != 0) {
throw RuntimeException("Zylix の初期化に失敗: $result")
}
refreshState()
}
override fun onCleared() {
super.onCleared()
ZylixLib.deinit()
}
private fun refreshState() {
ZylixLib.getState()?.let { newState ->
_state.value = newState
}
}
fun increment() {
ZylixLib.dispatch(ZylixEvents.INCREMENT)
refreshState()
}
fun decrement() {
ZylixLib.dispatch(ZylixEvents.DECREMENT)
refreshState()
}
fun reset() {
ZylixLib.dispatch(ZylixEvents.RESET)
refreshState()
}
fun addTodo(text: String) {
if (text.isNotBlank()) {
ZylixLib.dispatch(ZylixEvents.TODO_ADD, text)
refreshState()
}
}
}Composable UI
MainActivity.kt を作成:
package com.example.zylixapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
class MainActivity : ComponentActivity() {
private val viewModel: ZylixViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
ZylixApp(viewModel)
}
}
}
}
}
@Composable
fun ZylixApp(viewModel: ZylixViewModel) {
val state by viewModel.state.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// カウンターセクション
CounterSection(
count = state.counter,
onIncrement = viewModel::increment,
onDecrement = viewModel::decrement,
onReset = viewModel::reset
)
}
}
@Composable
fun CounterSection(
count: Int,
onIncrement: () -> Unit,
onDecrement: () -> Unit,
onReset: () -> Unit
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "カウンター",
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "$count",
fontSize = 64.sp,
style = MaterialTheme.typography.displayLarge
)
Spacer(modifier = Modifier.height(16.dp))
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Button(onClick = onDecrement) {
Text("-")
}
OutlinedButton(onClick = onReset) {
Text("リセット")
}
Button(onClick = onIncrement) {
Text("+")
}
}
}
}デバッグ
よくある問題
| 問題 | 解決策 |
|---|---|
| UnsatisfiedLinkError | ライブラリが正しい jniLibs フォルダにあるか確認 |
| init でクラッシュ | Logcat でネイティブエラーメッセージを確認 |
| 状態が更新されない | JNI メソッドシグネチャが一致しているか確認 |
Play Store デプロイ
署名付き APK/Bundle の生成
- Build → Generate Signed Bundle/APK
- Android App Bundle を選択
- キーストアを作成または使用
- リリースバージョンをビルド