Android服务中的GPS定时轮询,最大化电池寿命

4
我正在尝试编写一个服务,每隔X分钟尝试获取设备的GPS位置,并在后台运行和记录,即使应用程序没有焦点。

所以,现在是创建一个服务的时候了。

我创建了一个服务,设置了locationListener,获取了location Manager并请求更新位置... 一切正常... 基本框架可行。

现在,我不希望GPS一直运行,因为这会耗电,我想要发生的是服务启动GPS,请求更新,然后关闭(至少它对GPS位置的兴趣),5或10分钟后再次这样做。

很简单,在我的监听器的onLocationChanged()方法中,我添加了一行LocationManager (removeUpdates(locationListener)).. 因此,当我的服务请求更新时,它只获得一个更新,并关闭。

我决定添加一个小while循环,有效地注册我的位置侦听器,并休眠X分钟。 因此,从逻辑上讲,它应该注册它想要的信息,然后休眠.. 一个更新到达,我在onLocationChange中获取该更新,并取消对事件的兴趣,关闭GPS,直到下一次循环执行。

现在,我有两个问题:1)这在逻辑上是否可以?还是有更优雅的方式?请记住,我希望无论启动的应用程序是否在焦点中,都记录信息,即使启动的应用程序被杀死,也希望服务继续运行,但这是我尚未完全决定的设计决策。

第二个问题是,我需要将此循环放入一个线程中,因为将其放在oncreate中会导致服务最终被杀死,因为它从oncreate返回所需的时间太长,那么如何做到最好? AsyncTask是一种选择,但理论上永远不会完成此任务... Handler似乎有点愚蠢,因为没有真正的回调,它只是注册GPS更新,并且取消事件兴趣代码在LocationListener onLocationChange()中。

一旦实例化,就不会有任何实际通信出现在此线程中,但需要在服务要关闭时通过某些用户交互来发出信号以停止/结束...

那么我应该只使用基本线程吗?即使它从来没有真正返回,也要使用AsyncTask吗?还是使用Handler是更好的选择?还是我的模型从一开始就完全是错误的形式?

1个回答

3

我认为有一个管理GPS的服务是明智的选择,特别是如果您有多个可能需要位置信息的活动。我在我的应用程序中采用了这种方法。

首先,我在服务中使用了一个 Ibinder,并从活动中绑定到它。

bindService (new Intent(....),   mServconn, Context.BIND_AUTO_CREATE);

在 onStart() 方法中:

而在每个 activity 的 onStop() 方法中使用 unbindService(mServconn);

我曾让服务通过 sendBroadcast() 向已注册的活动中的 BroadcastReceivers 发送广播。位置数据是通过广播意图中的额外信息传递的。

我在服务中使用了一个状态机,包含 3 种状态:IDLE、SEEKING 和 GOT_A_FIX_NOW_SLEEPING。睡眠时间通过服务的公共方法中的 'changeGPSParameters' 方法传递。另一个参数是所需的精度,即在获得更好的定位之前不要发送消息,然后就可以进入睡眠模式。睡眠模式意味着关闭 GPS 直到时间到达。

时间由一个 Runnable 管理,Handler 使用类似下面的代码向其发布消息:

mHandler.postDelayed(this, mSleepTime );

我觉得这个方法很好用。当没有任何活动与该服务绑定时,onUnbind() 方法将在服务中运行。在那个方法中,您只需确保停止位置监听器并使用 mHandler.removeCallbacks 停止计时器即可。
更新:
下面是一个简单的示例,您可以通过 main.xml 中的两个按钮来启动/停止它,main.xml 应该有一个单独的文本视图来显示计时器状态:
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.TextView;

public class TimerLoopActivity extends Activity {

    private Handler mHandler = new Handler();
    private int mSleepTime  = 2; //seconds
    private int mLoopCount = 0;
    private Runnable mUpdateTimeTask = new Runnable() {
        public void run() {
            // Code here for when timer completes
            mLoopCount++;
            setTextBoxMsg("Running - count = " + mLoopCount);
            mHandler.removeCallbacks(this);
            mHandler.postDelayed(this, mSleepTime * 1000); // keep looping
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        setTextBoxMsg("Timer Idle");
    }

    private void setTextBoxMsg(String string) {
        TextView tv = (TextView) findViewById(R.id.textView1);
        tv.setText(string);
    }

    public void myClickHandler(View target) {
        switch (target.getId()) {
            case R.id.startbutton:
                setTextBoxMsg("Starting timer");
                startTimer();
                break;
            case R.id.stopbutton:
                setTextBoxMsg("Stopping timer");
                mLoopCount = 0;
                stopTimer();
                break;
        }
    }

    private void stopTimer() { mHandler.removeCallbacks(mUpdateTimeTask); }
    private void startTimer() { mHandler.postDelayed(mUpdateTimeTask, mSleepTime * 1000);}
}

您可以将此适应到您的服务中。

Nick,谢谢你的建议。我有点明白你的意思,但我遇到的问题是如何在服务中实际运行,并使其按照我的期望工作。 - Speckpgh
通用的开始循环注册事件,休眠。 - Speckpgh
在运行时,我准备了一个looper,创建了监听器,然后启动一个while(true)循环,简单地设置了位置管理器的标准,并请求位置更新;Looper.loop()然后结束循环。依靠onLocationChange回调来注销我的兴趣,一切都会休眠直到下一次触发。但是,一旦我最终获得更新,我的代码似乎永远不会离开Looper.Loop()代码。内部while True Loop按预期循环,直到第一个回调,但之后,代码从驱动一切的While(true)循环的末尾的looper.loop()调用中永远不会离开。 - Speckpgh
我并不完全确定为什么我需要looper,说实话,因为我并没有尝试或者有任何想要发送消息到loop的愿望,但是显然Android需要它来编译,而且如果在我的while(true)循环的结尾没有looper.loop(),那么onLocationChanged的回调就永远不会发生...所以我需要它们。显然。但现在我卡在了如何在我的线程被卡在Looper.loop()调用时调用Looper.quit()上,我觉得我做错了什么。 - Speckpgh
我会编辑我的答案,加上一个基本的Runnable/handler示例,您可以进行调整。 - NickT
Nick,谢谢,这个确实很好用。我在最初的意图上犯了一些错误,结果每次重新启动都会产生一个新的线程,但那只是因为我做了一些傻事。找到并纠正了我的愚蠢,现在它像魔法一样工作。非常感谢你。 - Speckpgh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接