使用地理围栏时如何从 BroadcastReceiver 通知调用 activity

How to notify the calling activity from a BroadcastReceiver when using Geofencing

我根据地理围栏文档定义了 BroacastReceiver,以便在用户与地理围栏交互时接收进入和退出更新。我的问题是该应用程序将在路上使用,因此,当用户开车并进入地理围栏时,我会收到有关它的通知,当他退出时也会发生同样的情况。但是,当收到退出事件时,我需要从客户端和 Google 地图中删除触发的地理围栏。这两个都存在于我的 MapsActivity 中(这是我根据文档设置接收器和事件通知过程的地方)所以我想从接收器调用我的 activity 的 removeGeofences(...) 方法。我查看了大量关于此事的帖子,但 none 似乎涵盖了地理围栏用例。我已经尝试通过代码而不是通过清单静态地声明接收器,但在那种情况下,我需要我无法找到地理围栏的意图过滤器。关于如何实现这一点有什么想法吗?

广播接收器:

class GeofenceReceiver : BroadcastReceiver() {
    private val TAG = GeofenceReceiver::class.java.simpleName

    override fun onReceive(context: Context?, intent: Intent?) {
        val geofencingEvent = GeofencingEvent.fromIntent(intent)
        if (geofencingEvent.hasError()) {
            val errorMessage = GeofenceStatusCodes
                .getStatusCodeString(geofencingEvent.errorCode)
            Log.e(TAG, "error: $errorMessage")
            return
        }

        // Get the transition type.
        val geofenceTransition = geofencingEvent.geofenceTransition

        // Test that the reported transition was of interest.
        if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
            geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
        ) {

            // Get the geofences that were triggered. A single event can trigger
            // multiple geofences.
            val triggeringGeofences = geofencingEvent.triggeringGeofences

            val ids = arrayListOf<String>()
            for (geofence in triggeringGeofences) {
                Log.d(TAG, "Geofence ${geofence.requestId} triggered!")
                ids.add(geofence.requestId)
            }

            if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER)
                Log.d(TAG, "User entered geofence!")
            else {
                Log.d(TAG, "User exited geofence!")
                //activity.removeGeofences(ids)
            }
        } else {
            // Log the error.
            Log.e(TAG, "Invalid transition")
        }
    }
}

