草庐IT

android - 如何真正准确地每分钟更新一个小部件

coder 2023-11-26 原文

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

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

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

  • 我把 android:updatePeriodMillis="60000"在我的appwidget_info.xml .但是,如 API Docs 中所述, “使用 updatePeriodMillis 请求的更新将不会每 30 分钟发送一次以上”,实际上这就是我的小部件更新的频率。
  • 我尝试使用 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));
    

    它可以完美运行几分钟,然后恢复为每五分钟左右更新一次(甚至几乎没有变化)。以下是收到更新 Intent 的一些时间戳:
  • 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”。具体来说,小部件只是在某个时候卡住了,根本没有更新。

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

    我还看到了使用 Timers 的建议。和 BroadcastReceiver s 但据说前者“不适合该任务”,我记得在做后者时遇到了麻烦。我认为我必须在服务中执行此操作,然后服务被终止,就像我使用 Handler 时一样s。

    需要注意的是 AlarmManager当手机连接到电脑时似乎运行良好(大概是因为这意味着电池正在充电),这无济于事,因为大多数时候我想知道我的火车什么时候离开是我已经在方法...

    作为 Handler非常准确,但一段时间后停止工作,AlarmManager选项太不准确但不会停止工作,我正在考虑通过 AlarmManager 组合它们每十分钟左右启动一个服务,并让该服务使用 Handler每分钟更新一次显示。不知何故,我觉得这会被 Android 检测为耗电者并被杀死,无论如何我确信我一定会遗漏一些明显的东西。做本质上是纯文本时钟小部件应该不难。

    编辑:如果重要的话,我在带有 Android 6.0.1 的三星 Galaxy Note 4 (2016-06-01) 上使用我的小部件。

    最佳答案

    对不起,我完全忘记了,很忙.. 好吧,我希望你知道你需要什么,下面是 fragment ,希望我没有忘记什么。

    在小部件提供程序类上。

    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);
            }
    }
    

    重复作业类
    @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" />
    

    关于android - 如何真正准确地每分钟更新一个小部件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38678241/

    有关android - 如何真正准确地每分钟更新一个小部件的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

      我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

    7. ruby - 如何指定 Rack 处理程序 - 2

      Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

    8. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

      使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

    9. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

      我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

    10. ruby-on-rails - 渲染另一个 Controller 的 View - 2

      我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

    随机推荐