12月 21, 2025
53 分で読める

シニアAndroid開発者の面接質問:Kotlin・Compose・アーキテクチャ

interview
career-advice
job-search
シニアAndroid開発者の面接質問:Kotlin・Compose・アーキテクチャ
Milad Bonakdar

Milad Bonakdar

著者

Kotlinコルーチン、Jetpack Composeの状態管理、アーキテクチャ、パフォーマンス、テスト、オフラインファースト、セキュリティを実践的に確認できるシニアAndroid面接対策です。


はじめに

シニアAndroid開発者の面接では、アーキテクチャのトレードオフ、ライフサイクルに安全な並行処理、Composeの状態所有、オフラインファーストのデータフロー、パフォーマンス診断、テスト戦略、セキュリティを説明できる必要があります。強い回答は、使うAPIを答えるだけでなく、その選択がプロダクト上の制約に合う理由と、本番環境でどう調査するかまで示します。

このガイドは実践的なチェックリストとして使ってください。まずはKotlinコルーチンとFlow、ViewModelと状態管理、Roomを使ったリポジトリ、Composeと既存UIの判断、起動時間とメモリ、そして自分のアプリで説明できる具体例を優先しましょう。

使い方

  • 各回答を「背景、トレードオフ、実装、失敗パターン」という短い判断として練習する。
  • 技術選択を、起動の速さ、安定したオフライン読み取り、安全なトークン保存、ライフサイクル不具合の削減などのユーザー価値につなげる。
  • アーキテクチャ、パフォーマンス、テスト、セキュリティについて、自分の実プロジェクトの話を1つずつ準備する。

高度な Kotlin と言語機能 (5つの質問)

1. Kotlin コルーチンとスレッドに対する利点を説明してください。

回答: コルーチンは、非同期コードを順次記述できる軽量な並行処理プリミティブです。

  • スレッドに対する利点:
    • 軽量: パフォーマンスの問題なく数千のコルーチンを作成できます
    • 構造化された並行処理: 親子関係により、適切なクリーンアップが保証されます
    • キャンセルサポート: キャンセルの伝播が組み込まれています
    • 例外処理: 構造化された例外処理
  • 主要コンポーネント:
    • CoroutineScope: ライフサイクルを定義します
    • Dispatchers: 実行コンテキストを制御します (Main, IO, Default)
    • suspend 関数: 一時停止および再開できます
class UserRepository(private val api: ApiService) {
    suspend fun getUser(id: Int): Result<User> = withContext(Dispatchers.IO) {
        try {
            val user = api.fetchUser(id)
            Result.success(user)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

class UserViewModel : ViewModel() {
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> = _user
    
    fun loadUser(id: Int) {
        viewModelScope.launch {
            when (val result = repository.getUser(id)) {
                is Result.Success -> _user.value = result.data
                is Result.Error -> handleError(result.exception)
            }
        }
    }
}

希少性: 非常に一般的 難易度: 難しい


2. シールドクラスとは何ですか?また、いつ使用すべきですか?

回答: シールドクラスは、すべてのサブクラスがコンパイル時に既知である制限されたクラス階層を表します。

  • 利点:
    • 網羅的な when
    • 型安全な状態管理
    • 複雑なデータには列挙型よりも優れています
  • ユースケース: 状態、結果、ナビゲーションイベントの表現
sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String) : UiState<Nothing>()
}

class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState<User>>(UiState.Loading)
    val uiState: StateFlow<UiState<User>> = _uiState.asStateFlow()
    
    fun loadUser() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val user = repository.getUser()
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "不明なエラー")
            }
        }
    }
}

// UI での使用例
when (val state = uiState.value) {
    is UiState.Loading -> showLoading()
    is UiState.Success -> showData(state.data)
    is UiState.Error -> showError(state.message)
    // コンパイラはすべてのケースが処理されることを保証します
}

希少性: 一般的 難易度: 普通


3. Kotlin Flow を説明し、LiveData との違いを説明してください。

回答: Flow は、値を順番に放出する Kotlin のコールド非同期ストリームです。

  • Flow vs LiveData:
    • Flow: コールドストリーム、演算子をサポート、ライフサイクルを認識しない、より柔軟
    • LiveData: ホットストリーム、ライフサイクルを認識する、Android 固有、UI にはよりシンプル
  • Flow の種類:
    • Flow: コールドストリーム (収集時に開始)
    • StateFlow: 現在の状態を持つホットストリーム
    • SharedFlow: イベント用のホットストリーム
