구글 맵 SDK 를 이용하여 구글 맵으로 내 위치를 표시하는 기능을 구현하여 보겠습니다.
buildscript
{
repositories
{
google()
mavenCentral()
}
dependencies
{
classpath "com.android.tools.build:gradle:7.0.3"
classpath 'com.google.gms:google-services:4.3.10'
}
}
task clean(type: Delete)
{
delete rootProject.buildDir
}
[ build.gradle (Project: 프로젝트명) ] → [ classpath 'com.google.gms:google-services:4.3.10' ] 추가
plugins
{
id 'com.android.application'
}
android
{
compileSdk 31
defaultConfig
{
applicationId "com.sample.google.map"
minSdk 23
targetSdk 31
versionCode 1
versionName "1.0"
}
buildTypes
{
release
{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions
{
sourceCompatibility JavaVersion.VERSION_15
targetCompatibility JavaVersion.VERSION_15
}
}
dependencies
{
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.google.android.gms:play-services-location:19.0.0'
implementation 'com.google.android.gms:play-services-maps:18.0.2'
}
[ build.gradle (Module: 프로젝트명) ] →
[ implementation 'com.google.android.gms:play-services-location:19.0.0' ] 추가 →
[ implementation 'com.google.android.gms:play-services-maps:18.0.2' ] 추가
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:orientation="vertical">
<ImageView
android:id="@+id/imageView4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:srcCompat="@drawable/img_google_map" />
</LinearLayout>
[ res ] → [ layout ] → [ layout_goolge.xml ] 저장
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal">
<TextView
android:id="@+id/textView5"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:text="위도 : "
android:textSize="18dp" />
<TextView
android:id="@+id/tv_latitude"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="8"
android:gravity="center|start"
android:hint="현재 위도가 표시됩니다."
android:paddingStart="15dp"
android:textSize="18dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:text="경도 : "
android:textSize="18dp" />
<TextView
android:id="@+id/tv_longitude"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="8"
android:gravity="center|start"
android:hint="현재 경도가 표시됩니다."
android:paddingStart="15dp"
android:textSize="18dp" />
</LinearLayout>
<Button
android:id="@+id/btn_detect_pos"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="위치 감지 시작" />
<Button
android:id="@+id/btn_back_detect_pos"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="백그라운드 위치 감지 시작" />
<fragment
android:id="@+id/gv_map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="600dp"
tools:layout="@layout/layout_goolge" />
</LinearLayout>
[ res ] → [ layout ] → [ activity_main ] 저장
package com.sample.google.map;
import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
public class LocationService extends Service
{
public static final String ACTION_NAME = "com.android.service.LOCATION";
public static Intent intent;
private static LocationRequest locationRequest = null;
private final int LOCATION_SERVICE_ID = 101;
private NotificationManager notificationManager;
private NotificationCompat.Builder builder;
@Nullable
@Override
public IBinder onBind(Intent intent)
{
return null;
}
@Override
public void onCreate()
{
super.onCreate();
if(locationRequest == null)
{
locationRequest = LocationRequest.create();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
LocationService.intent = intent;
if(locationRequest != null)
{
startLocationBackService();
}
return START_STICKY;
}
@Override
public void onDestroy()
{
super.onDestroy();
stopLocationBackService();
}
private final LocationCallback locationCallback = new LocationCallback()
{
@Override
public void onLocationResult(@NonNull LocationResult locationResult)
{
super.onLocationResult(locationResult);
double latitude = locationResult.getLastLocation().getLatitude();
double longitude = locationResult.getLastLocation().getLatitude();
if(notificationManager != null && builder != null)
{
builder.setContentText("위도 : " + latitude + " 경도 : " + longitude);
notificationManager.notify(LOCATION_SERVICE_ID, builder.build());
}
Log.i(this.getClass().getName(), "위도 : " + latitude + " 경도 : " + longitude);
}
};
@SuppressLint("MissingPermission")
private void startLocationBackService()
{
locationRequest.setInterval(1000);
locationRequest.setFastestInterval(1000);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationServices.getFusedLocationProviderClient(this).requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
builder = getDefaultBuilder();
// 백그라운드 서비스를 사용할 경우 사용자에게 알리도록 되어있다.
// startForeground 사용하여 Notification 을 통해 사용자에게 앱이 사용중임을 알려준다.
startForeground(LOCATION_SERVICE_ID, builder.build());
}
private void stopLocationBackService()
{
LocationServices.getFusedLocationProviderClient(this).removeLocationUpdates(locationCallback);
stopForeground(true);
}
private NotificationCompat.Builder getDefaultBuilder()
{
String channelID = "loc_notification_channel";
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// API 26 버전 이상부터 알림 통지를 위해서 알림을 받을 수 있는 채널을 생성하여야 한다.
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
if(notificationManager != null && notificationManager.getNotificationChannel(channelID) == null)
{
// IMPORTANCE_HIGH : 알림의 중요도 설정
NotificationChannel notificationChannel = new NotificationChannel
(
channelID,
"location Notification Channel",
NotificationManager.IMPORTANCE_NONE
);
notificationChannel.setDescription("지도 알림 채널");
notificationChannel.setSound(null, null);
notificationChannel.setShowBadge(false);
notificationChannel.setVibrationPattern(new long[]{0});
notificationChannel.enableVibration(true);
notificationManager.createNotificationChannel(notificationChannel);
}
}
Intent resultIntent = new Intent();
PendingIntent pendingIntent = null;
// FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE : 이미 존재할경우 해당 인텐트로 대체
pendingIntent = PendingIntent.getActivity(getApplicationContext(), 1, resultIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channelID);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle("맵");
builder.setDefaults(NotificationCompat.DEFAULT_SOUND);
builder.setVibrate(new long[]{- 1});
builder.setOnlyAlertOnce(true);
builder.setContentText("맵 정보 호출중입니다.");
builder.setContentIntent(pendingIntent);
builder.setAutoCancel(false);
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
return builder;
}
}
[ com.sample.google.map ] → [ LocationService.java ] 저장
package com.sample.google.map;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Looper;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MainActivity extends AppCompatActivity
{
private final int REQUEST_MAP_ACCESS = 1005;
private Activity activity;
private TextView tv_latitude, tv_longitude;
private Button btn_detect_pos;
private Button btn_back_detect_pos;
private Boolean locDetectStatus = false;
private Boolean locBackDetectStatus = false;
private LocationRequest locationRequest;
private Intent locIntent;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
try
{
setContentView(R.layout.activity_main);
init();
setting();
addListener();
}
catch(Exception ex)
{
Log.e(this.getClass().getName(), ex.getMessage(), ex);
}
}
private void init()
{
activity = this;
tv_latitude = findViewById(R.id.tv_latitude);
tv_longitude = findViewById(R.id.tv_longitude);
btn_detect_pos = findViewById(R.id.btn_detect_pos);
btn_back_detect_pos = findViewById(R.id.btn_back_detect_pos);
}
private void setting()
{
locationRequest = LocationRequest.create();
}
private void addListener()
{
tv_latitude = findViewById(R.id.tv_latitude);
tv_longitude = findViewById(R.id.tv_longitude);
btn_detect_pos.setOnClickListener(listener_detect_pos);
btn_back_detect_pos.setOnClickListener(listener_back_detect_pos);
}
private final View.OnClickListener listener_back_detect_pos = new View.OnClickListener()
{
@Override
public void onClick(View v)
{
// shouldShowRequestPermissionRationale : 사용자가 명시적으로 권한을 거부한 경우 true 가 발생
// ACCESS_BACKGROUND_LOCATION 의 경우 항상 명시적 권한을 거부한 경우로 처리하기 떄문에
// 사용자가 직접 설정에서 변경해주어야 하며 항상 허용 설정이 설정하여야 한다.
if(ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_BACKGROUND_LOCATION))
{
requestUserPermission("백그라운드 위치 확인을 위해\n사용자가 직접 위치에 대한 권한을\n항상 허용으로 설정할 필요가 있습니다.\n변경화면으로 이동하시겠습니까?");
}
else
{
checkLocationIntent();
locBackDetectStatus = ! locBackDetectStatus;
if(locBackDetectStatus)
{
startService(locIntent);
btn_back_detect_pos.setText("백그라운드 위치 감지 중지");
}
else
{
stopService(locIntent);
btn_back_detect_pos.setText("백그라운드 위치 감지 시작");
}
}
}
};
private final LocationCallback locationCallback = new LocationCallback()
{
@Override
public void onLocationResult(@NonNull LocationResult locationResult)
{
super.onLocationResult(locationResult);
double latitude = locationResult.getLastLocation().getLatitude();
double longitude = locationResult.getLastLocation().getLongitude();
tv_latitude.setText(String.valueOf(latitude));
tv_longitude.setText(String.valueOf(longitude));
SupportMapFragment supportMapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.gv_map);
if(supportMapFragment != null)
{
supportMapFragment.getMapAsync(new OnMapReadyCallback()
{
@Override
public void onMapReady(@NonNull GoogleMap googleMap)
{
LatLng myPosition = new LatLng(latitude, longitude);
googleMap.addMarker(new MarkerOptions().position(myPosition).title("나의 위치"));
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(myPosition, 16));
}
});
}
}
};
private final View.OnClickListener listener_detect_pos = new View.OnClickListener()
{
@SuppressLint("MissingPermission")
@Override
public void onClick(View v)
{
if(checkMapPermission())
{
locDetectStatus = ! locDetectStatus;
if(locDetectStatus)
{
locationRequest.setInterval(1000);
locationRequest.setFastestInterval(1000);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationServices.getFusedLocationProviderClient(activity)
.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
btn_detect_pos.setText("위치 감지 중지");
Toast.makeText(activity, "위치 감지를 시작합니다.", Toast.LENGTH_SHORT).show();
}
else
{
LocationServices.getFusedLocationProviderClient(activity)
.removeLocationUpdates(locationCallback);
btn_detect_pos.setText("위치 감지 시작");
Toast.makeText(activity, "위치 감지를 중지합니다.", Toast.LENGTH_SHORT).show();
}
}
}
};
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(grantResults.length > 0)
{
if(requestCode == REQUEST_MAP_ACCESS)
{
if
(
grantResults[0] != PackageManager.PERMISSION_GRANTED ||
grantResults[1] != PackageManager.PERMISSION_GRANTED
)
{
requestUserPermission("위치 접근 권한이 거부된 상태입니다.\n해당 기능 권한 취득을 위해 앱 정보\n화면에서 권한 변경이 필요합니다.\n이동하시겠습니까?");
}
}
}
}
private Boolean checkMapPermission()
{
// ACCESS_FINE_LOCATION : 기기의 위치 추정치 데이터 접근 권한 요청
// ACCESS_COARSE_LOCATION : 최대한 정확한 기기의 위치 추정치 데이터 접근 권한 요청
// ACCESS_BACKGROUND_LOCATION : 포그라운드 실행의 경우 문제 없으나 서비스 등을 이용해 백그라운드 에서 위치 접근시 권한 필요(API 31 버전부터)
if
(
checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
)
{
String[] permissions =
{
Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION
};
requestPermissions(permissions, REQUEST_MAP_ACCESS);
}
else
{
return true;
}
return false;
}
private void checkLocationIntent()
{
if(LocationService.intent == null)
{
locIntent = new Intent(LocationService.ACTION_NAME);
locIntent.setPackage(getPackageName());
}
else
{
locIntent = LocationService.intent;
}
}
private void requestUserPermission(String message)
{
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(message);
builder.setPositiveButton("예", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
// 어플리케이션 디테일 화면 이동
Intent appDetail = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName()));
// 카테고리를 디폴트로 지정(시스템 카테고리)
appDetail.addCategory(Intent.CATEGORY_DEFAULT);
// 현재 작업을 백그라운도르 이동시키고 새 작업 실행
appDetail.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(appDetail);
}
});
builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
builder.setCancelable(false);
builder.show();
}
}
[ com.sample.google.map ] → [ MainActivity.java ] 저장
<resources>
<string name="app_name">GoogleMapSample</string>
<string name="google_map_key" templateMergeStrategy="preserve" translatable="false">AIzaSyBJuwo93NhuvUxiag18llbfK80-q8NbHvY</string>
</resources>
[ values ] → [ strings.xml ] API 키 저장
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sample.google.map">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.GoogleMapSample">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_map_key" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".LocationService"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.android.service.LOCATION" />
</intent-filter>
</service>
</application>
</manifest>
[ manifests ] → [ AndroidManifest.xml ] 저장
'Mobile App Development > Android' 카테고리의 다른 글
[ Android ] 안드로이드 버튼 동시 클릭 방지 (0) | 2024.04.08 |
---|---|
[ Android ] 안드로이드 줄 바꿈 처리 (0) | 2024.04.08 |
[ Android ] 안드로이드 기본 환경 설정 (0) | 2022.02.12 |
[ Android ] 안드로이드 스튜디오 깃허브 연동 (0) | 2022.02.07 |
[ Android ] 안드로이드 에뮬레이터 간 통신하기 (0) | 2022.01.21 |