스트링 디코드 문제에서 망하고, 43분 후 복수에 성공한 이야기 (feat. 중첩 괄호도 처리함)

## 🔧 배경 얼마 전 기술 면접을 보다가 `decodeString` 문제를 받았습니다. 알고리즘 공부는 주로 BFS, DFS 쪽만 하던 터라, 문자열 파싱 문제에서 완전히 발목 잡혔습니다. 거기다 라이브 코딩. 제한 시간 30분. 당연히… 못 풀었죠. 멘탈까지 디코딩당한 날이었습니다. --- ## 🔥 복수의 시작 면접이 끝난 뒤에도 이 문제는 계속 머릿속을 맴돌았습니다. “내가 이걸 못 풀었다고? 진짜?” 그 순간부터 **마음속의 디버거**가 켜졌고, 라이브 코딩에서 시도했던 방식에서 출발해 다시 파기 시작했습니다. 스택도 쓰지 않았습니다. 재귀도 쓰지 않았습니다. 그냥 제 방식대로, 끈질기게 문자열을 조작하면서 풀었습니다. **걸린 시간: 추가 43분.** --- ## 🧠 접근 방식 요약 - `[`와 `]`를 찾는다. 가장 안쪽 괄호부터 시작. - 그 안의 문자열을 꺼내고, 반복 횟수를 계산한다. - 원래 문자열 배열을 `splice`로 조작해서 새로운 문자열을 삽입한다. - 그리고… **처음부터 다시 돌린다.** - 이걸 괄호가 다 없어질 때까지 반복한다. 사실상 **재귀를 흉내 내는 루프 기반 수동 파서**입니다. --- ## ✅ 최종 코드 (with 주석) ```javascript /** * @param {string} s * @return {string} */ var decodeString = function(s) { const strArr = s.split(''); let startIndex = -1; let endIndex = -1; for(let i = 0; i = 0 && endIndex >= 0 && endIndex > startIndex){ const addedStr = strArr.slice(startIndex + 1, endIndex).join(''); ...

react native gps foreground service - android, kotlin (1)


react native 에서 gps 트래킹을 해야한다. 앱이 백그라운드에 있어도 동작하도록

네이티브 모듈을 만드는 것만 해도 많이 안하다보니 까다로운데 gps + 서비스 동작을 해야한다니.

기본으로 돌아가기로 했다. 일단 안드로이드 앱에서 GPS 트래킹하는 코드부터 시도해보았다.

### MainActivity.kt

```
package com.example.gpstracker

import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.core.app.ActivityCompat
import com.example.gpstracker.ui.theme.GpsTrackerTheme

val TAG = "gpstracker"

class MainActivity : ComponentActivity() {
    private lateinit var locationManager: LocationManager
    private lateinit var locationListener: LocationListener

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


        locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
        locationListener = MyLocationListener()

        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
            ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), 1)
            Log.d(TAG, "request denied")
            return
        }

        setContent {
            GpsTrackerTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Column {
                        StartButton(locationManager, locationListener)
                        EndButton(locationManager, locationListener)
                    }
                }
            }
        }
    }
}


@SuppressLint("MissingPermission")
@Composable
fun StartButton(locationManager: LocationManager, locationListener: LocationListener) {
    Button(onClick = {
        Log.d(TAG,"start")
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, locationListener)
        locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0f, locationListener)
    }) {
        Text(text = "start")
    }
}
@Composable
fun EndButton(locationManager: LocationManager, locationListener: LocationListener) {
    Button(onClick = {
        Log.d(TAG,"end")
        locationManager.removeUpdates(locationListener)
    }) {
        Text(text="end")
    }
}

```

### MyLocationListener
```
package com.example.gpstracker

import android.location.Location
import android.location.LocationListener
import android.util.Log

class MyLocationListener : LocationListener{
    override fun onLocationChanged(p0: Location) {
        val latitude = p0.latitude
        val longitude = p0.longitude
        Log.d(TAG, "Latitude: $latitude, Longitude: $longitude")
    }
}
```
잘 돌아간다. 

펴미션은 아래 두가지를 추가해준다

```
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
```

이제 1초에 한번씩 아무 로그나 찍는 서비스를 구현해본다

```
package com.example.gpstracker

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import java.util.Timer
import java.util.TimerTask

class MyForegroundService: Service() {
    private val CHANNEL_ID = "ForegroundServiceChannel"
    private var timer: Timer? = null

    override fun onCreate() {
        super.onCreate()

        Log.d(TAG, "service create")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d(TAG, "onStartCommand")

        createNotificationChannel()

        val notificationIntent = Intent(this, MainActivity::class.java)

        val pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent,
            PendingIntent.FLAG_IMMUTABLE)

        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Foreground Service")
            .setContentText("Logging every second")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentIntent(pendingIntent)
            .build()

        startForeground(1, notification)

        startLogging()

        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        stopLogging()
        Log.d(TAG, "onDestroy")
    }

    override fun onBind(p0: Intent?): IBinder? {
        return null
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val serviceChannel = NotificationChannel(
                CHANNEL_ID,
                "Foreground Service Channel",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            val manager = getSystemService(NotificationManager::class.java)
            manager.createNotificationChannel(serviceChannel)
        }
    }

    private fun startLogging() {
        timer = Timer()
        timer?.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                Log.d(TAG, "Logging...")
            }
        }, 0, 1000) // Log every 1 second
    }

    private fun stopLogging() {
        timer?.cancel()
    }
}
```

퍼미션과 서비스가 추가되어야 한다
```
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<service
	android:name=".MyForegroundService"
	android:foregroundServiceType="location"
	android:exported="false"/>
```

이제 위의 두가지를 합쳐야 한다.

단순히 `onStartCommand` 에서 `locationManager` 에 위치 요청을 하니 포그라운드 서비스에서 잘 돌았다.

```
@SuppressLint("MissingPermission")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    Log.d(TAG, "onStartCommand")

    createNotificationChannel()

    val notificationIntent = Intent(this, MainActivity::class.java)

    val pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent,
        PendingIntent.FLAG_IMMUTABLE)

    val notification = NotificationCompat.Builder(this, CHANNEL_ID)
        .setContentTitle("Foreground Service")
        .setContentText("Logging every second")
        .setSmallIcon(R.drawable.ic_launcher_foreground)
        .setContentIntent(pendingIntent)
        .build()

    startForeground(1, notification)

    startLogging()

    locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, locationListener)
    locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0f, locationListener)

    return START_STICKY
}

override fun onDestroy() {
    super.onDestroy()
    stopLogging()

    locationManager.removeUpdates(locationListener)

    Log.d(TAG, "onDestroy")
}
```

### 전체 코드 github
`https://github.com/qja0707/Kotlin-GPS-ForegroundService`



### what is double colon?
참조를 위해 사용한다고 하는데 정확하게 이해하기 위해선 더 깊게 봐야할 것 같다...

### What is PendingIntent
서비스 notification channel 을 사용자가 눌렀을 때 작동할 정보. 보통의 안드로이드 앱이라면 액티비티 정보를 넣는 것으로 충분하겠지만...

### What does onStartCommand return integer means? (START_STICKY etc...)
서비스가 어떤식으로 동작해야하는지 명시해주는 Integer이다. 앱이 꺼졌을 때 서비스의 라이프사이클에 관련된 값이라고 생각된다.

Comments

Popular posts from this blog

Operating System Concepts 9th

스티키 헤더 여러개 만들기 (Multiple sticky header)

Operating System Concept