如何真正每分钟更新一个小部件

时间:2016-07-30 20:34:05

标签: android android-widget scheduled-tasks

我很难过。我知道这个问题已经被回答了一百次,但我没有尝试过任何工作。

我的问题:我制作了一个需要在每分钟刷新精确的Android小部件,就像所有时钟小部件一样。 (这个小部件告诉我在我的火车离开前剩下多少分钟,一分钟的错误使它无用)。

以下是我的尝试以及各自的结果:

  • 我将android:updatePeriodMillis="60000"放入appwidget_info.xml。但是,正如API Docs中指定的那样," updatePeriodMillis请求的更新不会每30分钟发送一次以上#34;而这确实是关于我的小部件更新的频率。
  • 我尝试使用AlarmManager。在我的WidgetProvider.onEnabled

    AlarmManager am = (AlarmManager)context.getSystemService
            (Context.ALARM_SERVICE);
    Calendar calendar = Calendar.getInstance();
    long now = System.currentTimeMillis();
    // start at the next minute
    calendar.setTimeInMillis(now + 60000 - (now % 60000));
    am.setRepeating(AlarmManager.RTC, calendar.getTimeInMillis(), 60000,
            createUpdateIntent(context));
    

    然而,正如API docs," API 19中所述,所有重复警报都是不准确的"事实上,我的小部件实际上每五分钟左右更新一次。

  • 根据前一点,我尝试将targetSdkVersion设置为18并且没有看到差异(每隔五分钟更新一次)。
  • setRepeating文档似乎建议使用setExact。我尝试了以下内容。在我的更新逻辑结束时:

    Calendar calendar = Calendar.getInstance();
    long now = System.currentTimeMillis();
    long delta = 60000 - (now % 60000);
    
    Log.d(LOG_TAG, "Scheduling another update in "+ (delta/1000) +" seconds");
    calendar.setTimeInMillis(now + delta);
    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    alarmManager.setExact(AlarmManager.RTC, calendar.getTimeInMillis(), //UPDATE_PERIOD_SECONDS * 1000,
            createUpdateIntent(context));
    

    它可以完美地工作几分钟,然后每五分钟左右恢复更新一次(甚至不会接近微小的变化)。以下是收到更新意图时的一些时间戳:

    • 21:44:17.962
    • 21:52:37.232
    • 21:59:13.872
    • 22:00:00.012←嘿,它突然又变得精确了??
    • 22:01:47.352
    • 22:02:25.132
    • 22:06:56.202
  • 有人建议使用Handler。我定义了一个Service,我在启用小部件提供程序时启动,并在更新代码后执行此操作:

    int delay = (int)(60000 - (System.currentTimeMillis() % 60000));
    Log.d(LOG_TAG, "Scheduling another update in " + delay/1000 + " seconds");
    new Handler().postDelayed(new Runnable() {
        public void run() {
            Log.d(LOG_TAG, "Scheduled update running");
            updateAppWidget();
        }
    }, delay);
    

    并且这个完美几个小时,然后服务突然被杀死并获得" scheduled to restart after HUGE delay"。具体来说,小部件只是在某个时刻陷入困境,根本没有更新。

我在网上看过的其他一些选项:上面的链接帖子建议创建一个前台服务(如果我理解正确的话,意味着在我已经拥挤的状态栏中有一个永久可见的图标。我不会我使用的每个时钟小部件都有一个永久图标,因此不需要)。另一个建议是从服务中运行一个高优先级的线程,感觉有点过分。

我也看到了使用TimersBroadcastReceiver的建议,但前者被称为"不适合任务"我记得在做后者时遇到了麻烦。我认为我必须在服务中执行此操作然后服务就像我使用Handler时一样被杀死。

应该注意的是,当手机连接到计算机时,AlarmManager似乎运行良好(可能是因为这意味着电池正在充电),这在大多数时间我没有帮助想知道我的火车什么时候开出的时候我已经在途中......

