安卓手机如何定时向服务器发送地理位置信息

49

我运营一个Web服务,允许用户记录他们的行程(类似谷歌的MyTracks)作为一个更大的应用程序的一部分。问题在于,当用户开始或结束旅行时,很容易将数据传递到服务器,包括坐标和其他项目。作为一个新手,我不知道如何设置后台服务,以每个(预定)时间段(最短3分钟,最长1小时)发送位置更新,直到用户标记旅程结束或直到预设的时间结束。

一旦从手机开始旅行,服务器会响应并返回一个轮询周期,以便手机使用它作为更新之间的间隔时间。这一部分是有效的,因为我可以在手机上显示响应,并且我的服务器注册了用户的操作。同样,在收到关闭旅行请求后,服务器端也会关闭旅行。

但是,当我尝试从StartTrack Activity内部启动定期跟踪方法时,使用requestLocationUpdates(String provider,long minTime,float minDistance,LocationListener listener)其中minTime为来自服务器的轮询周期时,它就无法工作,并且我没有收到任何错误。所以这意味着在这一点上我一无所知,因为我以前从未使用过Android。

我在这里看到了很多帖子,介绍了使用处理程序、待处理意图和其他类似方法的后台服务,但我真的不知道如何做。我希望用户可以在更新进行时在手机上做其他事情,所以如果您们能指出一篇教程,展示如何编写实际的后台服务(也许这些运行为单独的类?)或其他执行此操作的方法,那将是非常棒的。

3个回答

75
我最近写了一个类似的东西,但是我决定不再让后台服务一直运行。毕竟操作系统可能会关闭它。我的做法是使用启动意图过滤器并设置闹钟,通过闹钟管理器以固定时间间隔重新启动我的应用程序,然后发送数据。在Android文档中可以找到关于服务和闹钟管理器的相关信息。
首先,我创建了一个广播接收器,当打开网络连接时简单地启动我的服务(因为我只对连接感兴趣 - 你可能还想过滤启动事件)。启动接收器必须是短暂的,所以只需启动您的服务即可。
public class LaunchReceiver extends BroadcastReceiver {

    public static final String ACTION_PULSE_SERVER_ALARM = 
            "com.proofbydesign.homeboy.ACTION_PULSE_SERVER_ALARM";

    @Override
    public void onReceive(Context context, Intent intent) {
        AppGlobal.logDebug("OnReceive for " + intent.getAction());
        AppGlobal.logDebug(intent.getExtras().toString());
        Intent serviceIntent = new Intent(AppGlobal.getContext(),
                MonitorService.class);
        AppGlobal.getContext().startService(serviceIntent);
    }
}
在清单文件中,我有以下内容:
<receiver
    android:name="LaunchReceiver"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
    <intent-filter>
        <action android:name="com.proofbydesign.homeboy.ACTION_PULSE_SERVER_ALARM" />
    </intent-filter>
</receiver>

注意我为自己的闹钟设置了一个过滤器,这允许我在服务完成工作后关闭并重新启动它。

我的监视器服务顶部如下:

public class MonitorService extends Service {

    private LoggerLoadTask mTask;
    private String mPulseUrl;
    private HomeBoySettings settings;
    private DataFile dataFile;
    private AlarmManager alarms;
    private PendingIntent alarmIntent;
    private ConnectivityManager cnnxManager;

    @Override
    public void onCreate() {
        super.onCreate();
        cnnxManager = (ConnectivityManager) 
                getSystemService(Context.CONNECTIVITY_SERVICE);
        alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intentOnAlarm = new Intent(
                LaunchReceiver.ACTION_PULSE_SERVER_ALARM);
        alarmIntent = PendingIntent.getBroadcast(this, 0, intentOnAlarm, 0);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        // reload our data
        if (mPulseUrl == null) {
            mPulseUrl = getString(R.string.urlPulse);
        }
        AppGlobal.logDebug("Monitor service OnStart.");
        executeLogger();
    }

executeLogger启动一个异步任务,这可能是我过于谨慎(这是我第三个Android应用程序)。异步任务获取GPS数据,将其发送到互联网,最后设置下一个闹钟:

private void executeLogger() {
    if (mTask != null
        && mTask.getStatus() != LoggerLoadTask.Status.FINISHED) {
        return;
    }
    mTask = (LoggerLoadTask) new LoggerLoadTask().execute();
}

private class LoggerLoadTask extends AsyncTask<Void, Void, Void> {

