android动态获取gps信息后本地文件保存问题总结

一、

针对android项目中加速度传感检测手机方位,运行过程会时刻记录手机GPSSRN详细数据,实时显示当前获取的卫星的PIDC/N,实时显示手机终端系统时间的需求,对于启动后台服务采集gps数据同时要把数据实时保存到本地文件中,要在gps状态改变时更新gps信息到界面上显示并且记录下来保存到本地的实时性冲突,对频繁读写文件还有界面更新不能让数据出现时间不连续。

GPS项目的初始代码中逐步发现无法进行定时器更新执行,对界面显示数据的分离问题还有获取卫星列表失败、数据保存不连续等问题,造成获取gps信息不正常。

二、

在定时器无法更新执行任务的问题中原来代码使用AlarmManager服务,但是在设定重复任务的时候发现设定好了interval时间后,任务并没有按期望启动。从API 19开始,alarm的机制都是非准确传递,操作系统将会转换闹钟,来最小化唤醒和电池使用。有一些新的API会支持严格准确的传递,见setWindow(int, long, long, PendingIntent)setExact(int, long, PendingIntent)targetSdkVersionAPI 19之前应用仍将继续使用以前的行为,所有的闹钟在要求准确传递的情况下都会准确传递。setRepeating方法在API 19开始将会失去原来的效果。可以换个定时器timer来使用重复执行任务,或者重复执行循环定时AlarmManager的设置。对于界面中时间显示和gps数据更新不在一个流程下,可以进行模块分离,单独更新对应控件显示获取卫星列表信息失败,没有从gps状态监听器中获取,造成数据获取异常。需要在GpsStatus监听器的onGpsStatusChanged回调函数中的当卫星状态改变时来获取gps信息。获取gps信噪比数据不连续是因为服务被系统优化给杀死了,得保证采集数据的服务常驻不被系统停止。

三、

GPSAndroid系统中重要的组成部分,通过它可以衍生出众多的与位置相关的应用。AndroidGPS有一个专门的管理类,称为LocationManager,所有的GPS定位服务都由其对象产生并进行控制。首先需要明确的是,LocationManager类的对象获取并不是直接创建的,而是由系统提供的,具体来说,通过如下方法,为一个LocationManager对象建立一个对象引用:

 

LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

至此,我们可以用locationManager这个对象对任意有关GPS的功能进行操作了。下表列出了几个常用的成员方法:getAllProviders获取所有与设备关联的定位模块的列表;getBestProvider获取设定的标准(Criteria对象)中最适合的一个设备;getGpsStatus获取GPS当前状态;getLastKnownLocation获取最近一次的可用地点信息;isProviderEnabled判断参数所提及的设备是否可用。

GPS还有一个支持API,即Location,它的作用是一个代表位置信息的抽象类,用它可以获取所有的位置数据。我们可以用以上的方法开始进行定位,可以将地点信息传递给一个Location对象:

 

Locationlocation = locationManager.getLastKnownLocation (LocationManager.GPS_PROVIDER);

我们还可以调用以下函数,对每次更新的位置信息进行我们想要的操作:

 

locationManager.requestLocationUpdates (LocationManager.GPS_PROVIDER,1000, 10, new LocationListener())

其中,第一个参数是LocationProvider对象,第二个参数是刷新的时间差,这里设定为1秒,第三个参数是位置差,这里设定为10米,第四个参数为一个位置监听器对象,可以重写这些方法来实现我们的需求。

大致流程就是首先通过LocationMangager位置管理器,要想操作定位相关设备,必须先定义个LocationManager。可以通过如下代码创建LocationManger对象。然后通过LocationListener位置监听器,可以监听位置变化,监听设备开关与状态。Location位置信息,通过Location可以获取时间、经纬度、海拔等位置信息。采用locationListener里面的onLocationChanged()来获取location,也可以通过locationManager获取getLastKnownLocation主动获取locationGpsStatus.Listener来实现GPS状态监听,包括GPS启动、停止、第一次定位、卫星变化等事件。GpsStatus对应GPS状态信息,在卫星状态变化时就用到了GpsStatusGpsSatellite对应定位卫星,包含卫星的方位、高度、伪随机噪声码、信噪比等信息。