由于Handler非常准确但过了一段时间后才停止工作,并且AlarmManager选项太不准确但没有停止工作,我想通过{{{}}来组合它们{1}}每隔十分钟左右启动一次服务,并让该服务使用AlarmManager每分钟更新一次显示。不知怎的,我觉得这会被Android检测为一种强大的杀戮并被杀死,无论如何,我确定我必须遗漏一些明显的东西。不应该 编辑:如果重要的话,我在三星Galaxy Note 4(2016-06-01)和Android 6.0.1上使用我的小工具。

4 个答案:

答案 0 :(得分:3)

对不起,我完全忘记了,很忙......嗯,我希望你知道你需要什么,片段跟着,希望我不要忘记什么。

在小部件提供程序类上。

public static final String ACTION_TICK = "CLOCK_TICK";
public static final String SETTINGS_CHANGED = "SETTINGS_CHANGED";
public static final String JOB_TICK = "JOB_CLOCK_TICK";

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

        preferences =  PreferenceManager.getDefaultSharedPreferences(context);

        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        ComponentName thisAppWidget = new ComponentName(context.getPackageName(), WidgetProvider.class.getName());
        int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget);

        if (intent.getAction().equals(SETTINGS_CHANGED)) {
            onUpdate(context, appWidgetManager, appWidgetIds);
            if (appWidgetIds.length > 0) {
                restartAll(context);
            }
        }

        if (intent.getAction().equals(JOB_TICK) || intent.getAction().equals(ACTION_TICK) ||
                intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
                || intent.getAction().equals(Intent.ACTION_DATE_CHANGED)
                || intent.getAction().equals(Intent.ACTION_TIME_CHANGED)
                || intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
            restartAll(context);
            onUpdate(context, appWidgetManager, appWidgetIds);
        }
    }

private void restartAll(Context context){
        Intent serviceBG = new Intent(context.getApplicationContext(), WidgetBackgroundService.class);
        context.getApplicationContext().startService(serviceBG);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            scheduleJob(context);
        } else {
            AppWidgetAlarm appWidgetAlarm = new AppWidgetAlarm(context.getApplicationContext());
            appWidgetAlarm.startAlarm();
        }
    }



 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void scheduleJob(Context context) {
        ComponentName serviceComponent = new ComponentName(context.getPackageName(), RepeatingJob.class.getName());
        JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
        builder.setPersisted(true);
        builder.setPeriodic(600000);
        JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
        int jobResult = jobScheduler.schedule(builder.build());
        if (jobResult == JobScheduler.RESULT_SUCCESS){
        }
    }


    @Override
    public void onEnabled(Context context){

        restartAll(context);
    }


    @Override
    public void onDisabled(Context context){

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            jobScheduler.cancelAll();
        } else {
            // stop alarm
            AppWidgetAlarm appWidgetAlarm = new AppWidgetAlarm(context.getApplicationContext());
            appWidgetAlarm.stopAlarm();
        }

        Intent serviceBG = new Intent(context.getApplicationContext(), WidgetBackgroundService.class);
        serviceBG.putExtra("SHUTDOWN", true);
        context.getApplicationContext().startService(serviceBG);
        context.getApplicationContext().stopService(serviceBG);
    }

WidgetBackgroundService

public class WidgetBackgroundService extends Service {

        private static final String TAG = "WidgetBackground";
        private static BroadcastReceiver mMinuteTickReceiver;

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

        @Override
        public void onCreate(){
            super.onCreate();
        }



        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            if(intent != null) {
                if (intent.hasExtra("SHUTDOWN")) {
                    if (intent.getBooleanExtra("SHUTDOWN", false)) {

                        if(mMinuteTickReceiver!=null) {
                            unregisterReceiver(mMinuteTickReceiver);
                            mMinuteTickReceiver = null;
                        }
                        stopSelf();
                        return START_NOT_STICKY;
                    }
                }
            }

