Android Studio
開発ツール
Android Studio
概要
Android StudioはGoogle社が開発するAndroid専用の統合開発環境(IDE)です。Android アプリケーション開発に特化し、公式の開発ツールとして世界中のAndroid開発者に使用されています。
詳細
Android StudioはIntelliJ IDEAをベースに2013年にGoogle社によってリリースされたAndroid開発専用IDEです。現在の最新版はAndroid Studio Koala(2024.1.1)で、IntelliJ 2024.1プラットフォームをベースとしています。JavaとKotlinの完全サポート、Android SDKとの深い統合、高速エミュレーター、豊富なデバッグツールなど、Android開発に最適化された環境を提供します。
2024年版では重要な新機能が多数追加されています。生成AI統合により、Google AI SDKを使用したGemini APIテンプレートによってAI機能を持つアプリの開発が可能になりました。Running Devicesウィンドウの拡張により複数デバイスの同時表示、新しいターミナル、エディターの「sticky lines」機能などが追加されています。
Android Studioの最大の特徴は、Android開発に特化した包括的な機能セットです。スマートコードエディター、自動コード補完、リアルタイム監査、Gradle統合、レイアウトエディター、プロファイリングツール、Firebase統合、Google Play Console連携など、Android開発の全工程をカバーしています。AVDエミュレーターは6秒以内の高速起動を実現し、様々なデバイスでのテストを効率化します。
メリット・デメリット
メリット
- Android公式IDE: Googleによる公式サポートとAndroid SDKとの完全統合
- 軽量で高速: 軽量設計により高速起動とメモリ効率の良い動作
- 直感的なUI: 初心者から上級者まで使いやすいインターフェース
- 豊富なライブラリサポート: 他のIDEより多くのライブラリとの統合
- 高速エミュレーター: 6秒以内の起動で多様なデバイステストが可能
- チーム開発支援: コード共有、変更マージ、バージョン管理の優れたサポート
- 完全無料: 全機能を無償で利用可能
デメリット
- 高いシステム要件: 最低4GBのRAMと64ビットOSが必要
- インストール時間: 初回インストールに時間がかかる
- RAMの大量消費: 大量のメモリを使用し、古いPCでは動作が重い
- プラグインの影響: 多数のプラグイン使用時にパフォーマンス低下
- Android専用: Android以外の開発には使用できない
- 学習コスト: 機能豊富ゆえに初期習得に時間が必要
- ビルド時間: 大規模プロジェクトでは長いビルド時間
主要リンク
- Android Studio公式サイト
- Android Developers Documentation
- Android Studio Release Notes
- Kotlin for Android
- Firebase Documentation
- Google Play Console
書き方の例
プロジェクト設定例
// MainActivity.kt - Kotlin Activityの基本構造
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapp.ui.theme.MyAppTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainScreen()
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {
var text by remember { mutableStateOf("") }
val items by viewModel.items.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
TopAppBar(
title = { Text("My Android App") }
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Enter item") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
if (text.isNotEmpty()) {
viewModel.addItem(text)
text = ""
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("Add Item")
}
Spacer(modifier = Modifier.height(16.dp))
LazyColumn {
items(items) { item ->
ItemCard(
item = item,
onDelete = { viewModel.removeItem(it) }
)
}
}
}
}
@Composable
fun ItemCard(
item: String,
onDelete: (String) -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = item,
style = MaterialTheme.typography.bodyLarge
)
IconButton(onClick = { onDelete(item) }) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Delete"
)
}
}
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
MyAppTheme {
MainScreen()
}
}
Gradle ビルド設定
// build.gradle.kts (Module: app)
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
id("androidx.navigation.safeargs.kotlin")
}
android {
namespace = "com.example.myapp"
compileSdk = 34
defaultConfig {
applicationId = "com.example.myapp"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
debug {
isDebuggable = true
applicationIdSuffix = ".debug"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
viewBinding = true
dataBinding = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.8"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.compose.ui:ui:1.5.8")
implementation("androidx.compose.ui:ui-tooling-preview:1.5.8")
implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.navigation:navigation-compose:2.7.6")
// Hilt Dependency Injection
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
// Network
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
// Database
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
kapt("androidx.room:room-compiler:2.6.1")
// Testing
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.5.8")
debugImplementation("androidx.compose.ui:ui-tooling:1.5.8")
debugImplementation("androidx.compose.ui:ui-test-manifest:1.5.8")
}
ViewModel例
// MainViewModel.kt - MVVM アーキテクチャ
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
private val repository: ItemRepository
) : ViewModel() {
private val _items = MutableStateFlow<List<String>>(emptyList())
val items: StateFlow<List<String>> = _items.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private val _error = MutableStateFlow<String?>(null)
val error: StateFlow<String?> = _error.asStateFlow()
init {
loadItems()
}
fun addItem(item: String) {
viewModelScope.launch {
try {
_isLoading.value = true
repository.addItem(item)
loadItems()
} catch (e: Exception) {
_error.value = e.message
} finally {
_isLoading.value = false
}
}
}
fun removeItem(item: String) {
viewModelScope.launch {
try {
repository.removeItem(item)
loadItems()
} catch (e: Exception) {
_error.value = e.message
}
}
}
private fun loadItems() {
viewModelScope.launch {
try {
_isLoading.value = true
_items.value = repository.getAllItems()
_error.value = null
} catch (e: Exception) {
_error.value = e.message
} finally {
_isLoading.value = false
}
}
}
}
Room データベース設定
// AppDatabase.kt - Room データベース設定
import androidx.room.*
import androidx.room.Room
@Entity(tableName = "items")
data class ItemEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis()
)
@Dao
interface ItemDao {
@Query("SELECT * FROM items ORDER BY created_at DESC")
suspend fun getAllItems(): List<ItemEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertItem(item: ItemEntity)
@Query("DELETE FROM items WHERE name = :name")
suspend fun deleteItemByName(name: String)
@Query("DELETE FROM items")
suspend fun deleteAllItems()
}
@Database(
entities = [ItemEntity::class],
version = 1,
exportSchema = false
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
companion object {
const val DATABASE_NAME = "app_database"
}
}
// データベースプロバイダー(Hilt)
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
AppDatabase.DATABASE_NAME
).build()
}
@Provides
fun provideItemDao(database: AppDatabase): ItemDao {
return database.itemDao()
}
}
Android Manifest設定
<!-- AndroidManifest.xml -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApp"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.MyApp"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".DetailActivity"
android:exported="false"
android:parentActivityName=".MainActivity" />
<service
android:name=".BackgroundService"
android:enabled="true"
android:exported="false" />
<receiver
android:name=".NotificationReceiver"
android:enabled="true"
android:exported="false" />
</application>
</manifest>
Unit Test例
// MainViewModelTest.kt - Unit テスト
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.*
import org.junit.*
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.junit.MockitoJUnitRunner
@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner::class)
class MainViewModelTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val testDispatcher = UnconfinedTestDispatcher()
@Mock
private lateinit var repository: ItemRepository
private lateinit var viewModel: MainViewModel
@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
viewModel = MainViewModel(repository)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun `addItem should update items list`() = runTest {
// Given
val newItem = "Test Item"
val expectedItems = listOf(newItem)
`when`(repository.getAllItems()).thenReturn(expectedItems)
// When
viewModel.addItem(newItem)
// Then
verify(repository).addItem(newItem)
verify(repository, times(2)).getAllItems() // 初期化時とaddItem後
assert(viewModel.items.value == expectedItems)
}
@Test
fun `removeItem should remove item from list`() = runTest {
// Given
val itemToRemove = "Item to remove"
val updatedItems = emptyList<String>()
`when`(repository.getAllItems()).thenReturn(updatedItems)
// When
viewModel.removeItem(itemToRemove)
// Then
verify(repository).removeItem(itemToRemove)
verify(repository, times(2)).getAllItems()
assert(viewModel.items.value == updatedItems)
}
}