class UserRepository {
    // コールド Flow - 収集時に開始
    fun getUsers(): Flow<List<User>> = flow {
        val users = api.fetchUsers()
        emit(users)
    }.flowOn(Dispatchers.IO)
    
    // StateFlow - ホット、状態を保持
    private val _userState = MutableStateFlow<List<User>>(emptyList())
    val userState: StateFlow<List<User>> = _userState.asStateFlow()
    
    // SharedFlow - ホット、イベント用
    private val _events = MutableSharedFlow<Event>()
    val events: SharedFlow<Event> = _events.asSharedFlow()
}

class UserViewModel : ViewModel() {
    val users = repository.getUsers()
        .map { it.filter { user -> user.isActive } }
        .catch { emit(emptyList()) }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )
}

// Activity/Fragment で収集
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.users.collect { users ->
            updateUI(users)
        }
    }
}

希少性: 非常に一般的 難易度: 難しい


4. インライン関数とは何ですか?また、いつ使用すべきですか?

回答: インライン関数は、関数呼び出しのオーバーヘッドを回避するために、関数本体を呼び出しサイトにコピーします。

  • 利点:
    • ラムダ割り当てのオーバーヘッドを排除
    • ラムダからの非ローカルリターンを許可
    • 高階関数でのパフォーマンス向上
  • ユースケース: ラムダパラメータを持つ高階関数
  • トレードオフ: コードサイズが増加します
// インラインなし - ラムダオブジェクトを作成
fun measureTime(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    val end = System.currentTimeMillis()
    println("Time: ${end - start}ms")
}

// インラインあり - ラムダオブジェクトは作成されません
inline fun measureTimeInline(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    val end = System.currentTimeMillis()
    println("Time: ${end - start}ms")
}

// noinline - 特定のパラメータがインライン化されるのを防ぎます
inline fun performOperation(
    inline operation: () -> Unit,
    noinline logger: () -> Unit
) {
    operation()
    saveLogger(logger)  // noinline ラムダを保存できます
}

// crossinline - インラインを許可しますが、非ローカルリターンを防ぎます
inline fun runAsync(crossinline block: () -> Unit) {
    thread {
        block()  // 外側の関数からリターンできません
    }
}

希少性: 普通 難易度: 難しい


5. Kotlin の委譲について説明してください。

回答: 委譲により、オブジェクトはその責任の一部を別のオブジェクトに委譲できます。

  • クラス委譲: by キーワード
  • プロパティ委譲: Lazy、observable、delegates
  • 利点: コードの再利用、継承よりもコンポジション
// クラス委譲
interface Repository {
    fun getData(): String
}

class RemoteRepository : Repository {
    override fun getData() = "Remote data"
}

class CachedRepository(
    private val remote: Repository
) : Repository by remote {
    private var cache: String? = null
    
    override fun getData(): String {
        return cache ?: remote.getData().also { cache = it }
    }
}

// プロパティ委譲
class User {
    // 遅延初期化
    val database by lazy {
        Room.databaseBuilder(context, AppDatabase::class.java, "db").build()
    }
    
    // Observable プロパティ
    var name: String by Delegates.observable("Initial") { prop, old, new ->
        println("$old -> $new")
    }
    
    // カスタムデリゲート
    var token: String by PreferenceDelegate("auth_token", "")
}

class PreferenceDelegate(
    private val key: String,
    private val defaultValue: String
) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return sharedPreferences.getString(key, defaultValue) ?: defaultValue
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        sharedPreferences.edit().putString(key, value).apply()
    }
}

希少性: 普通 難易度: 普通


アーキテクチャパターン (6つの質問)

6. MVVM アーキテクチャとその利点を説明してください。

回答: MVVM (Model-View-ViewModel) は、UI ロジックをビジネスロジックから分離します。

Loading diagram...
  • Model: データ層 (リポジトリ、データソース)
  • View: UI 層 (Activity、Fragment、Composable)
  • ViewModel: プレゼンテーションロジック、構成変更を乗り切る
  • 利点: テスト可能、関心の分離、ライフサイクルを認識
// Model
data class User(val id: Int, val name: String, val email: String)

// Repository
class UserRepository(
    private val api: ApiService,
    private val dao: UserDao
) {
    suspend fun getUser(id: Int): User {
        return dao.getUser(id) ?: api.fetchUser(id).also {
            dao.insertUser(it)
        }
    }
}

// ViewModel
class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {
    private val _uiState = MutableStateFlow<UiState<User>>(UiState.Loading)
    val uiState: StateFlow<UiState<User>> = _uiState.asStateFlow()
    
