반응형

 

FusedLocationProviderClient를 사용하여 Google Map에 현재 위치를 표시하는 예제입니다.

 

 

2018. 10.2

2019. 7. 19  androidx 관련 내용 추가 

2019. 8. 15  SupportMapFragment로 변경

2020. 9. 12 테스트 및 수정 (  Android Studio 4.0.1, Android 11.0  API 30 )

2021. 5. 11 테스트 및 수정 (  Android Studio 4.1.3, Android 11.0  API 30 )





1. 구현된 내용


2. 실행 과정 설명


3. 프로그램 흐름


4. 소스 코드


5. 관련 포스팅

    5.1. Google Maps Android API 사용 방법 및 예제

    5.2. Places API Web Service를 사용하여 Android Google Map에 현재 위치 주변의 음식점 표시하기

    5.3. GenyMotion 가상머신에 Google Apps설치하여 Google Maps Android API 테스트 하기



1. 구현된 내용

 

현재 구현된 내용은 다음과 같습니다.

 

  • 현재 위치를 지도 상에 마커로 표시해줍니다.
  • 디바이스의 위치 서비스(GPS)가 비활성화 되어 있는 경우 사용자가 활성화하도록 요구합니다.
  • 디바이스의 운영체제 버전이 안드로이드 6.0이상일 경우에는 위치 관련 퍼미션을 런타임에 요구합니다.
  • 현재 위치가 변경되면 계속 카메라가 이동하여 현재 위치를 중심으로 지도를 보여주게 됩니다. 




2. 실행 과정 설명

 

처음 실행하면 지도의 초기위치를 서울로 이동시키고 위치 정보 사용을 위한 퍼미션 허가를 사용자에게 요청합니다.  

Android 6.0 미만 운영체제를 사용하는 안드로이드 디바이스에서는 보이지 않습니다. 

 



처음 실행하면 안드로이드 디바이스가 현재 위치를 찾는데 시간이 걸립니다. 

적절한 조치를 안해주면 아프리카 대륙 옆에 있는 바다가 보입니다.



여러가지 방법이 있지만 저는 서울로 초기위치를 이동시켜주는 방법을 사용했습니다. 

onMapReady() 메소드에서 서울로 카메라를 이동시켜줍니다.

그래서 앱에서 GPS가 사용 못하더라도 서울을 보여주게 됩니다. 

 




다른 방법은 마지막에 안드로이드폰이 위치를 인식했던 정보를 가져오는 방법이 있습니다.

아래 링크를 참고하세요..

https://developer.android.com/training/location/retrieve-current.html 




위치 서비스가 비활성화되어 있는 경우 활성화 여부를 사용자에게 물어봅니다.

 




설정을 선택했다면, 위치 정보 설정이 보입니다. 

활성화시키고 백버튼을 눌러 앱으로 돌아오면  

 




현재 위치에 코드에서 추가한 파란색 마커와 구글맵에서 현재 위치를 표시하는데 사용되는 파란색 동그라미가 표시됩니다.



마커를 터치하면 현재 위치의 주소와 좌표가 표시됩니다.




안드로이드 디바이스를 움직이면 현재 위치가 달라짐에 따라 파란색 마커의 위치가 이동하게 됩니다.



3. 프로그램 흐름

 

앱을 실행시키면 다음 순서대로 메소드들이 실행됩니다.

(런타임 퍼미션, GPS 활성화를 제외한 상태입니다.)

 

2018-10-02 11:35:40.478 15364-15364/? D/googlemap_example: onCreate

2018-10-02 11:35:40.481 15364-15364/? D/googlemap_example: onStart

2018-10-02 11:35:40.483 15364-15364/? D/googlemap_example: onStart : call mFusedLocationClient.requestLocationUpdates

2018-10-02 11:35:40.493 15364-15364/? D/googlemap_example: onMapReady :

2018-10-02 11:35:40.574 15364-15364/? D/googlemap_example: startLocationUpdates : call mFusedLocationClient.requestLocationUpdates




이제 일정시간마다 현재 위치를 업데이트합니다.

 

2018-10-02 11:35:40.644 15364-15364/? D/googlemap_example: onLocationResult : 위도:37.5173504 경도:126.8945132

2018-10-02 11:35:43.604 15364-15364/? D/googlemap_example: onLocationResult : 위도:37.5174425 경도:126.8949426

