如何在安卓系统中使用Firebase和谷歌地图创建一个位置追踪应用程序

400 阅读7分钟

在安卓系统中使用Firebase和谷歌地图创建一个位置跟踪应用程序

在本教程中,你将学习如何创建一个使用谷歌地图API来确定另一个用户的精确位置的应用程序。我们将通过在Firebase实时数据库中保存用户的位置,然后从另一个设备中检索数据来实现这一目标。

位置服务最近在我们日常使用的社交应用程序中变得很普遍。此外,这些服务还可以用来追踪丢失或被盗的设备。因此,了解如何实现位置服务可以让你创造出更多富有成效的应用程序。

目标

通过本教程,你将能够想出两个简单的应用程序来帮助进行位置跟踪。这些项目使用谷歌地图API和Firebase的实时数据库。

前提条件

  • 对Android开发中的Kotlin有一个很好的理解。
  • Android Studio。

在本教程中,我们将使用真实的Android设备进行测试。然而,你可以通过模拟位置数据来使用模拟器。

第1步 - 创建一个新项目

在Android Studio上创建一个新项目。在选择Project template 页面,选择Google Maps Activity 模板。

project template

等待Android Studio构建你的项目。

此时运行应用程序后,你会看到一个空白屏幕,因为你还没有为地图设置API key

有几件事需要注意

一旦你打开AndroidManifest.xml ,你会看到这些自动填充的细节。

  • ACCESS_FINE_LOCATION 权限。这可以访问用户的精确位置。大多数情况下,当你需要最精确的位置时使用。另一种类型的位置精度是 ,它的精度较低。因此,我们将使用 ,以获得准确的读数。ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION
  • com.google.android.geo.API_KEY 指定了API密钥。
  implementation 'com.google.android.gms:play-services-maps:17.0.0'

第2步 - 创建一个API密钥

在使用Google Maps API 之前,你需要创建一个API key ,并在developer console 中启用它。使用你的谷歌账户来注册。

打开res/values/google_maps_api.xml 。这是你将看到的。

Console link

这个文件将包含你的API key 。点击下划线的链接,按create a project ,然后继续。

create project

在下一个屏幕中,你将需要创建一个API key 来调用API。

create API Key

API key 是在purple

API Key

一旦你有了API key ,把它复制并粘贴到XML文件中的google_maps_key 的值中。

MapsActivity

这是对默认的MapsActivity 的概述。

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

    private lateinit var mMap: GoogleMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)
        
        val mapFragment = supportFragmentManager
                .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
    }

    // This method is called when we need to initialize the map and as you can see, it creates a marker with coordinates near Sydney and adds it to the map.

    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap

        // Add a marker in Sydney and move the camera
        val sydney = LatLng(-34.0, 151.0)
        mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
        //call moveCamera() on mMap to update the camera
        mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
    }
}

在这一点上,一旦你运行这个应用程序,你会注意到标记是在悉尼。

第3步 - 创建一个Firebase项目

我们将使用Firebase来存储用户的位置。再一次,你将需要一个谷歌账户。

Firebase Introduction

第4步 - 将Firebase项目连接到应用程序上

你现在需要把这个项目连接到你的应用程序。

  • 转到工具>Firebase

Firebase in Android Studio

确保你将Realtime Database 到你的应用程序。

第5步 - 添加权限

  • 添加互联网权限。

这个权限允许应用程序连接到互联网并保存数据。

<uses-permission android:name="android.permission.INTERNET"/>
  • 添加谷歌地图的位置依赖。
 implementation 'com.google.android.gms:play-services-location:17.0.0'

第6步 - MapsActivity

导航到MapsActivity.kt ,添加以下代码。

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

    private lateinit var map: GoogleMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        val mapFragment = supportFragmentManager
                .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
        setupLocClient()

    }

    private lateinit var fusedLocClient: FusedLocationProviderClient
    // use it to request location updates and get the latest location

    override fun onMapReady(googleMap: GoogleMap) {
        map = googleMap //initialise map
        getCurrentLocation()
    }
    private fun setupLocClient() {
        fusedLocClient =
            LocationServices.getFusedLocationProviderClient(this)
    }

    // prompt the user to grant/deny access
    private fun requestLocPermissions() {
        ActivityCompat.requestPermissions(this,
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), //permission in the manifest
            REQUEST_LOCATION)
    }

    companion object {
        private const val REQUEST_LOCATION = 1 //request code to identify specific permission request
        private const val TAG = "MapsActivity" // for debugging
    }

    private fun getCurrentLocation() {
        // Check if the ACCESS_FINE_LOCATION permission was granted before requesting a location
        if (ActivityCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION) !=
            PackageManager.PERMISSION_GRANTED) {
    
          // call requestLocPermissions() if permission isn't granted
            requestLocPermissions()
        } else {

            fusedLocClient.lastLocation.addOnCompleteListener {
                // lastLocation is a task running in the background
                val location = it.result //obtain location
                //reference to the database
                val database: FirebaseDatabase = FirebaseDatabase.getInstance()
                val ref: DatabaseReference = database.getReference("test")
                if (location != null) {

                    val latLng = LatLng(location.latitude, location.longitude)
                   // create a marker at the exact location
                    map.addMarker(MarkerOptions().position(latLng)
                        .title("You are currently here!"))
                    // create an object that will specify how the camera will be updated
                    val update = CameraUpdateFactory.newLatLngZoom(latLng, 16.0f)

                    map.moveCamera(update)
                    //Save the location data to the database
                    ref.setValue(location)
                } else {
                      // if location is null , log an error message
                    Log.e(TAG, "No location found")
                }



            }
        }
    }


    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray) {
        //check if the request code matches the REQUEST_LOCATION
        if (requestCode == REQUEST_LOCATION)
        {
            //check if grantResults contains PERMISSION_GRANTED.If it does, call getCurrentLocation()
            if (grantResults.size == 1 && grantResults[0] ==
                PackageManager.PERMISSION_GRANTED) {
                getCurrentLocation()
            } else {
                //if it doesn`t log an error message
                Log.e(TAG, "Location permission has been denied")
            }
        }
    }

}