    fun loadUser(id: Int) {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val user = repository.getUser(id)
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "Error")
            }
        }
    }
}

// View (Fragment)
class UserFragment : Fragment() {
    private val viewModel: UserViewModel by viewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.uiState.collect { state ->
                when (state) {
                    is UiState.Loading -> showLoading()
                    is UiState.Success -> showUser(state.data)
                    is UiState.Error -> showError(state.message)
                }
            }
        }
    }
}

希少性: 非常に一般的 難易度: 普通


7. クリーンアーキテクチャとは何ですか?また、Android でどのように実装しますか?

回答: クリーンアーキテクチャは、コードを明確な依存関係を持つレイヤーに分離します。

Loading diagram...
  • Presentation: UI、ViewModels
  • Domain: ユースケース、ビジネスロジック、エンティティ
  • Data: リポジトリ、データソース (API、データベース)
  • 依存性ルール: 内側のレイヤーは外側のレイヤーについて知りません
// Domain Layer - Entities
data class User(val id: Int, val name: String, val email: String)

// Domain Layer - Repository Interface
interface UserRepository {
    suspend fun getUser(id: Int): Result<User>
}

// Domain Layer - Use Case
class GetUserUseCase(private val repository: UserRepository) {
    suspend operator fun invoke(id: Int): Result<User> {
        return repository.getUser(id)
    }
}

// Data Layer - Repository Implementation
class UserRepositoryImpl(
    private val remoteDataSource: UserRemoteDataSource,
    private val localDataSource: UserLocalDataSource
) : UserRepository {
    override suspend fun getUser(id: Int): Result<User> {
        return try {
            val localUser = localDataSource.getUser(id)
            if (localUser != null) {
                Result.success(localUser)
            } else {
                val remoteUser = remoteDataSource.fetchUser(id)
                localDataSource.saveUser(remoteUser)
                Result.success(remoteUser)
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

// Presentation Layer - ViewModel
class UserViewModel(
    private val getUserUseCase: GetUserUseCase
) : ViewModel() {
    fun loadUser(id: Int) {
        viewModelScope.launch {
            when (val result = getUserUseCase(id)) {
                is Result.Success -> handleSuccess(result.data)
                is Result.Failure -> handleError(result.exception)
            }
        }
    }
}

希少性: 一般的 難易度: 難しい


8. 依存性注入と Dagger/Hilt について説明してください。

回答: 依存性注入は、クラス内で依存関係を作成する代わりに、クラスに依存関係を提供します。

  • 利点: テスト可能性、疎結合、再利用性
  • Dagger: コンパイル時の DI フレームワーク
  • Hilt: Android 向けの簡略化された Dagger
// Hilt で依存関係を定義
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    
    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }
}

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(context, AppDatabase::class.java, "app_db")
            .build()
    }
    
    @Provides
    fun provideUserDao(database: AppDatabase): UserDao {
        return database.userDao()
    }
}

// 注入された依存関係を持つリポジトリ
@Singleton
class UserRepository @Inject constructor(
    private val apiService: ApiService,
    private val userDao: UserDao
) {
    suspend fun getUser(id: Int): User {
        return userDao.getUser(id) ?: apiService.fetchUser(id).also {
            userDao.insertUser(it)
        }
    }
}

// 注入されたリポジトリを持つ ViewModel
@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    // ViewModel ロジック
}

// Activity/Fragment
@AndroidEntryPoint
class UserActivity : AppCompatActivity() {
    private val viewModel: UserViewModel by viewModels()
}

希少性: 非常に一般的 難易度: 難しい


9. リポジトリパターンとは何ですか?また、なぜ使用するのですか?

回答: リポジトリパターンはデータソースを抽象化し、データアクセス用のクリーンな API を提供します。

  • 利点:
    • 信頼できる唯一の情報源
    • 集中化されたデータロジック
    • データソースの切り替えが簡単
    • テスト可能
  • 実装: 複数のデータソース間の調整
interface UserRepository {
    suspend fun getUsers(): Flow<List<User>>
    suspend fun getUser(id: Int): User?
    suspend fun saveUser(user: User)
    suspend fun deleteUser(id: Int)
}