地图活动:

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

    private var mMap: GoogleMap? = null
    private var geofenceClient: GeofencingClient? = null
    private var geofenceList: ArrayList<Geofence> = arrayListOf()
    private var geofenceMapMarks: MutableMap<String, Pair<Marker, Circle>> = mutableMapOf()
    private var currLocationMarker: Marker? = null

    private val geofencePendingIntent: PendingIntent by lazy {
        val intent = Intent(this, GeofenceReceiver::class.java)
        // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
        // addGeofences() and removeGeofences()
        PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    }

    private val mLocationListener: LocationListener = LocationListener {
        currLocationMarker?.remove()
        currLocationMarker = mMap?.addMarker(
            MarkerOptions().position(LatLng(it.latitude, it.longitude)).title("Current Location")
                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE))
        )
        animateCameraToLocation(currLocationMarker?.position!!)
    }

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

        geofenceClient = LocationServices.getGeofencingClient(this)
        // 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)

        val mLocationManager = getSystemService(LOCATION_SERVICE) as LocationManager

        if (!shouldRequestPermissions()) subscribeToLiveCurrentLocation(mLocationManager)
    }

    @SuppressLint("MissingPermission")
    private fun subscribeToLiveCurrentLocation(mLocationManager: LocationManager) {
        mLocationManager.requestLocationUpdates(
            LocationManager.GPS_PROVIDER, 1000,
            50F, mLocationListener
        )
    }

    private fun requestPermissionsIfNeeded() {
        if (!shouldRequestPermissions()) return

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                2
            )
        else ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
            1
        )
    }

    @SuppressLint("InlinedApi")
    private fun shouldRequestPermissions(): Boolean = ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.ACCESS_FINE_LOCATION
    ) != PackageManager.PERMISSION_GRANTED
            && ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.ACCESS_BACKGROUND_LOCATION
    ) != PackageManager.PERMISSION_GRANTED

    /**
     * Manipulates the map once available.
     * This callback is triggered when the map is ready to be used.
     * This is where we can add markers or lines, add listeners or move the camera. In this case,
     * we just add a marker near Sydney, Australia.
     * If Google Play services is not installed on the device, the user will be prompted to install
     * it inside the SupportMapFragment. This method will only be triggered once the user has
     * installed Google Play services and returned to the app.
     */
    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap

        mMap?.setOnMapClickListener { latlong ->
            drawGeofenceOnMap(latlong)
            addGeofenceToList(latlong)
        }
    }

    private fun drawGeofenceOnMap(latlong: LatLng) {
        val marker = mMap?.addMarker(MarkerOptions().position(latlong).title("Geofence"))
        val circle = mMap?.addCircle(
            CircleOptions().center(latlong).radius(defaultGeofenceRadius.toDouble()).strokeColor(
                resources.getColor(android.R.color.holo_red_light, null)
            ).fillColor(
                resources.getColor(android.R.color.transparent, null)
            )
        )
        mMap?.moveCamera(CameraUpdateFactory.newLatLng(latlong))
        geofenceMapMarks["Geofence #" + (geofenceList.size + 1)] = Pair(marker!!, circle!!)
    }

    private fun animateCameraToLocation(latlong: LatLng) {
        val cameraPosition = CameraPosition.Builder()
            .target(latlong)
            .zoom(17f)
            .build()

        mMap?.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
    }

    private fun addGeofenceToList(latlong: LatLng) {
        geofenceList.add(
            Geofence.Builder()
                // Set the request ID of the geofence. This is a string to identify this
                // geofence.
                .setRequestId("Geofence #" + (geofenceList.size + 1))

                // Set the circular region of this geofence.
                .setCircularRegion(
                    latlong.latitude,
                    latlong.longitude,
                    defaultGeofenceRadius
                )

                // Set the expiration duration of the geofence. This geofence gets automatically
                // removed after this period of time.
                .setExpirationDuration(Geofence.NEVER_EXPIRE)

                // Set the transition types of interest. Alerts are only generated for these
                // transition. We track entry and exit transitions in this sample.
                .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)

                // Create the geofence.
                .build()
        )
    }

    private fun getGeofencingRequest(): GeofencingRequest {
        return GeofencingRequest.Builder().apply {
            setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            addGeofences(geofenceList)
        }.build()
    }

    private fun addGeofences() {
        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            Toast.makeText(this, "Need to grant permissions to continue", Toast.LENGTH_LONG).show()
            requestPermissionsIfNeeded()
            return
        }
        geofenceClient?.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run {
            addOnSuccessListener {
                Toast.makeText(this@MapsActivity, "Geofences added!", Toast.LENGTH_LONG).show()
            }
            addOnFailureListener {
                Toast.makeText(this@MapsActivity, "Failed to add geofences!", Toast.LENGTH_LONG)
                    .show()
                Log.d("MAPSACTIVITY", it.message.toString())
            }
        }
    }

    fun removeGeofences(ids: ArrayList<String>) {
        removeGeofencesFromClient(ids)
        removeGeofencesFromMap(ids)
    }

    private fun removeGeofencesFromMap(ids: ArrayList<String>) {
        for (id in ids) {
            if (geofenceMapMarks.keys.contains(id)) geofenceMapMarks.remove(id)
        }
    }

    private fun removeGeofencesFromClient(ids: ArrayList<String>) {
        for (fence in geofenceList) {
            if (ids.contains(fence.requestId)) geofenceList.remove(fence)
        }

        geofenceClient?.removeGeofences(ids)
            ?.addOnCompleteListener { task ->
                if (task.isSuccessful) {
                    Toast.makeText(
                        this,
                        "Geofences have been removed!",
                        Toast.LENGTH_LONG
                    ).show()
                } else {
                    Toast.makeText(
                        this,
                        "Failed to remove geofences",
                        Toast.LENGTH_LONG
                    ).show()
                    Log.d("MAPSACTIVITY", "error ==> " + task.exception)
                }
            }
    }

    override fun onDestroy() {
        super.onDestroy()
        mMap = null
        geofenceClient = null
    }

    @SuppressLint("InlinedApi")
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        var grantFailed = false
        when (requestCode) {
            1 -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "GRANTED", Toast.LENGTH_SHORT).show()
                } else grantFailed = true
            }
            2 -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "GRANTED first", Toast.LENGTH_SHORT).show()
                    ActivityCompat.requestPermissions(
                        this,
                        arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
                        3
                    )
                } else grantFailed = true
            }
            3 -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "GRANTED second", Toast.LENGTH_SHORT).show()
                } else grantFailed = true
            }
        }
        if (grantFailed) {
            if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) ||
                shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
            ) {
                Toast.makeText(this, "Show permission rationale", Toast.LENGTH_LONG).show()
            } else Toast.makeText(
                this,
                "You must grant the requested permissions to continue",
                Toast.LENGTH_SHORT
            ).show()
        }
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.addGeofence -> {
                addGeofences()
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        val inflater = menuInflater
        inflater.inflate(R.menu.options_menu, menu)
        return true
    }
}

