电子围栏,校园跑的常客,也是定位打卡必不可少的东西
主要代码:
创建电子围栏代码
// 添加多边形地理围栏(兼容2023版SDK)
private void addPolygon(String fenceName, List<LatLng> points) {
if (points == null || points.size() < 3) {
Toast.makeText(this, "多边形至少需要3个点", Toast.LENGTH_SHORT).show();
return;
}
// 生成唯一围栏ID
String fenceId = "polygon_" + mCustomID++;
// 转换坐标点格式
List<DPoint> dPoints = new ArrayList<>();
for (LatLng point : points) {
dPoints.add(new DPoint(point.latitude, point.longitude));
}
// 正确调用方式(关键修改点)
mClientInAndStayAction.addGeoFence(
dPoints, // 参数1: 多边形顶点坐标(必须闭合)
fenceId // 参数2: 自定义围栏ID
);
// 绘制多边形到地图
PolygonOptions options = new PolygonOptions()
.addAll(points)
.fillColor(0x5566CCFF) // 填充色(带透明度)
.strokeColor(Color.BLUE) // 边框色
.strokeWidth(5); // 边框宽度(px)
Polygon polygon = aMap.addPolygon(options);
mCustomEntitys.put(fenceId, polygon);
// 设置围栏监听(新增)
mClientInAndStayAction.setGeoFenceListener(new GeoFenceListener() {
@Override
public void onGeoFenceCreateFinished(List<GeoFence> geoFences, int errorCode, String msg) {
if (errorCode == GeoFence.ADDGEOFENCE_SUCCESS) {
runOnUiThread(() ->
Toast.makeText(MapActivity.this, fenceName + "围栏创建成功", Toast.LENGTH_SHORT).show()
);
}
}
});
// 移动视角到围栏中心
aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(points.get(0), 15));
}
//这里通过数据库进行添加电子围栏的多边形
private void addTiananmenFence() {
List<LatLng> tiananmenArea = new ArrayList<>();
// 添加闭合坐标点(示例数据)
tiananmenArea.add(new LatLng(39.908722, 116.397499)); // 天安门中心
tiananmenArea.add(new LatLng(39.910000, 116.395000)); // 西北角
tiananmenArea.add(new LatLng(39.909500, 116.400000)); // 东北角
tiananmenArea.add(tiananmenArea.get(0)); // 闭合多边形
addPolygon("天安门核心区", tiananmenArea);
}
创建广播(当定位在定位外时触发)
// 修改onStart方法中的注册逻辑
@Override
protected void onStart() {
super.onStart();
// 创建带安全标志的广播过滤器
IntentFilter filter = new IntentFilter(GEOFENCE_BROADCAST_ACTION);
// 适配Android 13+广播权限要求
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(
mGeoFenceReceiver,
filter,
Context.RECEIVER_NOT_EXPORTED // 禁止外部应用访问
);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
registerReceiver(mGeoFenceReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
}
}
createNotificationChannel();
}
@Override
protected void onStop() {
super.onStop();
// 取消注册接收器
unregisterReceiver(mGeoFenceReceiver);
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "地理围栏警报";
String description = "电子围栏越界通知";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(ALERT_CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
发送日志
// 在MapActivity中
private final BroadcastReceiver mGeoFenceReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!isValidBroadcast(intent)) return;
Bundle bundle = intent.getExtras();
if (bundle == null) return;
// 解析基础数据
int status = bundle.getInt(GeoFence.BUNDLE_KEY_FENCESTATUS);
String fenceId = bundle.getString(GeoFence.BUNDLE_KEY_FENCEID);
GeoFence fence = bundle.getParcelable(GeoFence.BUNDLE_KEY_FENCE);
// 分发事件处理
handleGeoFenceEvent(context, status, fenceId, fence);
}
private boolean isValidBroadcast(Intent intent) {
return intent != null &&
GEOFENCE_BROADCAST_ACTION.equals(intent.getAction());
}
private void handleGeoFenceEvent(Context context, int status,
String fenceId, GeoFence fence) {
switch (status) {
case GeoFence.STATUS_IN:
handleEnter(context, fenceId);
break;
case GeoFence.STATUS_OUT:
handleExit(context, fence);
break;
case GeoFence.STATUS_STAYED:
handleStay(fence);
break;
}
}
private void handleEnter(Context context, String fenceId) {
// 记录日志
GeoFenceLogger.logEnter(fenceId);
// UI提示
Toast.makeText(context, "进入监控区域", Toast.LENGTH_SHORT).show();
}
private void handleExit(Context context, GeoFence fence) {
if (fence == null) return;
// 记录日志
GeoFenceLogger.logExit(fence.getFenceId());
// 触发警报
String alertMsg = "⚠️ 已离开安全区域: " + fence.getFenceId();
Toast.makeText(context, alertMsg, Toast.LENGTH_LONG).show();
sendAlertNotification(context, alertMsg);
}
private void handleStay(GeoFence fence) {
if (fence == null) return;
// 记录日志
GeoFenceLogger.logStay(fence.getFenceId(), fence.getExpiration());
}
};
private void sendAlertNotification(Context context, String message) {
NotificationManager manager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
// 构建通知
Notification notification = new NotificationCompat.Builder(context, ALERT_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_alert)
.setContentTitle("区域越界警告")
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setAutoCancel(true)
.build();
// 显示通知
if (manager != null) {
manager.notify(new Random().nextInt(1000), notification);
}
}
package com.example.gdmap;
import android.util.Log;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
//用来记录学生何时进出入电子围栏
//这部分可将log打印的日志部分写为string放在教师端的文本框中
// GeoFenceLogger.java
public class GeoFenceLogger {
private static final String TAG = "GeoFenceTracker";
private static final SimpleDateFormat DATE_FORMAT =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault());
// 记录进入事件
public static void logEnter(String fenceId) {
String timestamp = DATE_FORMAT.format(new Date());
Log.d(TAG, String.format("[进入] 围栏ID: %s | 时间: %s", fenceId, timestamp));
}
// 记录离开事件
public static void logExit(String fenceId) {
String timestamp = DATE_FORMAT.format(new Date());
Log.d(TAG, String.format("[离开] 围栏ID: %s | 时间: %s", fenceId, timestamp));
}
// 记录停留事件
public static void logStay(String fenceId, long duration) {
String timestamp = DATE_FORMAT.format(new Date());
Log.d(TAG, String.format("[停留] 围栏ID: %s | 时间: %s | 持续时间: %dms",
fenceId, timestamp, duration));
}
}
activity代码
package com.example.gdmap;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.amap.api.fence.GeoFence;
import com.amap.api.fence.GeoFenceListener;
import com.amap.api.maps.AMap;
import com.amap.api.maps.CameraUpdateFactory;
import com.amap.api.maps.MapView;
import com.amap.api.maps.MapsInitializer;
import com.amap.api.maps.model.CircleOptions;
import com.amap.api.maps.model.LatLng;
import com.amap.api.maps.model.Marker;
import com.amap.api.maps.model.MarkerOptions;
import com.amap.api.maps.model.MyLocationStyle;
import com.amap.api.fence.GeoFence;
import com.amap.api.fence.GeoFenceClient;
import com.amap.api.maps.model.Polygon;
import com.amap.api.maps.model.PolygonOptions;
import com.amap.api.maps.model.Circle;
import com.amap.api.location.DPoint; // 注意:高德使用DPoint而非LatLng
import android.Manifest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
public class MapActivity extends AppCompatActivity implements
AMap.OnMarkerClickListener,
AMap.InfoWindowAdapter {
private MapView mMapView;
// 地理围栏相关
private GeoFenceClient mClientInAndStayAction;
private int mCustomID = 1000; // 围栏ID种子值
private HashMap<String, Object> mCustomEntitys = new HashMap<>(); // 存储围栏图形对象
private static final String GEOFENCE_BROADCAST_ACTION = "com.example.gdmap.geofence";
private AMap aMap;
private static final int PERMISSION_REQUEST_CODE = 1001;
// 在MapActivity类中添加以下代码
private static final String TAG = "GeoFenceTracker";
// 定义通知渠道ID
private static final String ALERT_CHANNEL_ID = "geo_fence_alerts";
private static final int REQUEST_CODE = 1001;
private void requestLocationPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_CODE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults); // 关键调用
if (requestCode == REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initializeMap();
} else {
Toast.makeText(this, "权限被拒绝,部分功能受限", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
// 初始化地图
MapsInitializer.updatePrivacyShow(this, true, true);
MapsInitializer.updatePrivacyAgree(this, true);
mMapView = findViewById(R.id.map);
mMapView.onCreate(savedInstanceState);
initializeMap();
setupLocationStyle();
addMarkers();
addTiananmenFence();
}
private void initializeMap() {
if (aMap == null) {
aMap = mMapView.getMap();
mClientInAndStayAction = new GeoFenceClient(getApplicationContext());
mClientInAndStayAction.createPendingIntent(GEOFENCE_BROADCAST_ACTION);
mClientInAndStayAction.setActivateAction(
GeoFenceClient.GEOFENCE_IN | GeoFenceClient.GEOFENCE_STAYED
);
// 设置信息窗口适配器
aMap.setInfoWindowAdapter(this);
// 设置Marker点击监听器
aMap.setOnMarkerClickListener(this);
// 初始地图位置和缩放级别
aMap.animateCamera(CameraUpdateFactory.zoomTo(16));
aMap.getUiSettings().setScaleControlsEnabled(true);
}
}
private void setupLocationStyle() {
MyLocationStyle myLocationStyle = new MyLocationStyle();
myLocationStyle.interval(6000);
myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_SHOW);
aMap.setMyLocationStyle(myLocationStyle);
aMap.setMyLocationEnabled(true);
}
//这里可以通过数据库进行添加点
private void addMarkers() {
// 使用自定义方法创建带数据的Marker
addMarker(28.711666, 115.826600, "AAA水果批发商小王", "苹果");
addMarker(28.711667, 115.826750, "AAA水果批发商小李", "芒果");
addMarker(28.711680, 115.826450, "AAA水果批发商小张", "草莓");
// 东北地区
addMarker(45.8038, 126.5340, "哈尔滨水果批发商老刘", "蓝莓"); // 黑龙江哈尔滨
addMarker(43.8171, 125.3235, "长春水果批发商孙姐", "人参果"); // 吉林长春
addMarker(41.8354, 123.4299, "沈阳水果批发商老金", "南果梨"); // 辽宁沈阳
// 华北地区
addMarker(39.9042, 116.4074, "北京新发地批发市场", "平谷大桃"); // 北京
addMarker(37.8706, 112.5489, "太原水果批发商老陈", "沙金红杏"); // 山西太原
addMarker(38.0428, 114.5149, "石家庄水果批发商赵总", "赞皇大枣"); // 河北石家庄
// 华东地区
addMarker(31.2304, 121.4737, "上海西郊国际批发", "南汇水蜜桃"); // 上海
addMarker(30.2595, 120.2194, "杭州水果批发商王姐", "塘栖枇杷"); // 浙江杭州
addMarker(32.0603, 118.7969, "南京众彩批发市场", "固城湖螃蟹"); // 江苏南京
addMarker(36.6512, 117.1201, "济南堤口批发市场", "烟台樱桃"); // 山东济南
// 华中地区
addMarker(30.5951, 114.2999, "武汉光霞果批市场", "梁子湖螃蟹"); // 湖北武汉
addMarker(28.1941, 112.9723, "长沙红星批发市场", "炎陵黄桃"); // 湖南长沙
addMarker(34.7473, 113.6253, "郑州万邦物流城", "灵宝苹果"); // 河南郑州
// 华南地区
addMarker(23.1291, 113.2644, "广州江南果菜市场", "增城荔枝"); // 广东广州
addMarker(20.0444, 110.1999, "海口南北水果市场", "文昌椰子"); // 海南海口
addMarker(22.8176, 108.3663, "南宁海吉星市场", "百色芒果"); // 广西南宁
// 西北地区
addMarker(34.3416, 108.9398, "西安雨润批发市场", "周至猕猴桃"); // 陕西西安
addMarker(36.0611, 103.8343, "兰州大青山市场", "白兰瓜"); // 甘肃兰州
addMarker(43.8256, 87.6168, "乌鲁木齐九鼎市场", "哈密瓜"); // 新疆乌鲁木齐
// 西南地区
addMarker(29.6535, 91.1705, "拉萨药王山市场", "林芝苹果"); // 西藏拉萨
addMarker(25.0433, 102.7062, "昆明金马正昌市场", "蒙自石榴"); // 云南昆明
addMarker(26.6470, 106.6302, "贵阳石板哨市场", "修文猕猴桃"); // 贵州贵阳
addMarker(30.5728, 104.0668, "成都濛阳农批市场", "攀枝花芒果"); // 四川成都
// 特别行政区
addMarker(22.3193, 114.1694, "香港长沙湾市场", "菲律宾香蕉"); // 香港
}
private void addMarker(double lat, double lng, String title, String phone) {
LatLng position = new LatLng(lat, lng);
aMap.addMarker(new MarkerOptions()
.position(position)
.title(title)
.snippet("售卖商品: " + phone)
.draggable(false));
}
// 实现信息窗口布局
@Override
public View getInfoWindow(Marker marker) {
View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_window, null);
renderInfoWindow(marker, infoWindow);
return infoWindow;
}
@Override
public View getInfoContents(Marker marker) {
return null; // 使用默认信息窗口背景时返回null
}
private void renderInfoWindow(Marker marker, View view) {
TextView title = view.findViewById(R.id.info_window_title);
TextView content = view.findViewById(R.id.info_window_content);
title.setText(marker.getTitle());
content.setText(marker.getSnippet());
}
// 处理Marker点击事件
@Override
public boolean onMarkerClick(Marker marker) {
// 显示信息窗口(第二个参数为是否强制使用默认布局)
marker.showInfoWindow();
return true; // 消费点击事件
}
// 添加多边形地理围栏(兼容2023版SDK)
private void addPolygon(String fenceName, List<LatLng> points) {
if (points == null || points.size() < 3) {
Toast.makeText(this, "多边形至少需要3个点", Toast.LENGTH_SHORT).show();
return;
}
// 生成唯一围栏ID
String fenceId = "polygon_" + mCustomID++;
// 转换坐标点格式
List<DPoint> dPoints = new ArrayList<>();
for (LatLng point : points) {
dPoints.add(new DPoint(point.latitude, point.longitude));
}
// 正确调用方式(关键修改点)
mClientInAndStayAction.addGeoFence(
dPoints, // 参数1: 多边形顶点坐标(必须闭合)
fenceId // 参数2: 自定义围栏ID
);
// 绘制多边形到地图
PolygonOptions options = new PolygonOptions()
.addAll(points)
.fillColor(0x5566CCFF) // 填充色(带透明度)
.strokeColor(Color.BLUE) // 边框色
.strokeWidth(5); // 边框宽度(px)
Polygon polygon = aMap.addPolygon(options);
mCustomEntitys.put(fenceId, polygon);
// 设置围栏监听(新增)
mClientInAndStayAction.setGeoFenceListener(new GeoFenceListener() {
@Override
public void onGeoFenceCreateFinished(List<GeoFence> geoFences, int errorCode, String msg) {
if (errorCode == GeoFence.ADDGEOFENCE_SUCCESS) {
runOnUiThread(() ->
Toast.makeText(MapActivity.this, fenceName + "围栏创建成功", Toast.LENGTH_SHORT).show()
);
}
}
});
// 移动视角到围栏中心
aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(points.get(0), 15));
}
//这里通过数据库进行添加电子围栏的多边形
private void addTiananmenFence() {
List<LatLng> tiananmenArea = new ArrayList<>();
// 添加闭合坐标点(示例数据)
tiananmenArea.add(new LatLng(39.908722, 116.397499)); // 天安门中心
tiananmenArea.add(new LatLng(39.910000, 116.395000)); // 西北角
tiananmenArea.add(new LatLng(39.909500, 116.400000)); // 东北角
tiananmenArea.add(tiananmenArea.get(0)); // 闭合多边形
addPolygon("天安门核心区", tiananmenArea);
}
// 修改onStart方法中的注册逻辑
@Override
protected void onStart() {
super.onStart();
// 创建带安全标志的广播过滤器
IntentFilter filter = new IntentFilter(GEOFENCE_BROADCAST_ACTION);
// 适配Android 13+广播权限要求
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(
mGeoFenceReceiver,
filter,
Context.RECEIVER_NOT_EXPORTED // 禁止外部应用访问
);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
registerReceiver(mGeoFenceReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
}
}
createNotificationChannel();
}
@Override
protected void onStop() {
super.onStop();
// 取消注册接收器
unregisterReceiver(mGeoFenceReceiver);
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "地理围栏警报";
String description = "电子围栏越界通知";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(ALERT_CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
// 在MapActivity中
private final BroadcastReceiver mGeoFenceReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!isValidBroadcast(intent)) return;
Bundle bundle = intent.getExtras();
if (bundle == null) return;
// 解析基础数据
int status = bundle.getInt(GeoFence.BUNDLE_KEY_FENCESTATUS);
String fenceId = bundle.getString(GeoFence.BUNDLE_KEY_FENCEID);
GeoFence fence = bundle.getParcelable(GeoFence.BUNDLE_KEY_FENCE);
// 分发事件处理
handleGeoFenceEvent(context, status, fenceId, fence);
}
private boolean isValidBroadcast(Intent intent) {
return intent != null &&
GEOFENCE_BROADCAST_ACTION.equals(intent.getAction());
}
private void handleGeoFenceEvent(Context context, int status,
String fenceId, GeoFence fence) {
switch (status) {
case GeoFence.STATUS_IN:
handleEnter(context, fenceId);
break;
case GeoFence.STATUS_OUT:
handleExit(context, fence);
break;
case GeoFence.STATUS_STAYED:
handleStay(fence);
break;
}
}
private void handleEnter(Context context, String fenceId) {
// 记录日志
GeoFenceLogger.logEnter(fenceId);
// UI提示
Toast.makeText(context, "进入监控区域", Toast.LENGTH_SHORT).show();
}
private void handleExit(Context context, GeoFence fence) {
if (fence == null) return;
// 记录日志
GeoFenceLogger.logExit(fence.getFenceId());
// 触发警报
String alertMsg = "⚠️ 已离开安全区域: " + fence.getFenceId();
Toast.makeText(context, alertMsg, Toast.LENGTH_LONG).show();
sendAlertNotification(context, alertMsg);
}
private void handleStay(GeoFence fence) {
if (fence == null) return;
// 记录日志
GeoFenceLogger.logStay(fence.getFenceId(), fence.getExpiration());
}
};
private void sendAlertNotification(Context context, String message) {
NotificationManager manager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
// 构建通知
Notification notification = new NotificationCompat.Builder(context, ALERT_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_alert)
.setContentTitle("区域越界警告")
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setAutoCancel(true)
.build();
// 显示通知
if (manager != null) {
manager.notify(new Random().nextInt(1000), notification);
}
}
// 生命周期方法
@Override
protected void onResume() {
super.onResume();
mMapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mMapView.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
mMapView.onDestroy();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
}
}
标志.XML
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF0000"
android:pathData="M12,2L3,20h18L12,2zM13,16h-2v-2h2v2zm0-4h-2V8h2v4z"/>
</vector>
效果图: