在手机中最常用的定位技术是GPS。
但是国内的应用并不能直接获取真实的地理信息。
火星坐标系统
各个坐标系的介绍

知识点

  1. service
  2. listener
  3. location

高德SDK

直接使用高德的SDK可以省去不少麻烦。但是在使用SDK的时候需要key store

文档很清楚高德SDK文档,通过简单的设置即可获取需要的GPS信息。
但是要做到长时间监听,还需要AlarmManager来保持CPU的工作。

监听位置信息

主要有4个步骤:

  1. 配置AndroidManifest.xml
  2. 初始化定位
  3. 配置参数并启动定位
  4. 获取定位结果
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);
    }
}

开机启动

BootBroadcastReceiver

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去工作。比如一个基于安卓的机器人系统。在空闲的时候允许用户打开别的应用玩玩游戏什么的,但是我的后台系统必须要时刻保持工作状态。

service 进程保护机制

试了里面的几种办法,最后还是没有在我的魅蓝2,yunOS 3.1.6上达到开机启动加后台常驻。
果然还是大厂技术高。我手机里的高德、微信、金山词霸、cortana都做到了。囧。但是除了cortana其余的都是2个进程在跑。微信和高德的是可以被杀掉,并不会重启。而金山词霸、cortana可以做到重启服务。两者有一个共同点,就是都能修改锁屏内容。可能要做到流氓就必须抱大腿,跟特权的系统服务绑定在一起。

载入中...
erow erow

17 声望