【问题标题】:Android run a task every 4 secondAndroid 每 4 秒运行一个任务
【发布时间】:2016-12-30 11:28:55
【问题描述】:

嗨,我需要每 4 秒调用一次方法,即使设备处于睡眠状态,我也使用带有服务 Start_stick 的警报管理器,服务名称是 TransactionService。当设备处于活动状态并且每隔 4 秒调用一次该方法时,该代码运行良好,但是当屏幕被锁定并且设备休眠时,调用变得不准确。所以该方法现在每 2 秒调用一次,有时每 1 秒调用一次,5 ....

这就是我运行线程以每 4 秒调用一次方法的方式

    AlarmManager mgr = (AlarmManager) getApplicationContext().getSystemService(
            Context.ALARM_SERVICE);
        Intent notificationIntent = new Intent(getApplicationContext(),
                TransactionService.class);
        PendingIntent pendingIntent = PendingIntent.getService(
                getApplicationContext(), 0, notificationIntent, 0);
        mgr.setRepeating(AlarmManager.RTC_WAKEUP,
                System.currentTimeMillis(), 4000, pendingIntent);

这是设备处于活动状态且屏幕打开时调用方法的日志

12-30 13:23:00.565 17397-17479/com.ids.simcardrefill D/url: calling
 12-30 13:23:04.565 17397-17537/com.ids.simcardrefill D/url:calling
 12-30 13:23:08.565 17397-17411/com.ids.simcardrefill D/url:calling
 12-30 13:23:12.565 17397-17655/com.ids.simcardrefill D/url:calling

这就是设备休眠时方法的调用方式

12-30 13:09:12.565 17397-17655/com.ids.simcardrefill D/url:calling
12-30 13:09:17.785 17397-17598/com.ids.simcardrefill D/url:calling
12-30 13:09:20.565 17397-17479/com.ids.simcardrefill D/url:calling
12-30 13:09:25.775 17397-17537/com.ids.simcardrefill D/url:calling
12-30 13:09:28.565 17397-17411/com.ids.simcardrefill D/url:calling

这里调用的区别是不准确的:2秒、5秒、3秒

这就是服务的样子:

public int onStartCommand(Intent intent, int flags, int startId) {


    mshared = PreferenceManager
            .getDefaultSharedPreferences(getApplicationContext());
    edit = mshared.edit();
    hostname = mshared.getString(
            getApplicationContext().getString(R.string.hostname), "0");
    contin = true;
    cost
   = mshared.getString(getString(R.string.test), "0.09");
            if (contin) {
                getTransactions get = new    getTransactions(getApplicationContext());
                get.execute(hostname);
            }

    return START_STICKY;
}