第7步 - 运行该应用程序

运行该应用程序。这就是你要实现的目标(位置可能有所不同)。给予该应用程序的位置权限。

project

用户的位置将被看到,如下图所示。

project

从上面的代码来看,你已经成功地将用户的位置保存在数据库中。导航到Firebase控制台,点击你创建的项目。

你应该看到与此类似的东西。

project

LocationChecker应用程序

这第二个应用程序允许你从数据库中检索用户的位置。

第1步:创建一个新的项目

按照上面讨论的过程来创建一个新的项目。确保你选择了Google Maps 模板,并为其适当地命名。

第2步:向现有的密钥添加证书

由于我们已经有一个API key ,我们可以直接在控制台中包含它。打开你的开发者控制台,点击edit icon

Add Item

导航到google_maps_api.xml 文件,复制软件包名称和SHA-1证书指纹,将这些细节粘贴到add item 部分。然后,保存更改。

第3步:添加一个按钮

我们需要添加一个button ,它将触发对数据库中当前位置的读取。

这里是activity_maps.xml

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    android:layout_height="match_parent"
    tools:context=".MapsActivity"
    xmlns:tools="http://schemas.android.com/tools">

<fragment
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    map:layout_constraintLeft_toLeftOf="parent"
    map:layout_constraintRight_toRightOf="parent"
    map:layout_constraintTop_toTopOf="parent"
    map:layout_constraintBottom_toBottomOf="parent" />

    <Button
        android:layout_width="wrap_content"
        map:layout_constraintLeft_toLeftOf="parent"
        map:layout_constraintRight_toRightOf="parent"
        map:layout_constraintBottom_toBottomOf="parent"
        android:padding="20dp"
        android:id="@+id/btn_find_location"
        android:text="@string/find_user_s_location"
        android:layout_height="wrap_content" />
</androidx.constraintlayout.widget.ConstraintLayout>

第4步:添加权限

默认情况下,GoogleMaps 活动模板在AndroidManifest.xml 文件中添加了ACCESS_FINE_LOCATION 的权限。因为我们需要互联网来读取数据库,所以要添加互联网的权限,如下图所示。

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

第5步:模型类

因为我们要从数据库中读取数据,所以我们需要一个类来添加attributes,latitude, 和longitude 来处理数据。

import com.google.firebase.database.IgnoreExtraProperties

@IgnoreExtraProperties
data class LocationInfo(
    var latitude: Double? = 0.0,
    var longitude: Double? = 0.0
)

第6步:MapsActivity

添加以下代码。

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

    private lateinit var map: GoogleMap
    private var database: FirebaseDatabase = FirebaseDatabase.getInstance()
    private var dbReference: DatabaseReference = database.getReference("test")
    private lateinit var find_location_btn: Button


        override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)

            find_location_btn = findViewById(R.id.btn_find_location)
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        val mapFragment = supportFragmentManager
                .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
        // Get a reference from the database so that the app can read and write operations
            dbReference = Firebase.database.reference
            dbReference.addValueEventListener(locListener)
    }

    val locListener = object : ValueEventListener {
        //     @SuppressLint("LongLogTag")
        override fun onDataChange(snapshot: DataSnapshot) {
            if(snapshot.exists()){
            //get the exact longitude and latitude from the database "test"
                val location = snapshot.child("test").getValue(LocationInfo::class.java)
                val locationLat = location?.latitude
                val locationLong = location?.longitude
                //trigger reading of location from database using the button
                find_location_btn.setOnClickListener {

                     // check if the latitude and longitude is not null
                    if (locationLat != null && locationLong!= null) {
                    // create a LatLng object from location
                        val latLng = LatLng(locationLat, locationLong)
                        //create a marker at the read location and display it on the map
                        map.addMarker(MarkerOptions().position(latLng)
                                .title("The user is currently here"))
                                //specify how the map camera is updated
                        val update = CameraUpdateFactory.newLatLngZoom(latLng, 16.0f)
                        //update the camera with the CameraUpdate object
                        map.moveCamera(update)
                    }
                    else {
                        // if location is null , log an error message
                        Log.e(TAG, "user location cannot be found")
                    }
                }

            }
        }
        // show this toast if there is an error while reading from the database
        override fun onCancelled(error: DatabaseError) {
                Toast.makeText(applicationContext, "Could not read from database", Toast.LENGTH_LONG).show()
        }

    }

    override fun onMapReady(googleMap: GoogleMap) {
        map = googleMap //initialize map when the map is ready

    }
    companion object {
        // TAG is passed into the Log.e methods used above to print information to the Logcat window
        private const val TAG = "MapsActivity" // for debugging
    }
}

当你运行该应用程序时,你应该能够得到一个类似的视图(位置会有所不同)。

Location checker

测试应用程序

在你的手机上安装两个应用程序。确保网络连接良好。检查数据库中的保存位置。在第二个应用程序LocationChecker上,检查是否检索到了位置。

结论

你可以使用地图API来创建令人敬畏的应用程序,或者为现有的应用程序添加更多的功能。你还可以添加你可能感兴趣的其他功能。例如,你可以通知用户他们正在被追踪。