在手机中最常用的定位技术是GPS。
但是国内的应用并不能直接获取真实的地理信息。
火星坐标系统
各个坐标系的介绍
知识点
- service
- listener
- location
高德SDK
直接使用高德的SDK可以省去不少麻烦。但是在使用SDK的时候需要key store。
文档很清楚高德SDK文档,通过简单的设置即可获取需要的GPS信息。
但是要做到长时间监听,还需要AlarmManager来保持CPU的工作。
监听位置信息
主要有4个步骤:
- 配置AndroidManifest.xml
- 初始化定位
- 配置参数并启动定位
- 获取定位结果
class GpsService : Service() {
private var mLocationClient: AMapLocationClient? = null
//声明定位回调监听器
private var mLocationListener: AMapLocationListener = AMapLocationListener { amapLocation ->
if (amapLocation != null) {
if (amapLocation.errorCode == 0) {
//解析定位结果
val latitude = amapLocation.latitude;//获取纬度
val longitude = amapLocation.longitude;//获取经度
PostDataTask().execute(Pair(latitude,longitude))
Log.i("location","acc : ${amapLocation.accuracy}")
Log.i("location", "altitude : ${amapLocation.altitude}")
} else
Log.w("location","error code: ${amapLocation.errorInfo}")
}
}
override fun onCreate() {
mLocationClient = AMapLocationClient(applicationContext)
//设置定位回调监听
mLocationClient!!.setLocationListener(mLocationListener)
val option = AMapLocationClientOption()
/**
* 设置定位场景,目前支持三种场景(签到、出行、运动,默认无场景)
*/
option.locationPurpose = AMapLocationClientOption.AMapLocationPurpose.Transport
option.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy
//启动定位
mLocationClient!!.startLocation()
}
}
位置存储
安卓不能直接使用MySQL。既然用了高德的服务,干脆存储也使用云存储。它提供了几个十分方便的API。
在安卓中不能再主线程中调用Http请求。需要TaskAync来辅助完成一次请求。
private class PostDataTask : AsyncTask<Pair<Double, Double>, Int, Boolean>() {
override fun doInBackground(vararg args: Pair<Double, Double>): Boolean {
for (arg in args) {
val longitude = arg.first;//获取纬度
val latitude= arg.second;//获取经度
val now = System.currentTimeMillis() / 1000
val params =getDataString(mapOf(
"key" to "2ad3b7f65c549d9155b7325c2d2c13b4",
"tableid" to "5a80175d2376c17f0129f54c",
"data" to " {\"_name\":\"$now\",\"_location\":\"$latitude,$longitude\" }"))
Log.d("location",params)
var url = URL("http://yuntuapi.amap.com/datamanage/data/create")
val urlConnection = url.openConnection() as HttpURLConnection
urlConnection.instanceFollowRedirects = false;
urlConnection.doOutput = true
urlConnection.requestMethod = "POST";
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("charset", "utf-8");
urlConnection.setRequestProperty("Content-Length", Integer.toString(params.length));
urlConnection.useCaches = false;
try {
DataOutputStream(urlConnection.outputStream).write((params).toByteArray())
urlConnection.connect()
if (urlConnection.responseCode in 200..299) {
val br = (InputStreamReader(urlConnection.inputStream).readText())
Log.i("response", br)
}
} catch (e: java.io.IOException) {
e.printStackTrace()
}
urlConnection.disconnect()
}
return true;
}
private fun getDataString(params: Map<String, String>): String {
val result = StringBuilder()
var first = true
for (entry in params.keys) {
if (first)
first = false;
else
result.append("&");
result.append(URLEncoder.encode(entry, "UTF-8"));
result.append("=");
result.append(URLEncoder.encode(params[entry], "UTF-8"));
}
return result.toString();
}
}
在使用该接口的时候,出现一个错误:
{
"info": "参数缺失或格式非法",
"infocode": "30001",
"status": 0
}
开始还以为是urlencode的不对,后来各种寻找也没发现问题。最终发现是由于经纬度的顺序写错了,不支持地区就算了,返回这么一个信息很头疼啊。
AlarmManager
其实这段代码非常简单,在启动service的时候创建一个alarm,然后在alarm的接收器里再次启动service。从而达到不被关闭的效果。
//service 类中
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.i(TAG, "onStartCommand() executed")
//Toast.makeText(this, "onStartCommand", Toast.LENGTH_SHORT).show();
val manager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmTime = 20 * 1000 // 定时10s
val triggerAtTime = SystemClock.elapsedRealtime() + alarmTime
val i = Intent(this, AlarmReceiver::class.java)
val pi = PendingIntent.getBroadcast(this, 0, i, 0)
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi)
return super.onStartCommand(intent, Service.START_FLAG_REDELIVERY, startId)
}
Reciver
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("alarm","trigger alarm");
Intent i = new Intent(context, GpsService.class);
context.startService(i);
}
}
开机启动
public class BootBroadcastReceiver extends BroadcastReceiver {
static final String ACTION = "android.intent.action.BOOT_COMPLETED";
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION)) {
Intent mainActivityIntent = new Intent(context, GpsService.class); // 要启动的Activity
mainActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mainActivityIntent);
}
}
}
进程还是被干掉了怎么办?
流氓软件的必备技能。但还是有时候需要这么一个东西。比如我的应用是一个定制化的软件。这个时候整个系统都应该围绕我的APP去工作。比如一个基于安卓的机器人系统。在空闲的时候允许用户打开别的应用玩玩游戏什么的,但是我的后台系统必须要时刻保持工作状态。
试了里面的几种办法,最后还是没有在我的魅蓝2,yunOS 3.1.6上达到开机启动加后台常驻。
果然还是大厂技术高。我手机里的高德、微信、金山词霸、cortana都做到了。囧。但是除了cortana其余的都是2个进程在跑。微信和高德的是可以被杀掉,并不会重启。而金山词霸、cortana可以做到重启服务。两者有一个共同点,就是都能修改锁屏内容。可能要做到流氓就必须抱大腿,跟特权的系统服务绑定在一起。