Идея заключалась в том, что мне нужен был GPS в фоновом режиме для проекта Unity. Он предназначен для того, чтобы узнать, находится ли пользователь рядом с «информационной точкой» во время пеших походов по определенным маршрутам. Для этого мы решили (поскольку нам нужно было получать довольно частые обновления GPS) использовать геозону. Я создал собственный модуль в студии Android и поместил файл AAR в единое целое. однако широковещательный приемник никогда не запускается. Я начал с учебника по фоновой трансляции Android как базового c шаблона (https://developer.android.com/training/location/geofencing.html). Код в итоге выглядел так:
Bridge. java
package com.BrabantWater.mynativemodulegps;
//import needed packages / classes.
import android.annotation.SuppressLint;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingClient;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
public class Bridge extends Application
{
public static Context context;
public static String notificationContentTitle = "";
public static String notificationContentText = "";
public static String notificationDescription = "";
private GeofencingClient geofencingClient;
private ArrayList<Geofence> geofenceList;
GeofencingRequest.Builder builder;
PendingIntent geofencePendingIntent;
GeofencingRequest geofencingRequest;
public Bridge(Context ctx)
{
context = ctx;
geofencingClient = new GeofencingClient(context);
geofenceList = new ArrayList<>();
builder = new GeofencingRequest.Builder();
}
public Bridge(Context ctx, String contentTitle, String contentText, String description)
{
context = ctx;
notificationContentTitle = contentTitle;
notificationContentText = contentText;
notificationDescription = description;
geofencingClient = new GeofencingClient(context);
geofenceList = new ArrayList<>();
builder = new GeofencingRequest.Builder();
}
public void addGeoFence(String id, double latitude, double longitude)
{
geofenceList.add(new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId(id)
.setCircularRegion(
latitude,
longitude,
300
)
.setNotificationResponsiveness(5000)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT |
Geofence.GEOFENCE_TRANSITION_DWELL)
.setLoiteringDelay(1)
.build());
Log.i("TESTTAG", "GEOFENCE toegevoegd aan lijst");
Log.i("TESTTAG", geofenceList.toString());
}
@SuppressLint("MissingPermission")
public void GeoFenceCompleted()
{
Log.i("TESTTAG", "Completing Geofence");
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER | GeofencingRequest.INITIAL_TRIGGER_DWELL);
builder.addGeofences(geofenceList);
geofencingRequest = builder.build();
geofencingClient.addGeofences(geofencingRequest, getGeofencePendingIntent())
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
// Geofences added
// ...
Log.i("TESTTAG", "Geofences added");
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Failed to add geofences
// ...
Log.e("TESTTAG", "Couldn't add geofences");
}
});
}
public void removeAllGeoFences()
{
geofencingClient.removeGeofences(getGeofencePendingIntent())
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
// Geofences removed
// ...
Log.i("TESTTAG", "removed all geofences");
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// Failed to remove geofences
// ...
Log.e("TESTTAG", "Couldn't remove all geofences");
}
});
}
public void removeGeoFences(List<String> ids) {
geofencingClient.removeGeofences(ids);
}
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (geofencePendingIntent != null) {
Log.i("TESTTAG", "used old intent for GeofenceBroadcastReceiver");
return geofencePendingIntent;
}
Log.i("TESTTAG", "Created new intent for GeofenceBroadcastReceiver");
Intent intent = new Intent(context, GeofenceBroadcastReceiver.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
// calling addGeofences() and removeGeofences().
geofencePendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.
FLAG_UPDATE_CURRENT);
return geofencePendingIntent;
}
}
GeofenceBroadcastReceiver. java
package com.BrabantWater.mynativemodulegps;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofenceStatusCodes;
import com.google.android.gms.location.GeofencingEvent;
import java.util.List;
import androidx.core.app.NotificationCompat;
import static android.content.ContentValues.TAG;
import static androidx.core.content.ContextCompat.getSystemService;
public class GeofenceBroadcastReceiver extends BroadcastReceiver {
// ...
@Override
public void onReceive(Context context, Intent intent) {
Log.i("TESTTAG", "GEOFENCE received");
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
String errorMessage = GeofenceStatusCodes.getStatusCodeString(geofencingEvent.getErrorCode());
Log.e(TAG, errorMessage);
return;
}
// Get the transition type.
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT || geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Get the transition details as a String.
String geofenceTransitionDetails = triggeringGeofences.toString();
// Send notification and log the transition details.
sendNotification(geofenceTransitionDetails, context);
Log.i(TAG, geofenceTransitionDetails);
} else {
// Log the error.
Log.e(TAG, "Transition Error");
}
}
private void sendNotification(String geofenceTransitionDetails, Context context)
{
Log.i("TESTTAG", "GEOFENCE triggered");
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "Brabant_Water_GO")
.setContentTitle(Bridge.notificationContentTitle)
.setContentText(Bridge.notificationContentText)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Brabant_Water_GO";
String description = Bridge.notificationDescription;
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel("Brabant_Water_GO", name, importance);
channel.setDescription(description);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = getSystemService(context, NotificationManager.class);
notificationManager.createNotificationChannel(channel);
notificationManager.notify(10000, builder.build());
Log.i("TESTTAG", "notification Shown");
}
}
}
Манифест выглядит так:
AndroidManifest. xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.BrabantWater.mynativemodulegps">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application>
<receiver android:name=".GeofenceBroadcastReceiver" />
</application>
</manifest>
На стороне Unity я создал класс, который вызывает методы моста и изменил манифест, чтобы убедиться, что я получил доступ к местоположению.
AndroidCaller.cs
#if UNITY_ANDROID
//basic imports.
using UnityEngine;
public class AndroidCaller : MonoBehaviour
{
AndroidJavaClass unityPlayerClass;
AndroidJavaObject unityActivity;
AndroidJavaObject bridge;
object[] parameters;
void Start()
{
//just done for testing purposes
CallNativePlugin();
}
//method that calls our native plugin.
public void CallNativePlugin()
{
// Retrieve the UnityPlayer class.
unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
// Retrieve the UnityPlayerActivity object ( a.k.a. the current context )
unityActivity = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");
// Setup the parameters we want to send to our native plugin.
parameters = new object[4];
parameters[0] = unityActivity;
parameters[1] = "Title";
parameters[2] = "ContentText";
parameters[3] = "Description";
// Retrieve the "Bridge" from our native plugin.
// ! Notice we define the complete package name.
bridge = new AndroidJavaObject("com.BrabantWater.mynativemodulegps.Bridge", parameters);
// Call addGeoFence in bridge, with our parameters.
bridge.Call("addGeoFence", "id", 51.461059, 4.310962);
bridge.Call("addGeoFence", "id2", 51.451459, 4.329905);
bridge.Call("GeoFenceCompleted");
}
}
#endif
AndroidManifest. xml (в Unity project)
<?xml version="1.0" encoding="utf-8"?>
<!-- GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
xmlns:tools="http://schemas.android.com/tools">
<application>
<receiver android:name="com.BrabantWater.mynativemodulegps.GeofenceBroadcastReceiver" />
<activity android:name="com.unity3d.player.UnityPlayerActivity"
android:theme="@style/UnityThemeSelector">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
</application>
<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.CAMERA" />
</manifest>
После большого количества тестов с использованием logcat я не мог понять, почему onReceive никогда не запускался. Вот так я и застрял.