2018-10-02 11:35:44.230 15364-15364/? D/googlemap_example: onLocationResult : 위도:37.5174422 경도:126.8949434

2018-10-02 11:35:45.244 15364-15364/? D/googlemap_example: onLocationResult : 위도:37.5174414 경도:126.8949452

2018-10-02 11:35:46.258 15364-15364/? D/googlemap_example: onLocationResult : 위도:37.5174395 경도:126.8949457




백버튼을 눌러서 앱종료시 다음 메소드가 실행됩니다.

 

2018-10-02 11:36:59.110 15364-15364/? D/googlemap_example: onStop : call stopLocationUpdates




4. 소스 코드

 

다음 포스팅을 기반으로 수정합니다.  중복되는 내용은 빠져있습니다.

 


Google Maps Android API 사용 방법 및 예제

http://webnautes.tistory.com/647 

  





1. 매네페스트 파일 AndroidManifest.xml<manifest> 태그 하위요소로 <uses-permission> 태그를 사용하여 위치정보 접근을 위한 퍼미션을 추가해줍니다.

 

 

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

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

 



2. buidle.gradle를 수정합니다. Snackbar를 사용하기 위해 com.google.android.material:material를 추가해야 합니다. 

추가한 후, Sync Now를 클릭합니다.

 

implementation 'com.google.android.material:material:1.4.0-alpha02'

 



3.  Snackbar를 사용하기 위해 레이아웃에 ID를 추가해야 합니다. 

 

 

android:id="@+id/layout_main"

 

 

    

4.  MainActivity.java를 다음 내용으로 변경합니다.

 



import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;

import com.google.android.gms.location.FusedLocationProviderClient;
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.location.LocationSettingsRequest;
import com.google.android.gms.maps.CameraUpdate;
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.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.material.snackbar.Snackbar;

import java.io.IOException;
import java.util.List;
import java.util.Locale;


