背景
最近遇到了这样一个需求:实现一个实时定位功能,并将定位到的信息上传至服务器。
思路
首先实现一个长生命周期的Service
服务;
其次通过BroadcastReceiver
监听系统广播,当Service
被杀死的时候重新启动服务;
然后采用百度地图进行定时定位;
最后使用SharedPreferences
将定位信息过滤后通过WebSocket
上传至服务器。
实现
Service服务
public class LocateService extends Service {
private boolean isRun = true;
private MapLocationHelper location;
private SharedPreferencesHelper sp;
private WebSocket socket;
private Gson mGson;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
initData();
return START_STICKY;
}
/**
* 初始化数据
*/
private void initData() {
isRun = true;
location = MapLocationHelper.getInstance();
sp = SharedPreferencesHelper.getInstance();
socket = WebSocket.getInstance();
if (mGson == null) {
mGson = new Gson();
}
thread.start();
}
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (isRun) {
try {
Message msg = new Message();
msg.what = 1;
mHandler.sendMessage(msg);
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
if (location.isLocation() == false) {
location.startLocation();
}
String longitude = String.valueOf(location.getLongitude());
String latitude = String.valueOf(location.getLatitude());
if (location.getLongitude() != 0.0 && location.getLatitude() != 0.0) {
if (!sp.get("longitude", "").equals(longitude)
|| !sp.get("latitude", "").equals(latitude)) {
sp.put("longitude", longitude);
sp.put("latitude", latitude);
String str = mGson.toJson(new Format(200,
new Locate(longitude, latitude)));
socket.sendMessage(str);
}
}
break;
}
}
};
/**
* 重启服务
*/
private void restartService() {
if (!ServiceUtil.isServiceRunning(this, Constant.SERVICE_NAME)) {
isRun = false;
MapLocationHelper.getInstance().stopLocation();
WebSocket.getInstance().closeConnect();
SharedPreferencesHelper.getInstance().clear();
Intent intent = new Intent(this, LocateService.class);
this.startService(intent);
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
restartService();
}
@Override
public void onDestroy() {
super.onDestroy();
restartService();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
在Service
中主要对定位进行了管理,没有开始定位的时候开启定位;通过定位帮助类得到经纬度信息,采用SharedPreferences
来获取、更新定位信息,当前定位信息和保存信息不同时进行数据更新并将数据封转成json
格式字符串通过WebSocket
发送上传至服务器。
BroadcastReceiver监听系统广播
在AndroidManifest.xml
中注册广播:
<receiver
android:name=".receiver.LocateReceiver"
android:exported="true">
<intent-filter android:priority="1000">
<!-- 系统启动完成后会调用 -->
<action android:name="android.intent.action.BOOT_COMPLETED" />
<!-- 解锁完成后会调用 -->
<action android:name="android.intent.action.USER_PRESENT" />
<!-- 监听情景切换 -->
<action android:name="android.media.RINGER_MODE_CHANGED" />
<!-- 网络状态 -->
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
添加权限:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
- 1
- 2
在Application
中动态注册广播:
private void initReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
LocateReceiver receiver = new LocateReceiver();
registerReceiver(receiver, filter);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
广播监听:
public class LocateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_USER_PRESENT)
|| intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)
|| intent.getAction().equals(Intent.ACTION_TIME_TICK)
|| intent.getAction().equals(Intent.ACTION_SCREEN_OFF)
|| intent.getAction().equals(Intent.ACTION_SCREEN_ON)
|| intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
if (!ServiceUtil.isServiceRunning(context, Constant.SERVICE_NAME)) {
Intent service = new Intent(context, LocateService.class);
context.startService(service);
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
在BroadcastReceiver
中主要对系统的开机、解锁、系统时间、屏幕打开/关闭、网络状态等系统广播进行了监听,若当前Service
不在运行则重新启动Service
服务。
百度定位
public class MapLocationHelper {
private Context context;
private String province;// 省
private String city;// 市
private String county;// 区/县
private double longitude;// 经度
private double latitude;// 纬度
private String address;// 地址
private boolean isLocation = false;// 是否定位
private short mSpanTime = 2 * 1000;// 定位间隔
private LocationClient mLocationClient = null;
private MyBDLocationListener mMyBDLocationListener = null;// 接受百度地图定位的回调类
private OnLocationListener mLocationListener = null;// 自定义的定位回调接口, 主要是对百度地图定位的封装
private static MapLocationHelper instance = null;
public MapLocationHelper() {
this.context = MyApplication.getContext();
}
public static MapLocationHelper getInstance() {
if (instance == null) {
synchronized (MapLocationHelper.class) {
if (instance == null) {
instance = new MapLocationHelper();
}
}
}
return instance;
}
/**
* 启动定位
*/
public void startLocation() {
isLocation = true;
if (mLocationClient == null) {
mLocationClient = new LocationClient(context);
}
if (mMyBDLocationListener == null) {
mMyBDLocationListener = new MyBDLocationListener();
}
// 注册监听函数,当没有注册监听函数时,无法发起网络请求
mLocationClient.registerLocationListener(mMyBDLocationListener);
// LocationClientOption 该类用来设置定位SDK的定位方式 设置定位参数
LocationClientOption option = new LocationClientOption();
option.setOpenGps(true);// 可选,默认false,设置是否使用gps
option.setAddrType("all");// 返回的定位结果包含地址信息
option.disableCache(false);// 禁止启用缓存定位
option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);//设置定位模式
option.setCoorType("bd09ll");//返回的定位结果是百度经纬度,默认值gcj02
option.setScanSpan(mSpanTime);// 设置发起定位请求的间隔时间:单位ms
option.setIsNeedAddress(true);//返回的定位结果包含地址信息
option.setNeedDeviceDirect(true);//返回的定位结果包含手机机头的方向
// 设置定位参数
mLocationClient.setLocOption(option);
// start:启动定位SDK stop:关闭定位SDK 调用stop之后,设置的参数LocationClientOption仍然保留
mLocationClient.start();
mLocationClient.requestLocation();
}
/**
* 停止定位
*/
public void stopLocation() {
if (mLocationClient != null) {
if (mMyBDLocationListener != null) {
mLocationClient.unRegisterLocationListener(mMyBDLocationListener);
}
mLocationClient.stop();
mLocationClient = null;
isLocation = false;
} else {
isLocation = false;
}
}
/**
* 实现百度地图的接口
*/
public class MyBDLocationListener extends BDAbstractLocationListener {
@Override
public void onReceiveLocation(BDLocation location) {
if (null != location && location.getLocType() != BDLocation.TypeServerError) {
if (mLocationListener != null) {
mLocationListener.onReceiveLocation(location);
}
isLocation = true;
// 获得当前定位信息
StringBuffer sb = new StringBuffer();
province = location.getProvince();// 省
city = location.getCity();// 市
county = location.getDistrict();// 区/县
longitude = location.getLongitude();// 经度
latitude = location.getLatitude();// 纬度
address = location.getAddrStr();// 详细地址
sb.append("省:" + province + "\n");
sb.append("市:" + city + "\n");
sb.append("区/县:" + county + "\n");
sb.append("经度:" + longitude + "\n");
sb.append("纬度:" + latitude + "\n");
sb.append("详细地址:" + address);
} else {
stopLocation();
}
}
}
/**
* 经度
*/
public double getLongitude() {
return longitude;
}
/**
* 纬度
*/
public double getLatitude() {
return latitude;
}
/**
* 是否定位
*/
public boolean isLocation() {
return isLocation;
}
/**
* 定位间隔时间
*/
public void setmSpanTime(short mSpanTime) {
this.mSpanTime = mSpanTime;
}
/**
* 手动触发一次定位请求
*/
public void requestLocClick() {
mLocationClient.requestLocation();
}
/**
* 设置自定义的监听器
*/
public void setOnLocationListener(OnLocationListener locationListener) {
this.mLocationListener = locationListener;
}
public interface OnLocationListener {
void onReceiveLocation(BDLocation location);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
百度地图的集成详见: 百度定位
通过继承BDAbstractLocationListener
接口实现onReceiveLocation
方法,在其方法中获取到经纬度等相关信息。
WebSocket连接
public class WebSocket {
private static WebSocket instance = null;
private WebSocketConnection mConnect;
private static final String wsUri = "ws://112.74.43.157:3002";
private String mMsg = "";
private WebSocket() {
webSocketConnect(false);
}
public static WebSocket getInstance() {
if (instance == null) {
synchronized (WebSocket.class) {
if (instance == null) {
instance = new WebSocket();
}
}
}
return instance;
}
/**
* WebSocket连接,接收服务器消息
*/
private void webSocketConnect(boolean isMsg) {
if (mConnect == null) {
mConnect = new WebSocketConnection();
}
try {
mConnect.connect(wsUri, new WebSocketHandler() {
@Override
public void onOpen() {
super.onOpen();
// 连接启动时回调
LogUtil.e("Connect To: " + wsUri);
}
@Override
public void onTextMessage(String payload) {
super.onTextMessage(payload);
// 接收到消息后回调
LogUtil.e("接收消息:" + payload);
}
@Override
public void onClose(int code, String reason) {
super.onClose(code, reason);
// 关闭连接时候的回调
webSocketConnect(false);
Log.i(TAG, "Connect Close......");
}
});
} catch (WebSocketException e) {
e.printStackTrace();
}
if (isMsg) {
sendMessage(mMsg);
}
}
/**
* 发送消息
*/
public void sendMessage(String msg) {
this.mMsg = msg;
LogUtil.e("发送消息内容:" + msg);
if (mConnect != null && mConnect.isConnected()) {
mConnect.sendTextMessage(msg);
} else {
webSocketConnect(true);
}
}
/**
* 断开连接
*/
public void closeConnect() {
if (mConnect != null && mConnect.isConnected()) {
mConnect.disconnect();
mConnect = null;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
通过WebSocket
将封装好的json
字符串发送上传至服务器。
WebSocket的利弊:
优点:与Socket
相比可以节省额外的端口占用,直接使用一个公网域名访问,另外协议对报文的流量消耗做了优化。
缺点:毕竟WebSocket
底层也是Socket
连接,因而当大并发用户连接时也会消耗较多资源。
其他设置
1、设置Android当前任务管理器不显示应用进程
在Android
框架中维护了一个叫“最近运行”的应用程序列表用以方便进行应用程序的切换。其通过AndroidManifest.xml
中的activity
标签定义了excludeFromRecents
这样一个属性,该属性表示应用程序是否将Activity
从最近运行的应用程序列表中移除。android:excludeFromRecents="true"
表示移除,反之则不移除,默认值为false
。此属性设置的前提是该Activity
是某个任务的根Activity
。
<activity
android:name=".MainActivity"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2、设置Android隐藏自己的APP应用程序
通过修改AndroidManifest.xml
清单文件来达到隐藏自身应用的目的,在Activity
标签<intent-filter>
节点下添加<data/>
节点。
<activity
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<data
android:host="com.example.hideapp"
android:scheme="access" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
以上还可以这样表示:
<activity
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<data android:scheme="access" />
<data android:host="com.example.hideapp" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
项目地址 ☞ 传送门
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
</div>