class UserRepositoryImpl @Inject constructor(
    private val remoteDataSource: UserRemoteDataSource,
    private val localDataSource: UserLocalDataSource,
    private val cacheDataSource: UserCacheDataSource
) : UserRepository {
    
    override suspend fun getUsers(): Flow<List<User>> = flow {
        // 最初にキャッシュされたデータを放出
        val cachedUsers = cacheDataSource.getUsers()
        if (cachedUsers.isNotEmpty()) {
            emit(cachedUsers)
        }
        
        // ローカルデータベースからフェッチ
        val localUsers = localDataSource.getUsers()
        if (localUsers.isNotEmpty()) {
            emit(localUsers)
            cacheDataSource.saveUsers(localUsers)
        }
        
        // リモートからフェッチ
        try {
            val remoteUsers = remoteDataSource.fetchUsers()
            localDataSource.saveUsers(remoteUsers)
            cacheDataSource.saveUsers(remoteUsers)
            emit(remoteUsers)
        } catch (e: Exception) {
            // リモートが失敗した場合、キャッシュ/ローカルデータはすでに放出されています
        }
    }
    
    override suspend fun getUser(id: Int): User? {
        return cacheDataSource.getUser(id)
            ?: localDataSource.getUser(id)
            ?: remoteDataSource.fetchUser(id)?.also {
                localDataSource.saveUser(it)
                cacheDataSource.saveUser(it)
            }
    }
}

希少性: 非常に一般的 難易度: 普通


10. シングルアクティビティアーキテクチャについて説明してください。

回答: シングルアクティビティアーキテクチャは、ナビゲーションコンポーネントによって管理される複数のフラグメントを持つ 1 つのアクティビティを使用します。

  • 利点:
    • 簡略化されたナビゲーション
    • フラグメント間の共有 ViewModel
    • より優れたアニメーション
    • より簡単なディープリンク
  • ナビゲーションコンポーネント: フラグメントトランザクション、バックスタック、引数を処理します
// ナビゲーショングラフ (nav_graph.xml)
<navigation>
    <fragment
        android:id="@+id/homeFragment"
        android:name="com.app.HomeFragment">
        <action
            android:id="@+id/action_home_to_detail"
            app:destination="@id/detailFragment" />
    </fragment>
    
    <fragment
        android:id="@+id/detailFragment"
        android:name="com.app.DetailFragment">
        <argument
            android:name="userId"
            app:argType="integer" />
    </fragment>
</navigation>

// MainActivity - シングルアクティビティ
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val navController = findNavController(R.id.nav_host_fragment)
        setupActionBarWithNavController(navController)
    }
}

// HomeFragment - 詳細にナビゲート
class HomeFragment : Fragment() {
    fun navigateToDetail(userId: Int) {
        val action = HomeFragmentDirections.actionHomeToDetail(userId)
        findNavController().navigate(action)
    }
}

// DetailFragment - 引数を受け取る
class DetailFragment : Fragment() {
    private val args: DetailFragmentArgs by navArgs()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val userId = args.userId
        viewModel.loadUser(userId)
    }
}

希少性: 一般的 難易度: 普通


11. MVI (Model-View-Intent) アーキテクチャとは何ですか?

回答: MVI は、Redux に触発された単方向データフローアーキテクチャです。

  • コンポーネント:
    • Model: UI 状態を表します
    • View: 状態をレンダリングし、インテントを放出します
    • Intent: ユーザーアクション/イベント
  • 利点: 予測可能な状態、簡単なデバッグ、タイムトラベルデバッグ
// State
data class UserScreenState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val error: String? = null
)

// Intent (ユーザーアクション)
sealed class UserIntent {
    data class LoadUser(val id: Int) : UserIntent()
    object RetryLoading : UserIntent()
}

// ViewModel
class UserViewModel : ViewModel() {
    private val _state = MutableStateFlow(UserScreenState())
    val state: StateFlow<UserScreenState> = _state.asStateFlow()
    
    fun processIntent(intent: UserIntent) {
        when (intent) {
            is UserIntent.LoadUser -> loadUser(intent.id)
            is UserIntent.RetryLoading -> retryLoading()
        }
    }
    
    private fun loadUser(id: Int) {
        viewModelScope.launch {
            _state.value = _state.value.copy(isLoading = true, error = null)
            try {
                val user = repository.getUser(id)
                _state.value = _state.value.copy(
                    isLoading = false,
                    user = user
                )
            } catch (e: Exception) {
                _state.value = _state.value.copy(
                    isLoading = false,
                    error = e.message
                )
            }
        }
    }
}

// View
class UserFragment : Fragment() {
    private val viewModel: UserViewModel by viewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // インテントを送信
        viewModel.processIntent(UserIntent.LoadUser(123))
        