            if(mMinuteTickReceiver==null) {
                registerOnTickReceiver();
            }
            // We want this service to continue running until it is explicitly
            // stopped, so return sticky.
            return START_STICKY;
        }

        @Override
        public void onDestroy(){
            if(mMinuteTickReceiver!=null) {
                unregisterReceiver(mMinuteTickReceiver);
                mMinuteTickReceiver = null;
            }

            super.onDestroy();
        }

        private void registerOnTickReceiver() {
            mMinuteTickReceiver = new BroadcastReceiver(){
                @Override
                public void onReceive(Context context, Intent intent){
                    Intent timeTick=new Intent(WidgetProvider.ACTION_TICK);
                    sendBroadcast(timeTick);
                }
            };
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_TIME_TICK);
            filter.addAction(Intent.ACTION_SCREEN_ON);
            registerReceiver(mMinuteTickReceiver, filter);
        }
}

RepeatingJob class

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class RepeatingJob extends JobService {
    private final static String TAG = "RepeatingJob";

    @Override
    public boolean onStartJob(JobParameters params) {
        Log.d(TAG, "onStartJob");
        Intent intent=new Intent(WidgetProvider.JOB_TICK);
        sendBroadcast(intent);
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

AppWidgetAlarm类

public class AppWidgetAlarm {
    private static final String TAG = "AppWidgetAlarm";

    private final int ALARM_ID = 0;
    private static final int INTERVAL_MILLIS = 240000;
    private Context mContext;


    public AppWidgetAlarm(Context context){
        mContext = context;
    }


    public void startAlarm() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MILLISECOND, INTERVAL_MILLIS);
        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        Intent alarmIntent = new Intent(WidgetProvider.ACTION_TICK);
        PendingIntent removedIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        Log.d(TAG, "StartAlarm");
        alarmManager.cancel(removedIntent);
        // needs RTC_WAKEUP to wake the device
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), INTERVAL_MILLIS, pendingIntent);
    }

    public void stopAlarm()
    {
        Log.d(TAG, "StopAlarm");

        Intent alarmIntent = new Intent(WidgetProvider.ACTION_TICK);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(pendingIntent);
    }
}

清单

<receiver android:name=".services.SlowWidgetProvider" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <intent-filter>
        <action android:name="CLOCK_TICK" />
    </intent-filter>
    <intent-filter>
        <action android:name="JOB_CLOCK_TICK" />
    </intent-filter>
    <intent-filter>
        <action android:name="SETTINGS_CHANGED" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.TIME_SET" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.TIMEZONE_CHANGED" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.DATE_CHANGED" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.os.action.DEVICE_IDLE_MODE_CHANGED"/>
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.ACTION_DREAMING_STOPPED" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
        android:resource="@xml/slow_widget_info" />
</receiver>

<service
    android:name=".services.RepeatingJob"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true"/>

<service android:name=".services.WidgetBackgroundService" />

答案 1 :(得分:1)