`

任何解决方案??

【问题讨论】:

  • 使用唤醒日志来实现。
  • 嗨 Malo,您可以在前台使用服务并调用您的 AlarmManger 任务,就像 startForgroundService(true),truiton.com/2014/10/android-foreground-service-example
  • 时间变得“不准确”是由于 Android (6.0+) 将网络活动捆绑到更大的批次以节省电池时间。无论如何,如果您需要每 4 秒执行一次在线调用,则 API 的设计可能有问题。每 4 秒轮询一次是一种糟糕的风格,就像忙着等待一样。
  • 设备只能在这个应用中工作,并且一直连接充电,而且操作系统是4.4.1而不是6.0+。
  • 请不要这样做。应用程序会消耗电池,没有人会喜欢它。无论您想做什么,都有另一种方法。

标签: java android multithreading


【解决方案1】:

你应该创建一个在后台工作的服务:https://developer.android.com/guide/components/services.html

您应该使用Handler 来实现every 4 second 功能。

Handler handler = new Handler();
Runnable test = new Runnable() {
    @Override
    public void run() {
        //do work
        handler.post(test, 4000); //wait 4 sec and run again
    }
};

public void stopTest() {
    handler.removeCallbacks(test);
}

public void startTest() {
    handler.post(test,0); //wait 0 ms and run
}

编辑:我已经尝试了下面的代码,它对我有用

MyService.java

public class MyService extends Service {
    Handler handler;
    Runnable test;
    public MyService() {
        handler = new Handler();
        test = new Runnable() {
            @Override
            public void run() {
                Log.d("foo", "bar");
                handler.postDelayed(test, 100); //100 ms you should do it 4000
            }
        };

        handler.postDelayed(test, 0);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

AndroidManifest.xml

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true"></service>

MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    //some code
    startService(new Intent(this, MyService.class));
}

请记住,如果您希望 start-stop 功能在我的第一个示例中发挥作用。

【讨论】:

  • 我已经尝试过了,过了一会儿循环停止了。我认为是因为设备正在休眠
  • 你实现了服务还是只是一个应用程序?
  • 我也是你我每 4 秒怎么称呼它
  • 如果您发布您尝试过的内容,有人可能会提供解决方案(例如编辑:我已经尝试过使用服务和处理程序)
  • 使用适合我的代码块编辑我的答案
【解决方案2】:

这样做的正确方法是使用处理程序(在另一个答案中已经提到),但我会冒昧地添加几点。

问题

我也遇到过类似的情况,AlarmManager 触发异常。深入研究这个问题让我明白,由于 AlarmManager 操作通过持有 CPU 唤醒锁来唤醒 CPU 并且对电池很耗电(假设设备处于非活动状态),因此操作系统会尝试从不同的设备批量处理不同的警报应用程序并在设备唤醒时触发所有未决警报。这会导致 AlarmManager 的不稳定行为。该文档还指定我们不应该使用它来触发 exact 时间戳的事件。有一些 Android API 应该适用于精确的间隔,例如 AlarmManager.setExact(),但如果间隔持续时间少于一分钟,操作系统会自行优化以忽略精确性。 [没有记录,但从我的个人经验来说]

修复

我只使用处理程序解决了这个问题,正如另一个答案中所分享的那样。但有一个小警告。在处理程序被杀死(由于任何原因)的边缘情况下,它不会自行触发并且您的轮询将停止。

警告

备用方法是保留一个 AlarmManager,每分钟运行一次以再次触发 Handler,以防它被操作系统过早停止。所以,你有一个处理程序每​​ n 秒运行一次。在 SharedPreferences 中存储上次调用 Handler 的时间戳。每 x 分钟运行一个备用 AlarmManager(理想情况下 x = 5*n,这样您就不会错过超过 5 个轮询调用)。 AlarmManager 检查 Handler 上次运行的时间。如果它在边距内,AlarmManager 什么也不做,并在 x 分钟后自行重新安排。如果超过 x 分钟,则 Handler 一定已经被 OS 杀死,AlarmManager 会重新启动 Handler。

添加一些代码以提供上下文。

public class PollingAlarmReceiver extends WakefulBroadcastReceiver {

    final long POLLING_FREQUENCY_MARGIN = 5 * 1000; //margin kept in case the System delays any threads

    Context mContext = ServicesApp.getContext();


    /*
    Splash/BootReceiver starts the Alarm and the Handler for polling.
    The Handler starts the polling service and schedules the next run after an delay of the polling interval.
    Before starting the service, the Handler also checks when the service was last run and whether it is time for the next call or not (with a margin of 5 seconds [POLLING_FREQUENCY_MARGIN]).
    The Handler should cover all the cases and run smoothly. In case it fails, the Alarm acts as a failsafe.
    The Alarm runs at an interval of 1 minute checking when the Handler was last called.
    If it is past the time of the next scheduled call (with a margin of 5 seconds [POLLING_FREQUENCY_MARGIN]), the Alarm starts the runnable and makes the Handler queue the next run.
    */

    @Override
    public void onReceive(Context context, Intent intent) {

        if (mContext == null)
            mContext = ServicesApp.getContext();

        if (mContext == null)
            mContext = context.getApplicationContext();

        if (mContext != null) {
            if (getLastPolledTimestamp(mContext) > 0 && (System.currentTimeMillis() > (POLLING_FREQUENCY_MARGIN +  getPollingInterval(mContext) + getLastPolledTimestamp(mContext)))) {
                startPollingHandler();
            }
        }
    }


    Runnable mPoller = new Runnable() {
        @Override
        public void run() {

            if (mContext == null)
                mContext = ServicesApp.getContext();
            if (mContext != null) {
                try {
                    if ((System.currentTimeMillis() >= (getPollingInterval(mContext)) - POLLING_FREQUENCY_MARGIN + getLastPolledTimestamp(mContext))) {
                        if (!isServiceRunning(PollingService.class, mContext)) {
                            mContext.getSharedPreferences(CommonLib.APP_SETTINGS, 0).edit().putLong(LAST_POLLED_TIMESTAMP, System.currentTimeMillis()).commit();
                            Intent service = new Intent(mContext, PollingService.class);
                            startWakefulService(mContext, service);
                        }
                    }
                } finally {
                    ServicesApp.getHandler().postDelayed(mPoller, getPollingInterval(mContext));
                }
            }
        }
    };

    public void startAlarmToCheckForHandler() {
        if (mContext == null)
            mContext = ServicesApp.getContext();
        if (mContext != null) {
            AlarmManager alarmMgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
            Intent intent = new Intent(mContext, PollingAlarmReceiver.class);
            PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
            alarmMgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), 60 * 1000, alarmIntent);

        }
    }

    public void startPollingHandler() {
        mPoller.run();

    }

    public void cancelAlarm() {
        if (mContext == null)
            mContext = ServicesApp.getContext();
        if (mContext != null) {
            AlarmManager alarmMgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
            Intent intent = new Intent(mContext, PollingAlarmReceiver.class);
            PendingIntent alarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
            alarmMgr.cancel(alarmIntent);
        }
    }
}

附注:我有这个代码在数千台设备的生产环境中运行,它们的主要功能依赖于轮询的准确性,它似乎对我很有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-09
    • 2021-12-18
    • 1970-01-01
    • 1970-01-01
    • 2011-10-02
    相关资源
    最近更新 更多