        // 状態を監視
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.state.collect { state ->
                render(state)
            }
        }
    }
    
    private fun render(state: UserScreenState) {
        when {
            state.isLoading -> showLoading()
            state.error != null -> showError(state.error)
            state.user != null -> showUser(state.user)
        }
    }
}

希少性: 普通 難易度: 難しい


パフォーマンスと最適化 (5つの質問)

12. RecyclerView のパフォーマンスを最適化するにはどうすればよいですか?

回答: 複数の戦略により、RecyclerView のスクロールパフォーマンスが向上します。

  1. ViewHolder パターン: ビューを再利用します (組み込み)
  2. DiffUtil: 効率的なリスト更新
  3. 安定した ID: getItemId() をオーバーライドし、setHasStableIds(true) を設定します
  4. プリフェッチ: プリフェッチ距離を増やします
  5. 画像の読み込み: Glide/Coil などのライブラリを適切なサイズで使用します
  6. 重い操作の回避: onBindViewHolder でコストのかかる計算を実行しないでください
  7. ネストされた RecyclerView: setRecycledViewPool()setHasFixedSize(true) を設定します
class UserAdapter : ListAdapter<User, UserViewHolder>(UserDiffCallback()) {
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val binding = ItemUserBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return UserViewHolder(binding)
    }
    
    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
    
    override fun getItemId(position: Int): Long {
        return getItem(position).id.toLong()
    }
}

class UserDiffCallback : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem.id == newItem.id
    }
    
    override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem == newItem
    }
}

// Fragment/Activity での使用例
recyclerView.apply {
    setHasFixedSize(true)
    adapter = userAdapter
    // プリフェッチを増やす
    (layoutManager as? LinearLayoutManager)?.initialPrefetchItemCount = 4
}

// リストを効率的に更新
userAdapter.submitList(newUsers)

希少性: 非常に一般的 難易度: 普通


13. Android でメモリリークを検出して修正するにはどうすればよいですか?

回答: メモリリークは、オブジェクトが必要以上に長くメモリに保持されている場合に発生します。

  • 一般的な原因:
    • コンテキストリーク (Activity/Fragment 参照)
    • 静的参照
    • 匿名内部クラス
    • 登録解除されていないリスナー
    • キャンセルされていないコルーチン
  • 検出ツール:
    • LeakCanary ライブラリ
    • Android Studio Memory Profiler
    • ヒープダンプ
// BAD - メモリリーク
class MyActivity : AppCompatActivity() {
    companion object {
        var instance: MyActivity? = null  // 静的参照がリークします
    }
    
    private val handler = Handler()  // Activity への暗黙的な参照
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        instance = this
        
        // 匿名クラスは Activity 参照を保持します
        handler.postDelayed({
            // Activity が破棄される可能性があります
        }, 10000)
    }
}

// GOOD - メモリリークなし
class MyActivity : AppCompatActivity() {
    private val handler = Handler(Looper.getMainLooper())
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 必要に応じて弱い参照を使用します
        val weakRef = WeakReference(this)
        handler.postDelayed({
            weakRef.get()?.let { activity ->
                // Activity を安全に使用できます
            }
        }, 10000)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)  // クリーンアップ
    }
}

// ViewModel - 構成変更を乗り切る
class MyViewModel : ViewModel() {
    private val job = Job()
    private val scope = CoroutineScope(Dispatchers.Main + job)
    
    override fun onCleared() {
        super.onCleared()
        job.cancel()  // コルーチンをキャンセル
    }
}

希少性: 非常に一般的 難易度: 普通


14. アプリの起動時間を最適化するにはどうすればよいですか?

回答: 起動時間の短縮はユーザーエクスペリエンスを向上させます。

  1. 遅延初期化: オブジェクトが必要な場合にのみ初期化します
  2. Application.onCreate() での重い処理の回避:
    • バックグラウンドスレッドに移動
    • 重要でない初期化を延期
  3. コンテンツプロバイダー: 最小化または遅延読み込み
  4. 依存関係の削減: ライブラリが少ないほど、起動が速くなります
  5. App Startup Library: 構造化された初期化
  6. Baseline Profiles: 事前コンパイルのヒント
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // 重要な初期化のみ
        initCrashReporting()
        
        // 重要でない処理を延期
        lifecycleScope.launch {
            delay(100)
            initAnalytics()
            initImageLoader()
        }
        
        // バックグラウンド初期化
        CoroutineScope(Dispatchers.IO).launch {
            preloadData()
        }
    }
}