@Nikiforos提供的代码片段对我来说是一种祝福,虽然我在Android 8上使用它时遇到了很多问题,因此我决定让你知道我是如何解决我的问题的。提供的代码段有两个问题:

  1. 他们使用的是BackgroundService,现在是forbidden in some cases in Android 8
  2. 他们使用隐式广播,这些广播也有been restricted in Android O(你可以阅读它发生的原因here
  3. 要解决第一个问题,我必须从BackgroundService切换到ForegroundService。我知道在许多情况下这是不可能的,但对于那些可以进行更改的人来说,修改代码的说明是:

    1. 更改restartAll()功能,如下所示:
    2. private void restartAll(Context context){
          Intent serviceBG = new Intent(context.getApplicationContext(), WidgetBackgroundService.class);
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
              // for Android 8 start the service in foreground
              context.startForegroundService(serviceBG);
          } else {
              context.startService(serviceBG);
          }
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
              scheduleJob(context);
          } else {
              AppWidgetAlarm appWidgetAlarm = new AppWidgetAlarm(context.getApplicationContext());
              appWidgetAlarm.startAlarm();
          }
      }
      

      private void restartAll(Context context){ Intent serviceBG = new Intent(context.getApplicationContext(), WidgetBackgroundService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // for Android 8 start the service in foreground context.startForegroundService(serviceBG); } else { context.startService(serviceBG); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { scheduleJob(context); } else { AppWidgetAlarm appWidgetAlarm = new AppWidgetAlarm(context.getApplicationContext()); appWidgetAlarm.startAlarm(); } }

      1. 更新WidgetBackgroundService代码中的函数:
      2. onStartCommand()

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            // for Android 8 bring the service to foreground
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                startForeground(1, buildForegroundNotification("Test 3"));
            if(intent != null) {
                if (intent.hasExtra("SHUTDOWN")) {
                    if (intent.getBooleanExtra("SHUTDOWN", false)) {
        
                        if(mMinuteTickReceiver!=null) {
                            unregisterReceiver(mMinuteTickReceiver);
                            mMinuteTickReceiver = null;
                        }
                        stopSelf();
                        return START_NOT_STICKY;
                    }
                }
            }
        
            if(mMinuteTickReceiver==null) {
                registerOnTickReceiver();
            }
            // We want this service to continue running until it is explicitly
            // stopped, so return sticky.
            return START_STICKY;
        }
        

        1. @Override public int onStartCommand(Intent intent, int flags, int startId) { // for Android 8 bring the service to foreground if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForeground(1, buildForegroundNotification("Test 3")); if(intent != null) { if (intent.hasExtra("SHUTDOWN")) { if (intent.getBooleanExtra("SHUTDOWN", false)) { if(mMinuteTickReceiver!=null) { unregisterReceiver(mMinuteTickReceiver); mMinuteTickReceiver = null; } stopSelf(); return START_NOT_STICKY; } } } if(mMinuteTickReceiver==null) { registerOnTickReceiver(); } // We want this service to continue running until it is explicitly // stopped, so return sticky. return START_STICKY; } 函数添加到WidgetBackgroundService:
        2. sendImplicitBroadcast()

          1. 按以下方式修改
            private static void sendImplicitBroadcast(Context ctxt, Intent i) {
                PackageManager pm=ctxt.getPackageManager();
                List<ResolveInfo> matches=pm.queryBroadcastReceivers(i, 0);
            
                for (ResolveInfo resolveInfo : matches) {
                    Intent explicit=new Intent(i);
                    ComponentName cn=
                            new ComponentName(resolveInfo.activityInfo.applicationInfo.packageName,
                                    resolveInfo.activityInfo.name);
            
                    explicit.setComponent(cn);
                    ctxt.sendBroadcast(explicit);
                }
            }
            
            功能:
          2. private static void sendImplicitBroadcast(Context ctxt, Intent i) { PackageManager pm=ctxt.getPackageManager(); List<ResolveInfo> matches=pm.queryBroadcastReceivers(i, 0); for (ResolveInfo resolveInfo : matches) { Intent explicit=new Intent(i); ComponentName cn= new ComponentName(resolveInfo.activityInfo.applicationInfo.packageName, resolveInfo.activityInfo.name); explicit.setComponent(cn); ctxt.sendBroadcast(explicit); } }

            registerOnTickReceiver()

            希望它有所帮助!

答案 2 :(得分:0)

使用窗口小部件本身作为延迟runnable的主机。小部件有一个postDelayed方法。

如果窗口小部件被终止并重新创建,那么还可以在基本初始化过程中重新创建runnable。

编辑:

上述建议是基于OP正在编写自定义视图而不是app小部件的不准确假设。对于app小部件,我最好的建议是:

  • 使用ONE图标创建前台服务。
  • 该服务管理所有小部件,点击通知图标将显示各种活动的提醒和/允许他们进行管理

答案 3 :(得分:0)

试试此代码

    Intent intent = new Intent(ACTION_AUTO_UPDATE_WIDGET);
    PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE) + 1);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);

    AlarmManager alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    alarmMgr.setInexactRepeating(AlarmManager.RTC, calendar.getTimeInMillis(), 60 * 1000, alarmIntent);