Service+WebSocket实现实时定位

背景

最近遇到了这样一个需求:实现一个实时定位功能,并将定位到的信息上传至服务器。

思路

首先实现一个长生命周期的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>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值