public class MainActivity extends AppCompatActivity
        implements OnMapReadyCallback,
        ActivityCompat.OnRequestPermissionsResultCallback{


    private GoogleMap mMap;
    private Marker currentMarker = null;

    private static final String TAG = "googlemap_example";
    private static final int GPS_ENABLE_REQUEST_CODE = 2001;
    private static final int UPDATE_INTERVAL_MS = 1000;  // 1초
    private static final int FASTEST_UPDATE_INTERVAL_MS = 500; // 0.5초


    // onRequestPermissionsResult에서 수신된 결과에서 ActivityCompat.requestPermissions를 사용한 퍼미션 요청을 구별하기 위해 사용됩니다.
    private static final int PERMISSIONS_REQUEST_CODE = 100;
    boolean needRequest = false;


    // 앱을 실행하기 위해 필요한 퍼미션을 정의합니다.
    String[] REQUIRED_PERMISSIONS  = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};  // 외부 저장소


    Location mCurrentLocatiion;
    LatLng currentPosition;


    private FusedLocationProviderClient mFusedLocationClient;
    private LocationRequest locationRequest;
    private Location location;


    private View mLayout;  // Snackbar 사용하기 위해서는 View가 필요합니다.
    // (참고로 Toast에서는 Context가 필요했습니다.)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        setContentView(R.layout.activity_main);

        mLayout = findViewById(R.id.layout_main);

        locationRequest = new LocationRequest()
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
                .setInterval(UPDATE_INTERVAL_MS)
                .setFastestInterval(FASTEST_UPDATE_INTERVAL_MS);


        LocationSettingsRequest.Builder builder =
                new LocationSettingsRequest.Builder();

        builder.addLocationRequest(locationRequest);


        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);


        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }

    @Override
    public void onMapReady(final GoogleMap googleMap) {
        Log.d(TAG, "onMapReady :");

        mMap = googleMap;

        //런타임 퍼미션 요청 대화상자나 GPS 활성 요청 대화상자 보이기전에
        //지도의 초기위치를 서울로 이동
        setDefaultLocation();



        //런타임 퍼미션 처리
        // 1. 위치 퍼미션을 가지고 있는지 체크합니다.
        int hasFineLocationPermission = ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION);
        int hasCoarseLocationPermission = ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_COARSE_LOCATION);



        if (hasFineLocationPermission == PackageManager.PERMISSION_GRANTED &&
                hasCoarseLocationPermission == PackageManager.PERMISSION_GRANTED   ) {

            // 2. 이미 퍼미션을 가지고 있다면
            // ( 안드로이드 6.0 이하 버전은 런타임 퍼미션이 필요없기 때문에 이미 허용된 걸로 인식합니다.)


            startLocationUpdates(); // 3. 위치 업데이트 시작


        }else {  //2. 퍼미션 요청을 허용한 적이 없다면 퍼미션 요청이 필요합니다. 2가지 경우(3-1, 4-1)가 있습니다.

            // 3-1. 사용자가 퍼미션 거부를 한 적이 있는 경우에는
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, REQUIRED_PERMISSIONS[0])) {

                // 3-2. 요청을 진행하기 전에 사용자가에게 퍼미션이 필요한 이유를 설명해줄 필요가 있습니다.
                Snackbar.make(mLayout, "이 앱을 실행하려면 위치 접근 권한이 필요합니다.",
                        Snackbar.LENGTH_INDEFINITE).setAction("확인", new View.OnClickListener() {

                    @Override
                    public void onClick(View view) {

                        // 3-3. 사용자게에 퍼미션 요청을 합니다. 요청 결과는 onRequestPermissionResult에서 수신됩니다.
                        ActivityCompat.requestPermissions( MainActivity.this, REQUIRED_PERMISSIONS,
                                PERMISSIONS_REQUEST_CODE);
                    }
                }).show();


            } else {
                // 4-1. 사용자가 퍼미션 거부를 한 적이 없는 경우에는 퍼미션 요청을 바로 합니다.
                // 요청 결과는 onRequestPermissionResult에서 수신됩니다.
                ActivityCompat.requestPermissions( this, REQUIRED_PERMISSIONS,
                        PERMISSIONS_REQUEST_CODE);
            }

        }



        mMap.getUiSettings().setMyLocationButtonEnabled(true);
        // 현재 오동작을 해서 주석처리

        //mMap.animateCamera(CameraUpdateFactory.zoomTo(15));
        mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {

            @Override
            public void onMapClick(LatLng latLng) {

                Log.d( TAG, "onMapClick :");
            }
        });
    }

    LocationCallback locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);

            List<Location> locationList = locationResult.getLocations();

            if (locationList.size() > 0) {
                location = locationList.get(locationList.size() - 1);
                //location = locationList.get(0);

                currentPosition
                        = new LatLng(location.getLatitude(), location.getLongitude());


                String markerTitle = getCurrentAddress(currentPosition);
                String markerSnippet = "위도:" + String.valueOf(location.getLatitude())
                        + " 경도:" + String.valueOf(location.getLongitude());

                Log.d(TAG, "onLocationResult : " + markerSnippet);


                //현재 위치에 마커 생성하고 이동
                setCurrentLocation(location, markerTitle, markerSnippet);

                mCurrentLocatiion = location;
            }


        }

    };



    private void startLocationUpdates() {

        if (!checkLocationServicesStatus()) {

            Log.d(TAG, "startLocationUpdates : call showDialogForLocationServiceSetting");
            showDialogForLocationServiceSetting();
        }else {

            int hasFineLocationPermission = ContextCompat.checkSelfPermission(this,
                    Manifest.permission.ACCESS_FINE_LOCATION);
            int hasCoarseLocationPermission = ContextCompat.checkSelfPermission(this,
                    Manifest.permission.ACCESS_COARSE_LOCATION);



            if (hasFineLocationPermission != PackageManager.PERMISSION_GRANTED ||
                    hasCoarseLocationPermission != PackageManager.PERMISSION_GRANTED   ) {

                Log.d(TAG, "startLocationUpdates : 퍼미션 안가지고 있음");
                return;
            }


            Log.d(TAG, "startLocationUpdates : call mFusedLocationClient.requestLocationUpdates");

            mFusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());

            if (checkPermission())
                mMap.setMyLocationEnabled(true);

        }

    }


    @Override
    protected void onStart() {
        super.onStart();

        Log.d(TAG, "onStart");

        if (checkPermission()) {

            Log.d(TAG, "onStart : call mFusedLocationClient.requestLocationUpdates");
            mFusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null);

            if (mMap!=null)
                mMap.setMyLocationEnabled(true);

        }


    }


    @Override
    protected void onStop() {

        super.onStop();

        if (mFusedLocationClient != null) {

            Log.d(TAG, "onStop : call stopLocationUpdates");
            mFusedLocationClient.removeLocationUpdates(locationCallback);
        }
    }




    public String getCurrentAddress(LatLng latlng) {

        //지오코더... GPS를 주소로 변환
        Geocoder geocoder = new Geocoder(this, Locale.getDefault());

        List<Address> addresses;

        try {

            addresses = geocoder.getFromLocation(
                    latlng.latitude,
                    latlng.longitude,
                    1);
        } catch (IOException ioException) {
            //네트워크 문제
            Toast.makeText(this, "지오코더 서비스 사용불가", Toast.LENGTH_LONG).show();
            return "지오코더 서비스 사용불가";
        } catch (IllegalArgumentException illegalArgumentException) {
            Toast.makeText(this, "잘못된 GPS 좌표", Toast.LENGTH_LONG).show();
            return "잘못된 GPS 좌표";

        }


        if (addresses == null || addresses.size() == 0) {
            Toast.makeText(this, "주소 미발견", Toast.LENGTH_LONG).show();
            return "주소 미발견";

        } else {
            Address address = addresses.get(0);
            return address.getAddressLine(0).toString();
        }

    }


    public boolean checkLocationServicesStatus() {
        LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

        return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
                || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    }


    public void setCurrentLocation(Location location, String markerTitle, String markerSnippet) {


        if (currentMarker != null) currentMarker.remove();


        LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude());

        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(currentLatLng);
        markerOptions.title(markerTitle);
        markerOptions.snippet(markerSnippet);
        markerOptions.draggable(true);


        currentMarker = mMap.addMarker(markerOptions);

        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLng(currentLatLng);
        mMap.moveCamera(cameraUpdate);

    }


    public void setDefaultLocation() {


        //디폴트 위치, Seoul
        LatLng DEFAULT_LOCATION = new LatLng(37.56, 126.97);
        String markerTitle = "위치정보 가져올 수 없음";
        String markerSnippet = "위치 퍼미션과 GPS 활성 요부 확인하세요";


        if (currentMarker != null) currentMarker.remove();

        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(DEFAULT_LOCATION);
        markerOptions.title(markerTitle);
        markerOptions.snippet(markerSnippet);
        markerOptions.draggable(true);
        markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED));
        currentMarker = mMap.addMarker(markerOptions);

        CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(DEFAULT_LOCATION, 15);
        mMap.moveCamera(cameraUpdate);

    }


    //여기부터는 런타임 퍼미션 처리을 위한 메소드들
    private boolean checkPermission() {

        int hasFineLocationPermission = ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION);
        int hasCoarseLocationPermission = ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_COARSE_LOCATION);



        if (hasFineLocationPermission == PackageManager.PERMISSION_GRANTED &&
                hasCoarseLocationPermission == PackageManager.PERMISSION_GRANTED   ) {
            return true;
        }

        return false;

    }



    /*
    * ActivityCompat.requestPermissions를 사용한 퍼미션 요청의 결과를 리턴받는 메소드입니다.
    */
    @Override
    public void onRequestPermissionsResult(int permsRequestCode,
                                          @NonNull String[] permissions,
                                          @NonNull int[] grandResults) {

        if ( permsRequestCode == PERMISSIONS_REQUEST_CODE && grandResults.length == REQUIRED_PERMISSIONS.length) {

            // 요청 코드가 PERMISSIONS_REQUEST_CODE 이고, 요청한 퍼미션 개수만큼 수신되었다면

            boolean check_result = true;


            // 모든 퍼미션을 허용했는지 체크합니다.

            for (int result : grandResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    check_result = false;
                    break;
                }
            }


            if ( check_result ) {

                // 퍼미션을 허용했다면 위치 업데이트를 시작합니다.
                startLocationUpdates();
            }
            else {
                // 거부한 퍼미션이 있다면 앱을 사용할 수 없는 이유를 설명해주고 앱을 종료합니다.2 가지 경우가 있습니다.

                if (ActivityCompat.shouldShowRequestPermissionRationale(this, REQUIRED_PERMISSIONS[0])
                        || ActivityCompat.shouldShowRequestPermissionRationale(this, REQUIRED_PERMISSIONS[1])) {


                    // 사용자가 거부만 선택한 경우에는 앱을 다시 실행하여 허용을 선택하면 앱을 사용할 수 있습니다.
                    Snackbar.make(mLayout, "퍼미션이 거부되었습니다. 앱을 다시 실행하여 퍼미션을 허용해주세요. ",
                            Snackbar.LENGTH_INDEFINITE).setAction("확인", new View.OnClickListener() {

                        @Override
                        public void onClick(View view) {

                            finish();
                        }
                    }).show();

                }else {


                    // "다시 묻지 않음"을 사용자가 체크하고 거부를 선택한 경우에는 설정(앱 정보)에서 퍼미션을 허용해야 앱을 사용할 수 있습니다.
                    Snackbar.make(mLayout, "퍼미션이 거부되었습니다. 설정(앱 정보)에서 퍼미션을 허용해야 합니다. ",
                            Snackbar.LENGTH_INDEFINITE).setAction("확인", new View.OnClickListener() {

                        @Override
                        public void onClick(View view) {

                            finish();
                        }
                    }).show();
                }
            }

        }
    }


    //여기부터는 GPS 활성화를 위한 메소드들
    private void showDialogForLocationServiceSetting() {

        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle("위치 서비스 비활성화");
        builder.setMessage("앱을 사용하기 위해서는 위치 서비스가 필요합니다.\n"
                + "위치 설정을 수정하실래요?");
        builder.setCancelable(true);
        builder.setPositiveButton("설정", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int id) {
                Intent callGPSSettingIntent
                        = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                startActivityForResult(callGPSSettingIntent, GPS_ENABLE_REQUEST_CODE);
            }
        });
        builder.setNegativeButton("취소", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
            }
        });
        builder.create().show();
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        switch (requestCode) {

            case GPS_ENABLE_REQUEST_CODE:

                //사용자가 GPS 활성 시켰는지 검사
                if (checkLocationServicesStatus()) {
                    if (checkLocationServicesStatus()) {

                        Log.d(TAG, "onActivityResult : GPS 활성화 되있음");


                        needRequest = true;

                        return;
                    }
                }

                break;
        }
    }



}





