2016-07-30 41 views
4

我很難過。我知道這個問題已經被回答了一百次,但我沒有嘗試過。如何精確地每分鐘更新一個小部件

我的問題:我製作了一個Android小部件,每分鐘需要刷新,正好是,就像所有時鐘小部件一樣。 (這個小部件告訴我在我的火車離開之前剩下多少分鐘,一分鐘的錯誤使它無用)。

這裏是我的嘗試遠,相應的結果:

  • 我在appwidget_info.xmlandroid:updatePeriodMillis="60000"。但是,根據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)); 
    

    它完美的一對夫婦分鐘,然後恢復到每五分鐘左右(甚至沒有接近的細微變化)更新。下面是在接收到更新的意圖時的一些時間戳:

    • 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 s的建議,但前者被認爲「不適合這項任務」,我記得後者做得不好。我想我必須在服務中做到這一點,然後服務就會像我使用Handler一樣被殺死。

需要注意的是,AlarmManager在手機連接到電腦時似乎工作正常(大概是因爲這意味着電池正在充電),這並沒有幫助,因爲大部分時間我想知道什麼時候我的火車將離開當我已經在路上...

由於Handler是完全準確的,但只是在一段時間後停止工作,而AlarmManager選項太不準確,但不停止工作,我在想通過讓AlarmManager每十分鐘左右開始一次服務,並使該服務使用一個Handler來每分鐘更新一次顯示來組合它們。不知何故,我覺得這將被Android檢測爲電源並被殺死,無論如何,我相信我一定會漏掉一些明顯的東西。它不應該是很難做什麼本質上是純文本時鐘部件。

編輯:如果它很重要,我使用Android 6.0.1的三星Galaxy Note 4(2016-06-01)上我的小部件。

+0

你真的解決了這個問題嗎? 我面臨幾乎相同的問題。我不在乎精確到秒,我關心的是不停止,在Android 6+設備上發生的事情。 我跳過了'setRepeating()'並去了'setExactAndAllowWhileIdle'來解決這個問題,但仍然 – Nikiforos

+0

不,我管理的最好的是AlarmManager,它每分鐘更新一次,有時更慢(最多五六分鐘)。時鐘小工具如何工作對我來說是個謎。 – tendays

+0

@Nikiforos:你解決了這個問題嗎?我被困在同樣的問題上這麼多天。 – user1090751

回答

0

使用小部件本身作爲延遲可運行的主機。小部件有一個postDelayed方法。

如果小部件被殺死並重新創建,那麼還要將可運行的程序重新創建爲基本初始化的一部分。

編輯:

以上建議是基於不準確的假設OP是寫一個自定義視圖,而不是一個應用程序部件。對於應用小部件,我最好的建議是:

  • 用ONE圖標創建一個前臺服務。
  • 服務管理所有部件和點擊通知圖標會顯示現用的各種提醒和/讓他們來管理
+0

感謝您的回覆,但我不明白您的建議。 「小部件本身」?我看到'View'類有一個'postDelayed'方法,但我沒有訪問它(到視圖)。當我使用應用程序小部件時,我所擁有的只有一個'RemoteViews'對象,整數'appWidgetId'和'AppWidgetManager'(沒有一個沒有'postDelayed'方法)... – tendays

+0

Argh。現在我明白了,我已經將小部件表示爲自定義視圖,而不是Android應用程序小部件。我有一段時間沒有玩過App Widgets,但會在一夜之間給它一些想法...... – RabidMutant

+0

更新了,單一的前臺服務似乎是唯一的方式 – RabidMutant

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); 
+0

正如它在方法名稱中所說的重複間隔是「不精確」的,所以它可能會延遲幾分鐘。 – tendays

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類

@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" /> 
+0

謝謝Nikiforos .... – user1090751

+0

如果我們故意關閉屏幕並在屏幕上,Widget不會更新幾分鐘。有沒有解決這個問題的方法? – user1090751

+0

hm?我沒有注意到那樣的事情。 每當屏幕出現時,都會觸發「觸發」。而且,據我測試它,它工作。 – Nikiforos

相關問題