// App Startup library
class AnalyticsInitializer : Initializer<Analytics> {
    override fun create(context: Context): Analytics {
        return Analytics.getInstance(context).apply {
            initialize()
        }
    }
    
    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}

// 遅延初期化
class MyActivity : AppCompatActivity() {
    private val database by lazy {
        Room.databaseBuilder(this, AppDatabase::class.java, "db").build()
    }
    
    // アクセスされたときにのみ作成されます
    private val heavyObject by lazy {
        createHeavyObject()
    }
}

希少性: 一般的 難易度: 普通


15. ビットマップの読み込みとキャッシュを効率的に処理するにはどうすればよいですか?

回答: 効率的な画像処理は、パフォーマンスにとって非常に重要です。

  • ライブラリ: Glide、Coil (自動的にキャッシュを処理)
  • 手動最適化:
    • ダウンサンプリング (より小さな画像を読み込む)
    • メモリキャッシュ (LruCache)
    • ディスクキャッシュ
    • ビットマッププーリング
// Glide の使用
Glide.with(context)
    .load(imageUrl)
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .override(width, height)  // サイズ変更
    .into(imageView)

// LruCache を使用した手動実装
class ImageCache {
    private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
    private val cacheSize = maxMemory / 8
    
    private val memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
        override fun sizeOf(key: String, bitmap: Bitmap): Int {
            return bitmap.byteCount / 1024
        }
    }
    
    fun getBitmap(key: String): Bitmap? {
        return memoryCache.get(key)
    }
    
    fun putBitmap(key: String, bitmap: Bitmap) {
        if (getBitmap(key) == null) {
            memoryCache.put(key, bitmap)
        }
    }
}

// 大きな画像をダウンサンプリング
fun decodeSampledBitmap(file: File, reqWidth: Int, reqHeight: Int): Bitmap {
    return BitmapFactory.Options().run {
        inJustDecodeBounds = true
        BitmapFactory.decodeFile(file.path, this)
        
        inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
        inJustDecodeBounds = false
        
        BitmapFactory.decodeFile(file.path, this)
    }
}

fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    val (height: Int, width: Int) = options.run { outHeight to outWidth }
    var inSampleSize = 1
    
    if (height > reqHeight || width > reqWidth) {
        val halfHeight: Int = height / 2
        val halfWidth: Int = width / 2
        
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }
    
    return inSampleSize
}

希少性: 一般的 難易度: 難しい


16. ANR とは何ですか?また、どのように防止しますか?

回答: ANR (Application Not Responding) は、メインスレッドが長時間ブロックされている場合に発生します。

  • 原因:
    • メインスレッドでの重い計算
    • メインスレッドでのネットワーク呼び出し
    • メインスレッドでのデータベース操作
    • デッドロック
  • 防止:
    • 重い処理をバックグラウンドスレッドに移動
    • 適切なディスパッチャを持つコルーチンを使用
    • メインスレッドでの synchronized ブロックの回避
    • バックグラウンドタスクに WorkManager を使用
// BAD - メインスレッドをブロック
class BadActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // ANR のリスク!
        val data = fetchDataFromNetwork()  // UI スレッドをブロック
        processLargeFile()  // UI スレッドをブロック
    }
}

// GOOD - バックグラウンド処理
class GoodActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        viewModel.loadData()  // ViewModel でコルーチンを使用して処理
    }
}

class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            // 自動的に Main ディスパッチャに
            showLoading()
            
            // ネットワーク用に IO ディスパッチャに切り替え
            val data = withContext(Dispatchers.IO) {
                fetchDataFromNetwork()
            }
            
            // 重い計算用に Default ディスパッチャに切り替え
            val processed = withContext(Dispatchers.Default) {
                processData(data)
            }
            
            // UI 更新のために Main に戻る
            updateUI(processed)
        }
    }
}

希少性: 一般的 難易度: 簡単


テスト (3つの質問)

17. ViewModels の単体テストはどのように記述しますか?

回答: ViewModels は、

Newsletter subscription

実際に機能する週次のキャリアのヒント

最新の洞察をメールボックスに直接お届けします

採用率を60%向上させる履歴書を作成

数分で、6倍の面接を獲得することが証明された、ATS対応のカスタマイズされた履歴書を作成します。

より良い履歴書を作成

この投稿を共有

50%速く採用される

プロフェッショナルなAI強化履歴書を使用する求職者は、標準的な10週間に比べて平均5週間で職を得ています。待つのをやめて、面接を始めましょう。