5. 관련 포스팅

 

5.1. Google Maps Android API 사용 방법 및 예제

Google Maps Android API를 사용하는 기본적인  방법과 사용시 발생할 수 있는 문제점에 대해 다룹니다.

http://webnautes.tistory.com/647 




5.2. Places API Web Service를 사용하여 Android Google Map에 현재 위치 주변의 음식점 표시하기

 

Places API Web Service를 이용하여 현재 위치 주변의 음식점 정보를 안드로이드의 구글맵에 표시하는 내용을 다룹니다.

http://webnautes.tistory.com/1080 



5.3. GenyMotion 가상머신에 Google Apps설치하여 Google Maps Android API 테스트 하기

 

Genymotion에서 Google Map API을 가지고 작성한 Android 앱을 테스트하는 방법을 소개합니다.

http://webnautes.tistory.com/1064 





반응형

해본 것을 문서화하여 기록합니다.


포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
질문을 남겨주면 가능한 빨리 답변드립니다.


제가 쓴 책도 한번 검토해보세요 ^^

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기

댓글을 달아 주세요

">
  1. 이전 댓글 더보기
  2. thumbnail
    IT마스터를 위하여
    2020.09.22 00:25

    안녕하세요
    항상 도움 많이 받고 있습니다.
    한가지 궁금한게 있는데 처음에만 현재 위치를 찍어주고 그다음부터 카메라를 옮길때는 업데이트가 되지 않기 위해서는 어떻게 해야할까요?

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2020.09.22 07:40 신고

      setCurrentLocation를 주석처리하면 현재위치로 이동하지 않삽니다. 손으로 터치한경우 setCurrentLocation가 동작하지 않다가 현재 위치버튼누르면 다시 움직이도록 해도 될듯합니다

  3. thumbnail
    Favicon of https://mystation.tistory.com BlogIcon 뽀끄니

    E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.cyberstation.busservice, PID: 28459
    java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.android.gms.maps.model.Marker com.google.android.gms.maps.GoogleMap.addMarker(com.google.android.gms.maps.model.MarkerOptions)' on a null object reference
    at com.cyberstation.busservice.MainActivity.setDefaultLocation(MainActivity.java:433)
    at com.cyberstation.busservice.MainActivity.onMapReady(MainActivity.java:170)

    ERROR 나는군요 왜그럴까요

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2020.09.28 19:51 신고


      다음 부분에서 mMap 또는 markerOptions 가 문제인듯합니다.
      혹시 포스트와 다르게 수정한 부분이 있나요?

      MarkerOptions markerOptions = new MarkerOptions();
      markerOptions.position(currentLatLng);
      markerOptions.title(markerTitle);
      markerOptions.snippet(markerSnippet);
      markerOptions.draggable(true);


      currentMarker = mMap.addMarker(markerOptions);

    • thumbnail
      Favicon of https://bbangbbang2s.tistory.com BlogIcon 빵빵2
      2021.08.23 17:30 신고

      저도 똑같은 오류가 뜨는데, 해결하셨나요??

  4. thumbnail
    2020.09.28 16:38

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2020.09.28 19:52 신고

      자바코드 파일당 액티비티(클래스)는 하나씩 포함되어야 합니다.

      파일이름과 액티비티(클래스)이름도 일치해야합니다.

      질문을 맞게 이해했나 모르겠군요

  5. thumbnail
    2020.09.29 00:15

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2020.09.29 08:24 신고

      지도는 보이나요? 앱을 삭제하고 재설치해서 퍼미션 물어보는게 아니면 지도 액티비티가 실행되는데 문제가 있어보입니다

  6. thumbnail
    이은규
    2020.10.10 21:17

    mainactivity에서
    .findFragmentById(R.id.map);의 map에 빨간색으로 오류가 뜨는데 이거 해결방법이 어떻게 될까요??

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2020.10.10 21:43 신고

      레이아웃에 map이 있나보세요? 에러를 확인하여 구글에서 검색하면 원인을 찾을 수 있습니다

  7. thumbnail
    Favicon of https://all-share-source-code.tistory.com BlogIcon 공유해드림

    와...정말 대단하십니다!!
    잘 작동되는거 확인 했고 이렇게 자세히 설명해주는 분은 없을꺼 같네요!!

    질문하나 있는데 혹시 구글Map API 빼고 위치정보만 받아오게 할수는 있나요?
    지금 제가 작업 하고 있긴한데 만만치 않네요...

  8. thumbnail
    Favicon of https://all-share-source-code.tistory.com BlogIcon 공유해드림

    리플이 안되서 댓글로 올립니다.

    Favicon of https://webnautes.tistory.com webnautes2020.11.30 13:13 신고
    구글 Map API 없이라는게 지도에 표시안하고를 의미하나요?

    네 맞습니다.

  9. thumbnail
    Favicon of https://lolou.tistory.com BlogIcon 의도_

    LocationCallback locationCallback = new LocationCallback() {
    @Override
    public void onLocationResult(LocationResult locationResult) {
    super.onLocationResult(locationResult);

    List<Location> locationList = locationResult.getLocations();

    if (locationList.size() > 0) {
    location = locationList.get(locationList.size() - 1);

    안녕하세요, 이 부분의 해석이 안되어서요

    LocationCallback locationCallback = new LocationCallback() 이렇게 해주는 이유는 무엇인지?
    List<Location> locationList = locationResult.getLocations(); 이건 뭘 말하는건지
    location = locationList.get(locationList.size() - 1); 여기서 -1은 왜 해주는건지
    궁금합니다.

  10. thumbnail
    2021.06.12 18:39

    비밀댓글입니다

  11. thumbnail
    2021.06.13 15:20

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.06.13 15:31 신고

      LocationManager는 어느 정보를 사용하여 위치 정보를 획득할지 설정할때 사용됩니다.

      실제 위치 정보는 FusedLocationProviderClient 를 사용하여 가져옵니다.

      LocationManager만을 사용하여 위치 정보를 가져올 수도 있지만 복잡하기 때문에 대신 FusedLocationProviderClient 를 사용한다고 합니다.


      LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

      return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
      || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

  12. thumbnail
    2021.06.13 15:56

    비밀댓글입니다

  13. thumbnail
    2021.06.30 16:07

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.06.30 18:12 신고

      package로 시작하는 첫줄이 현재 프로젝트의 패키지이름과 일치 안해서 발생한거 같습니다

  14. thumbnail
    Favicon of https://jeyolhappy.tistory.com BlogIcon 코딩드가자

    안녕하세요 선생님 블로그 잘 보고 있습니다 ㅎㅎ 항상 감사합니다.
    마커를 클릭하면 주소 미발견이라고 뜨는데 해결방법이 있을까요?

  15. thumbnail
    Favicon of https://littleboy.tistory.com BlogIcon 꼬맹님

    안녕하세요. 코드 정말 작동 잘하네요^^
    궁금한 것이 있어서 질문 드립니다.
    지금 구현하고 싶은 앱이 실행 중일 때 문자로 위도, 경도 값을 받으면 자동으로 지도를 표시해 주고 싶습니다. 위에 코드를 수정하여 할 수 있는지 여쭙고 싶습니다.

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.07.22 12:50 신고

      네 지도에 마커를 추가하면 가능합니다. 현재 위치따라 이동하는 것을 중지시킬 필요가 있습니다

  16. thumbnail
    Favicon of https://littleboy.tistory.com BlogIcon 꼬맹님

    안녕하세요 질문이 있습니다.
    위에 코드에 xml파일에 textview를 추가 한뒤에
    위도와 경도를 textview에 추가 하려고 합니다.
    그래서 Handler를
    protected void onCreate(Bundle savedInstanceState)
    {
    mHandler = new Handler();
    mtextViewxval = findViewById(R.id.textViewxval);
    mtextViewyval = findViewById(R.id.textViewyval);

    Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
    //UI 작업 수행 X
    mHandler.post(new Runnable() {
    @Override
    public void run() {
    //UI 작업 수행 O
    String strx = Double.toString(x);
    String stry = Double.toString(y);
    mtextViewxval.setText(strx);
    mtextViewyval.setText(stry);
    }
    });
    }
    });
    t.start();
    }
    이런 식으로 넣어줬는데 앱이 실행이 되지 않는데
    주기 적으로 textview를 바꾸려면 어떻게 해야되는지 피드백 해주실 수 있나요??

  17. thumbnail
    Favicon of https://littleboy.tistory.com BlogIcon 꼬맹님

    네 x,y는 전역 변수로 선언되어 있습니다. x=37.56 y=126.97
    위에 올려주신 코드랑 모두 같고 위에 올린 Handler만 추가해주어 textview에 위도 경도 값을 표시하려고합니다.(x,y 변수 만 초기 위치 설정하는 곳(서울)에 넣어 놨습니다.)

    mtextViewxval.setText(strx);
    mtextViewyval.setText(stry);
    에러는 뜨지 않는데
    위에 두 줄을 넣으면 앱이 실행이 되지 않습니다.ㅜㅜ
    (점심 맛있게 드세용)

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.07.28 12:23 신고

      코드는 맞는듯한데 에러를 확인해보세요

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.07.28 12:25 신고

      onCreate가 아닌 다른 메소드에 넣어야 할거 같아요. UI 생성전에 코드가 들어간거.같네요. 액티비티 생명주기를 확인해보세요

  18. thumbnail
    Favicon of https://littleboy.tistory.com BlogIcon 꼬맹님

    여러가지로 해보고 있는데
    버튼이나 동작이나 textview 를 oncreat에 넣으면 앱이 실행이 안됩니다.
    어느 부분에 넣어야 할지 모르겠네요...ㅜ

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.07.28 16:20 신고

      액티비티 생명주기보면 onCreate 이후 UI가 준비된 시점의 메소드가 있었던거 같습니다.

  19. thumbnail
    Favicon of https://littleboy.tistory.com BlogIcon 꼬맹님

    setContentView(R.layout.activity_main); 뒤 쪽으로 선언한 것들을 옮겨 주니 작동이 됩니다!! 몰랐네요. 아직 모르는게 많아서요..ㅜㅜ
    더 여쭈어 보고싶은게 있습니다!!
    위에 코드에서

    //현재 위치에 마커 생성하고 이동
    //setCurrentLocation(location, markerTitle, markerSnippet);

    이 부분을 주석처리 하지않으면 계속 현재 위치로 돌아가서 추석처리 했습니다.
    그래서 지금은 처음 위치가 서울로 되어 있는데요. 이 서울로 되어있는 위도 경도를 중간에 버튼을 누르면 EditText 값을 받아와서 마커랑 위치가 바뀌게 하고 싶은데 Handler를 어디에 넣을지 모르겠네요 ㅠㅠ

  20. thumbnail
    Favicon of https://rltjsalstjd7.tistory.com BlogIcon 뽀루루루룽

    선생님.. 정말 감사드립니다... 실시간 위치 띄우려고 3주 동안 서칭했었는데 선생님을 만나 드디어 실현했습니다.. 진짜 감사드립니다..ㅠㅠ

  21. thumbnail
    2021.08.29 19:24

    비밀댓글입니다