清单:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.spap.geofencepoc">

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

    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.GeoFencePoC"
        tools:ignore="AllowBackup">

        <receiver android:name=".receivers.GeofenceReceiver"/>

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="@string/google_maps_key" />

        <activity
            android:name=".presentation.MapsActivity"
            android:label="@string/title_activity_maps">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

您可以使用 SharesPreference 将触发的 geoFenceId 通知给 MainActivity。

class GeofenceReceiver : BroadcastReceiver() {
    private val TAG = GeofenceReceiver::class.java.simpleName
    private var geoFencePref: SharedPreferences? = null
    private val triggeredExitGeofenceIds: HashSet<String> = HashSet()
    private var triggedGeofenceIdsList: ArrayList<String> = ArrayList()

    override fun onReceive(context: Context?, intent: Intent?) {
        geoFencePref = context?.getSharedPreferences("TriggerdExitedId",Context.MODE_PRIVATE)
        val geofencingEvent = GeofencingEvent.fromIntent(intent)
        if (geofencingEvent.hasError()) {
            val errorMessage = GeofenceStatusCodes
                .getStatusCodeString(geofencingEvent.errorCode)
            Log.e(TAG, "error: $errorMessage")
            return
        }

        // Get the transition type.
        val geofenceTransition = geofencingEvent.geofenceTransition

        // Test that the reported transition was of interest.
        if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
            geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
        ) {

            // Get the geofences that were triggered. A single event can trigger
            // multiple geofences.
            val triggeringGeofences = geofencingEvent.triggeringGeofences
            storeGeofenceTransitionDetails(geofenceTransition,triggeringGeofences)


            for (geofence in triggeringGeofences) Log.d(TAG, "Geofence ${geofence.requestId} triggered!")

            if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER)
                Log.d(TAG, "User entered geofence!")
            else {
                Log.d(TAG, "User exited geofence!")
            }
        } else {
            // Log the error.
            Log.e(TAG, "Invalid transition")
        }
    }

    private fun storeGeofenceTransitionDetails(
        geofenceTransition: Int,
        triggeredGeofences: List<Geofence>
    ) {
        triggeredExitGeofenceIds.clear()
        for (geofence in triggeredGeofences) {
            triggedGeofenceIdsList.add(geofence.requestId)
            if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
                triggeredExitGeofenceIds.add(geofence.requestId)
            }
        }
        geoFencePref?.edit()?.putStringSet("geoFenceId", triggeredExitGeofenceIds)?.apply()
    }
}  

//然后在MainActivity中,注册sharedpreference监听变化

class MapsActivity : AppCompatActivity(), OnMapReadyCallback,
    SharedPreferences.OnSharedPreferenceChangeListener {

....

    override fun onStart() {
        super.onStart()
        requestPermissionsIfNeeded()
        geoFencePref = getSharedPreferences("TriggerdExitedId", Context.MODE_PRIVATE)
        geoFencePref!!.registerOnSharedPreferenceChangeListener(this)
    }

....

    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
        val triggeredExitFences: HashSet<String>
        val triggeredGeofences = ArrayList<String>()
        if (key != null) {
            Log.d("onSharedChanged: ", key)
        }
        if (key.equals("geoFenceId")) {
            triggeredExitFences = geoFencePref?.getStringSet("geoFenceId", null) as HashSet<String>
            if(triggeredExitFences.isEmpty()) Log.d("onSharedChanged: ", "no exit fences triggered")
            triggeredGeofences.addAll(triggeredExitFences)
            for(fence in triggeredExitFences) Log.d("onSharedChanged: ", "ID: $fence triggered!")
            //Here you can call removeGeoFencesFromClient() to unRegister geoFences and removeGeofencesFromMap() to remove marker.
            // removeGeofencesFromClient(triggerdIdList);
            // removeGeofencesFromMap(triggerdIdList);
        }
    }
}