    // TODO: create two base service urls, one for debugging and one for live.
    @Override
    protected Void doInBackground(Void... arg0) {
        try {
            // if we have no data connection, no point in proceeding.
            NetworkInfo ni = cnnxManager.getActiveNetworkInfo();
            if (ni == null || !ni.isAvailable() || !ni.isConnected()) {
                AppGlobal
                        .logWarning("No usable network. Skipping pulse action.");
                return null;
            }
            // / grab and log data
        } catch (Exception e) {
            AppGlobal.logError(
                    "Unknown error in background pulse task. Error: '%s'.",
                    e, e.getMessage());
        } finally {
            // always set the next wakeup alarm.
            int interval;
            if (settings == null
                || settings.getPulseIntervalSeconds() == -1) {
                interval = Integer
                        .parseInt(getString(R.string.pulseIntervalSeconds));
            } else {
                interval = settings.getPulseIntervalSeconds();
            }
            long timeToAlarm = SystemClock.elapsedRealtime() + interval
                * 1000;
            alarms.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToAlarm,
                    alarmIntent);
        }
        return null;
    }
}
我注意到在设置闹钟后没有调用stopSelf()方法,所以除非被操作系统关闭,否则我的服务将一直闲置。虽然我是这个应用程序的唯一用户,但对于公共应用程序来说,你应该设置下一个时间间隔的闹钟然后调用stopSelf()方法关闭服务。 更新 请查看@juozas的评论,关于使用'alarms.setRepeating()'。

2
非常好的例子,谢谢!不过我有一个问题。为什么要进行连续的闹钟重新调度,alarms.setRepeating() 不是已经实现了相同的功能吗? - Juozas Kontvainis
1
关于 setRepeating() 的建议是使用 setInexactRepeating()(这样可以简化你的代码)- 我的问题是:即使手机进入睡眠状态这个方法是否仍然有效? - Mr_and_Mrs_D
2
@Mawg 这只是我自己的应用程序类,其中包含各种辅助方法,例如日志记录等。您可以忽略它。事实上,我应该剥离所有多余的代码。 - Rob Kent
1
在AppGlobal.getContext()中你返回什么?我很困惑,因为函数调用是静态的,但似乎没有静态引用到任何类型的上下文... - Kevin
1
对于那些遇到问题的人,请注意,在设置闹钟后,您需要调用stopSelf()才能使其生效。否则,新服务将无法启动。 - paradite
显示剩余16条评论

17
您需要创建一个单独的类,它是Service类的子类。 服务文档 您的主应用程序可以调用startServicestopService来启动后台进程。还有一些其他有用的调用在上下文类中用于管理服务: 上下文文档

2
请记住,操作系统会定期停止(并重新启动)您的服务,因为它认为适合:http://developer.android.com/reference/android/app/Service.html#ProcessLifecycle - jscharf
jscharf,我该如何确保服务定期被调用,并且只在那时运行,直到用户执行特定操作停止调用?我想使用闹钟并查看如何使用它们的文档... - Mark
2
你可以选择使用ALARM_SERVICE在一定时间间隔内向你的服务发送一个Intent,或者为你的服务生成一个新的线程,检查时间并使用sleep()方法来控制你希望的唤醒间隔。你的主要应用程序负责调用stopService来关闭它,因此需要捕获用户事件。还要记住,服务运行在主应用程序的进程中。你的主应用程序应该确保在onStop()方法中释放尽可能多的资源,以确保不会过载系统。 - Mark
@Mark,使用闹钟服务,我可以定期上传文件到服务器吗? - AndroidOptimist
@Mark:你能分享一些代码片段吗?这将是非常有帮助的。 - Saurabh Ahuja
显示剩余3条评论

2

我同意Rob Kent的观点,此外我认为在你的BroadcastReceiver中扩展WakefulBroadcastReceiver并使用它的静态方法startWakefulService(android.content.Context context,android.content.Intent intent)可能更好,因为它确保了你的服务不会被操作系统关闭。

public class YourReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        Intent service = new Intent(context, YourService.class);
        startWakefulService(context, service);
    }
}

Official documentation


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