项目中之前使用了AlarmManager来进行定时提醒的功能。当时开发这个功能的时候并没有发现会有这种Bug,原因是开发时只会预约一个来测试功能是否有实现,实现了就算完成,而这个问题恰好是有多个预约时才会发生的问题。发现其中会有一两个通知并没有按照我设定的时间唤醒设备,而是会和与他时间相近的下一个预约同时出现(也就是发生了时间不精确的问题)。当时发现这个问题时以为是我在传递预约时间时发生了问题,就打Log查看了一下,发现并没有什么问题,既然时间没有问题,那很有可能是AlarmManager的问题。

官方文档说从API19android4.4)开始,为了节能省电(减少系统唤醒和电池使用)。使用Alarm.set()Alarm.setRepeating()已经不保证精确性。其实AlarmManager就是用的AIDl通信,从Alarm对象的获取我们也可以看出来getSystemService对应的ALARM_SERVICE是获取的系统服务,AlarmManager真正的实现都在FrameWork层的AlarmManagerService这个服务中。普通的set()和setRepeating()方法会通过调用legacyExactLength()这个方法返回这个值:

private long legacyExactLength(){

return (mAlaysExact ? WINDOW_EXACT : WINDOW_HEURISTIC);

}

mAlwaysExact这个变量,如果是小于API19的版本会使用WINDOW_EXACT参数,这个参数是0(意思就是区间设置为0,那么就会按照triggerAtMillis这个时间准时触发,也就是精准触发)另一个参数WINDOW_HEURISTIC的值是-1,这个值具体的用法就要看AlarmManagerService具体的实现了,反正只要知道这个值是不精准就可以。剩下的其他set方法直接就将这个值设置为WINDOW_EXACT,那么那几个方法就都是精准通知了,除了setInexactRepeating方法将参数设置为WINDOW_ HEURISTIC。只需要不同方法传递不同参数就可以了。setAlarmClock这个方法用的是WINDOW_EXACT这个参数,所以这个方法也是精准的,不过他有一些局限性:只能在API21版本和之后的版本调用并且Type是固定RTC_WAKEUP

如果没有设置和使用精准通知的话,系统会把触发时间相近的Alarm放在同一个batch中,然后每个bach根据时间排序放在mAlarmBatchs中,前面的就是先要触发的alarm。也就是Alarm会分为一批一批的一起触发,而不是每个Alarm都要触发。系统中有多个Alarm时会发生时间不准的现象,但如果系统中只有几个Alarm,并且他们的触发时间隔得很远,那么Alarm就自己分为一批,触发还是会准的。

API 19开始,alarm的机制都是非准确传递,操作系统将会转换闹钟,来最小化唤醒和电池使用。有一些新的API会支持严格准确的传递,见setWindow(int, long, long, PendingIntent)setExact(int, long, PendingIntent)argetSdkVersionAPI 19之前应用仍将继续使用以前的行为,所有的闹钟在要求准确传递的情况下都会准确传递。setRepeating方法在API 19开始将会失去原来的效果,所以要判断系统sdk版本进行选择:

 

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

    //参数2是开始时间、参数3是允许系统延迟的时间

        am.setWindow(AlarmManager.RTC, timeInMillis, interval, sender);

    } else {

        am.setRepeating(AlarmManager.RTC, timeInMillis, interval, sender);

    }

 

因为setWindow只执行一次,所以要重新定义闹钟实现循环。在广播接收的地方实现无限循环:


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

     setAlarmTime(context, System.currentTimeMillis() + 15, "自定义action", 15);

}

或者采取另一种定时器方案:利用Timerschedule方法重复执行任务Timer是一种定时器工具,用来在一个后台线程计划执行指定任务。它可以计划执行一个任务一次或反复多次。TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。

Timer线程实现和计划执行一个任务的基础步骤:实现自定义的TimerTask的子类,run方法包含要执行的任务代码。实例化Timer类,创建计时器后台线程。实例化任务对象(new RemindTask())。制定执行计划用schedule方法,第一个参数是TimerTask对象,第二个参数表示开始执行前的延时时间。还有一种方法可以指定任务的执行时间。当 计划反复执行的任务时,如果你注重任务执行的平滑度,那么请使用schedule方法,如果你在乎的是任务的执行频度那么使用scheduleAtFixedRate方法。 例如,这里使用了schedule方法,这就意味着所有beep之间的时间间隔至少为1秒,也就是说,如 果有一个beap因为某种原因迟到了(未按计划执行),那么余下的所有beep都要延时执行。如果我们想让这个程序正好在3秒以后终止,无论哪一个beep因为什么原因被延时,那么我们需要使用scheduleAtFix edRate方法,这样当第一个beep迟到时,那么后面的beep就会以最快 的速度紧密执行(最大限度的压缩间隔时间)。

 

