build.gradle파일에 Googlemap library를 추가한다.
dependencies {
...
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.google.android.gms:play-services-location:21.0.1'
}
AndriodManifest.xml 에 permission과 google-map API키를 추가
<?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.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
...
>
<uses-library android:name="org.apache.http.legacy" android:required="true"/>
<meta-data android:name="com.google.android.maps.v2.API_KEY"
android:value="== Google Cloud에서 생성한 API키 입력!! ==="/>
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<activity
...
</activity>
</application>
</manifest>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.google.android.gms.maps.SupportMapFragment"/>메인 액티비티에서 OnMapReadyCallback 상속받고 onMapReady 오버라이드 받아서 사용
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mGoogleMap: GoogleMap
//var googleMap: GoogleMap? = null
//위치 서비스가 gps를 사용해서 위치를 확인
lateinit var fusedLocationClient: FusedLocationProviderClient
//위치 값 요청에 대한 갱신 정보를 받는 변수
lateinit var locationCallback: LocationCallback
lateinit var locationPermission: ActivityResultLauncher<Array<String>>
//지도를 출력하는 뷰 객체 연결하기
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
locationPermission = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()){ results ->
if(results.all{it.value}){
(supportFragmentManager.findFragmentById(R.id.mapView) as SupportMapFragment)!!.getMapAsync(this)
}else{ //문제가 발생했을 때
Toast.makeText(this,"권한 승인이 필요합니다.",Toast.LENGTH_LONG).show()
}
}
//권한 요청
locationPermission.launch(
arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
}
// 지도 객체를 이용할 수 있는 상황이 될 때
override fun onMapReady(p0: GoogleMap) {
val seoul = LatLng(37.566610, 126.978403)
mGoogleMap = p0
mGoogleMap.mapType = GoogleMap.MAP_TYPE_NORMAL // default 노말 생략 가능
mGoogleMap.apply {
//마커 표시하기
val markerOptions = MarkerOptions()
markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE))
markerOptions.position(seoul)
markerOptions.title("서울시청")
markerOptions.snippet("Tel:01-120")
addMarker(markerOptions)
}
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
updateLocation()
}
fun updateLocation(){
//위치 요청하기
val locationRequest = LocationRequest.create().apply {
interval = 1000
fastestInterval = 500
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
locationCallback = object : LocationCallback(){
//1초에 한번씩 변경된 위치 정보가 onLocationResult 으로 전달된다.
override fun onLocationResult(locationResult: LocationResult) {
locationResult?.let{
for (location in it.locations){
Log.d("위치정보", "위도: ${location.latitude} 경도: ${location.longitude}")
setLastLocation(location) //계속 실시간으로 위치를 받아오고 있기 때문에 맵을 확대해도 다시 줄어든다.
}
}
}
}
//권한 처리
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback,
Looper.myLooper()!!
)
}
//지도의 중심 이동하기
fun setLastLocation(lastLocation: Location){
val LATLNG = LatLng(lastLocation.latitude,lastLocation.longitude)
//현재위치 마커로 표시
val makerOptions = MarkerOptions().position(LATLNG).title("현재위치")
val cameraPosition = CameraPosition.Builder().target(LATLNG).zoom(15.0f).build()
mGoogleMap.addMarker(makerOptions)
mGoogleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
}
}
onCreate에서 지도를 출력하는 뷰 객체와 연결한다.updateLocation 에서 LocationRequest.create()와 LocationCallback()으로 위치를 요청한다.setLastLocation에서 지도의 중심을 이동하고, 현재위치를 마커를 추가하여 표시해준다.MarkerOptions().position()을 사용하여 마커를 설정할 수 있다. 이외에 title, icon, snippet 등을 설정할 수 있다. .addMarker()를 사용하여 지도상에 표시한다.REST API같은 웹서비스 통신 방식을 사용하기 위해 HTTP/HTTPS(웹 기반) 프로토콜을 사용한다.API(Application Programming Interface)[RESTful API] REST API (Representational State Transfer)GET(리소스조회), POST(생성), PUT(수정), DELETE(삭제)JSON : JavaScript Object Notation, 데이터를 저장하거나 전송할 때 많이 사용되는 경량의 DATA 교환 형식이다.GSON : Google에서 제공하는 오픈소스 라이브러리, Java와 Kotlin에서 주로 사용된다. '직렬화(Serialization)'와 '역직렬화(Deserialization)' 작업을 간단하게 할 수 있도록 도와준다. (= 간결성과 효율성이 높아진다.)REST API의 HTTP 요청을 자바 인터페이스로 변환하는 것을 주 목적으로 한다.http통신을 할 때에는 인터넷 사용권한을 AndroidManifest.xml에 추가해줘야한다.
<uses-permission android:name="android.permission.INTERNET"/>
build.gradle에 라이브러리를 추가해줘야한다.
dependencies {
...
implementation 'com.squareup.retrofit2:retrofit:2.x.x'
implementation 'com.squareup.retrofit2:converter-gson:2.x.x' // Gson 컨버터 추가
...
}
[NetWorkInterface.kt] : API 인터페이스를 정의해줘야한다.
interface ApiService {
@GET("users/{id}")
fun getUser(@Path("id") id: Int): Call<User>
}
//예시코드 @GET 안에 유저아이디는 요청주소의 세부옵션 값을 넣는다.
interface NetWorkInterface {
@GET("getCtprvnRltmMesureDnsty") //시도별 실시간 측정정보 조회 주소
suspend fun getDust(@QueryMap param: HashMap<String, String>): Dust
}
[NetWorkClient.kt] : Retrofit 인스턴스를 생성한다.
->보통 이 포맷을 그대로 사용한다.
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService = retrofit.create(ApiService::class.java)
//예시코드
object NetWorkClient {
//서비스URL 마지막에 /까지 붙여주기
private const val DUST_BASE_URL = "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/"
private fun createOkHttpClient(): OkHttpClient {
val interceptor = HttpLoggingInterceptor()
if (BuildConfig.DEBUG)
interceptor.level = HttpLoggingInterceptor.Level.BODY
else
interceptor.level = HttpLoggingInterceptor.Level.NONE
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.addNetworkInterceptor(interceptor)
.build()
}
private val dustRetrofit = Retrofit.Builder()
.baseUrl(DUST_BASE_URL).addConverterFactory(GsonConverterFactory.create()).client(
createOkHttpClient()
).build()
val dustNetWork: NetWorkInterface = dustRetrofit.create(NetWorkInterface::class.java)
}
@SerializedName("서버에서 변수명") val 앱내변수명:자료형 형태를 사용하여 변경할 수 있다.동기식 요청
//NetWorkClient에 apiService 설정.
val response: Response<User> = apiService.getUser(id).execute()
비동기식 요청 : 콜백 사용
//Call 인터페이스의 enqueue을 통해서도 비동기 구현 가능.
apiService.getUser(id).enqueue(object: Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
// 처리
}
override fun onFailure(call: Call<User>, t: Throwable) {
// 오류 처리
}
})
응답 객체 사용 : Response 객체를 통해 HTTP 응답의 여러 정보에 접근
if (response.isSuccessful) {
val user: User? = response.body()
} else {
// 오류 메시지 처리
val error: String = response.errorBody()?.string() ?: "Unknown error"
}
오류 처리하기 (onFailure 콜백) : 네트워크 오류나 데이터 변환 오류 등에서 호출
override fun onFailure(call: Call<User>, t: Throwable) {
// 오류 메시지 표시
Log.e("API_ERROR", t.message ?: "Unknown error")
}
http통신,데이터베이스연결같은 것들은 UI 메인스레드에서 불가능하다.
메인스레드에서 실행해버리면 통신하고서 통신쪽에서 렉이걸린다거나 딜레이가 생기면 그동안 메인UI가 멈춰버린채로 있기 때문이다.
코루틴에서는 UI쓰레드를 바로 건드릴 수 없기 때문에, 이를 위해선 runOnUiThread{ } 를 사용해줘야한다.