mRecordTimer = new Timer();

mRecordTimer.schedule(new ScheduleTask ("com.exams.demo10_lbs.catch", LBSService. this ), 0L, 1000L);

public class ScheduleTask extends TimerTask

{ 

Context mContext;

  String mSchedule;

  public ScheduleTask(String paramString, Context paramContext)

  {

    this.mSchedule = paramString;

    this.mContext = paramContext;

  }

 

  public void run()

  {

    Intent localIntent = new Intent(this.mSchedule);

    this.mContext.sendBroadcast(localIntent);

  }

}

对于界面中控件刷新时间和gps数据分离,只需要将原来在服务中一起处理的时间信息计时使用主界面的设置timer定时器来控制刷新界面时间就好了。

定位服务的初始化设置步骤后,但GPS卫星是定期广播数据的,也就是说会定期收到卫星的GPS数据。我们并不能跟卫星主动申请数据,只能被动接收数据。(因此我们需要注册一个监听器来处理卫星返回的数据。Android下提供了GpsStatus这个类来看卫星信息,通过调用此类的一个method  getSatellites(),可以得到接收到的卫星的信息列表Iterable<GpsSatellite>。当然这些操作也是在一个listener当中来做的:GpsStatus.ListenerGpsStatuslistener也是注册于locationManager

所以不能在主线程中使用locationManagergetGpsStatus函数直接获取卫星信息,需要在GPS状态监听器GpsStatus.Listener的回调函数onGpsStatusChanged中来监听包括GPS启动、停止、第一次定位、卫星变化等事件。


//定义

GpsStatus.Listener gpsStatusListener=new GpsStatus.Listener(){

public void onGpsStatusChanged(int event) {

if(event==GpsStatus.GPS_EVENT_FIRST_FIX){

//第一次定位

}else if(event==GpsStatus.GPS_EVENT_SATELLITE_STATUS){

//卫星状态改变

GpsStatus gpsStauts= locationManager.getGpsStatus(null); //取当前状态

int maxSatellites = gpsStauts.getMaxSatellites(); //获取卫星颗数的默认最大值

Iterator<GpsSatellite> it = gpsStatus.getSatellites().iterator();//创建一个迭代器保存所有卫星

int count = 0;  

while (it.hasNext() && count <= maxSatellites) {  

count++;

GpsStatellite s = it.next();

}  

system.out.println("搜索到:"+count+"颗卫星");

}else if(event==GpsStatus.GPS_EVENT_STARTED){


//定位启动

}else if(event==GpsStatus.GPS_EVENT_STOPPED){

//定位结束

}

}

};

//绑定

locationManager.addGpsStatusListener(gpsStatusListener);

 

对于频繁文件读写操作造成性能卡顿、界面更新延缓数据出现间隔就需要将数据处理、数据写文件保存、数据界面刷新的逻辑流程进行分离,首先每隔一秒采集数据并且保存到缓存变量,另外起定时器每隔一秒读取缓存变量的数据来刷新界面。为了避免频繁读写文件,需要将一段时间间隔的数据进行拼接保存,在一段比较长的时间间隔后再操作IO把数据写入文件。以下是广播接收器对几种操作流程的动作分离Action

 

cmdReceiver = new CommandReceiver();

IntentFilter filter = new IntentFilter();// 创建IntentFilter对象

filter.addAction("com.exams.demo10_lbs.stopDemo");

filter.addAction("com.exams.demo10_lbs.catch");

filter.addAction("com.exams.demo10_lbs.refresh");

filter.addAction("com.exams.demo10_lbs.write");

filter.addAction("com.exams.demo10_lbs.startDemo");

分别对采集数据、刷新界面、数据写入文件的三种定时器控制:

 

LBSService.this.mRecordTimer = new Timer();

        LBSService.this.mRecordTimer.schedule(new ScheduleTask("com.exams.demo10_lbs.catch", LBSService.this), 1000L, 1000L);        

        LBSService.this.mInterfaceRefreshTimer = new Timer();

        LBSService.this.mInterfaceRefreshTimer.schedule(new ScheduleTask("com.exams.demo10_lbs.refresh", LBSService.this), 1100L, 1000L);

        LBSService.this.mDataWriterTimer = new Timer();

        LBSService.this.mDataWriterTimer.schedule(new ScheduleTask("com.exams.demo10_lbs.write", LBSService.this), 6000L, 40000L);

 

对于服务被系统杀死,就需要提高服务的优先级或者设置杀死后再自动循环重启。当然对于系统休眠调整耗电引起的进程优化,那就需要保证系统不进入休眠,保证系统awake

Android系统会尽量保持拥有service的进程运行,只要在该service已经被启动(start)或者客户端连接(bindService)到它。当内存不足时,需要保持,拥有service的进程具有较高的优先级。还有在服务serviceonStartCommand方法,返回START_STICKY来保证service不被杀掉。START_STICKY在运行onStartCommandservice进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到nullintent。提升service优先级在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。

<service

            android:name="com.dbjtech.acbxt.waiqin.UploadService"

            android:enabled="true" >

            <intent-filter android:priority="1000" >

                <action android:name="com.dbjtech.myservice" />

            </intent-filter>

        </service>

 

提升service进程优先级Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是:前台进程、可视进程、次要服务进程、后台进程、内容供应节点、空进程。当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以使用startForegroundservice放到前台状态。这样在低内存时被kill的几率会低一些。

onStartCommand方法内添加如下代码:

 

Notification notification = new Notification(R.drawable.ic_launcher,

 getString(R.string.app_name), System.currentTimeMillis());

 PendingIntent pendingintent = PendingIntent.getActivity(this, 0,

 new Intent(this, AppMain.class), 0);

 startForeground(0x111, notification);

 

当然也可以onDestroy方法里重启service

public void onDestroy() {

 Intent sevice = new Intent(this, MainService.class);

 this.startService(sevice);

super.onDestroy();

}

Android的文档知道,当进程长期不活动,或系统需要资源时,会自动清理门户,杀死一些Service,和不可见的Activity等所在的进程。但是如果某个进程不想被杀死(如数据缓存进程,或状态监控进程,或远程服务进程),可以Application加上Persistent属性:


<application

        android:name="XXX"

        android:label="@string/app_name"

       android:persistent="true">

</application>

对于系统进入休眠,Android系统为了省电以及减少CPU消耗,在一段时间后会使系统进入休眠状态,这时Android系统中CPU会保持在一个相对较低的功耗状态,会将一些不在活动或者较为耗内存的应用优化。Wake Lock是一种锁的机制,只要有人拿着这个锁,系统就无法进入休眠,可以被用户态程序和内核获得。这个锁可以是有超时的 或者 是没有超时的,超时的锁会在时间过去以后自动解锁。如果没有锁了或者超时了,内核就会启动休眠的那套机制来进入休眠。所有的锁必须成对的使用, 如果申请了而没有及时释放,会造成系统故障。如申请了partial wakelock,而没有及时释放,那系统就永远进不了Sleep模式。

mWakeLock = mPowerManager. newWakeLock( PowerManager.PARTIAL_ WAKE_LOCK , "grow lock");

mWakeLock.setReferenceCounted(false);

...

if(mWakeLock!=null &&!mWakeLock.isHeld())

{

mWakeLock.acquire(40000);

}

...

if(mWakeLock!=null){

mWakeLock.release();

mWakeLock=null;

 

另外WakeLock的设置是Activiy级别的,不是针对整个Application应用的。所以application下有多个activity一定需要注意下。要进行电源的操作需要在AndroidManifest.xml中声明该应用有设置电源管理的权限。

 

四、

通过测试对修改后apk的运行采集数据效果来看,使用启动GPS实时获取监听requestLocationUpdates的系统自带定时设置就可以定时获取新的卫星信息,使用AlarmManagersetWindow或者设置timer循环定时器就可以避免定时执行任务失败的现象。使用mvc流程分离后对数据的采集保存不再出现不连续的问题,加了对应保证服务不被系统优化的措施后,采集执行任务就不会中断了。

 


版权声明:本文为yuyongling111原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。