AndroidのAppWidgetでupdatePeriodMillisを使わずに回す

ほどよくまとまった情報がなかったので自分用メモ。


参考ページ
Build an App Widget  |  Android Developers
Developer.com: Your Home for Java and Open Source Development Knowledge - Developer.com
AndroidのappWidget作成時に行っておくべき消費電力対策についてのまとめ(その1) - 闘争より逃走したい日記


(2番目の内容が、Androidのホーム画面に常駐するアプリを作るには (1/3):Androidで動く携帯Javaアプリ作成入門(10) - @ITとかぶってる気がするんだけど、翻訳? パクリ?)


updatePeriodMillisを使うと、

  • スリープ時でもスリープしてくれない
  • ユーザが更新間隔を変更することができない

などのデメリットがあるのですが、これをAlarmManagerでやろうとしたらちょっと複雑になったのでメモ。(Serviceでやらない理由は3番目の記事に)
メモと言うかスニペットですが。


(追記)
以下の方法だと、スリープしていてもAlarmManagerが止まってくれないっぽい…。調べてみても分からなかったので、ACTION_SCREEN_ON/OFFを捕まえることに


(再追記)
あれ…ACTION_SCREEN_ON/OFFは捕まえられないのか…。30分未満で更新するWidgetは止めとけってことなのかどうなのか。2.1以降ならPowerManager#isScreenOnが使えるということですが…
デフォルトのAnalogClockのソースをざっと見てみたけど、1分毎にViewをちょこっと更新するだけならぶん回しても問題ない、ということかな。眠い

public class FooWidgetProvider extends AppWidgetProvider {
    public static final String URI_SCHEME = "foowidget";

    private Intent buildAlarmIntent( Context context , int appWidgetId ) {
        Intent intent = new Intent( context , CurrencyWidgetProvider.class );
        intent.setAction( AppWidgetManager.ACTION_APPWIDGET_UPDATE );
        intent.putExtra( AppWidgetManager.EXTRA_APPWIDGET_ID , appWidgetId );
        intent.setData( Uri.parse( URI_SCHEME + "://update/" + appWidgetId ) );

        return intent;
    }

    private void deleteAlarm( Context context , Intent intent ) {
        int appWidgetId =
                intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID , AppWidgetManager.INVALID_APPWIDGET_ID );

        if ( appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID ) {
            AlarmManager alarmManager = ( AlarmManager ) context.getSystemService( Context.ALARM_SERVICE );
            alarmManager.cancel( PendingIntent.getBroadcast( context , 0 , buildAlarmIntent( context , appWidgetId ) ,
                    PendingIntent.FLAG_UPDATE_CURRENT ) );

            /* 設定の削除など */
        }
    }

    private void doProc( Context context , Intent intent ) {
        int appWidgetId = intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID , -1 );

        /* メインの処理など */
    }

    @Override
    public void onReceive( Context context , Intent intent ) {
        if ( AppWidgetManager.ACTION_APPWIDGET_DELETED.equals( intent.getAction() ) ) {
            deleteAlarm( context , intent );
        } else if ( AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals( intent.getAction() ) ) {
            if ( !URI_SCHEME.equals( intent.getScheme() ) ) {
                setAlarm( context , intent );
            } else {
                doProc( context , intent );
            }
        }

        super.onReceive( context , intent );
    }

    private void setAlarm( Context context , Intent intent ) {
        for ( int appWidgetId : intent.getExtras().getIntArray( AppWidgetManager.EXTRA_APPWIDGET_IDS ) ) {
            /* 設定の読み込みなど */
            long interval = anyValueYouWant;

            if ( interval != -1 ) {
                AlarmManager alarmManager = ( AlarmManager ) context.getSystemService( Context.ALARM_SERVICE );
                alarmManager.setRepeating( AlarmManager.RTC , System.currentTimeMillis() , interval * 1000 ,
                        PendingIntent.getBroadcast( context , 0 , buildAlarmIntent( context , appWidgetId ) ,
                                PendingIntent.FLAG_UPDATE_CURRENT ) );
            }
        }
    }
}

とこんな感じになりました。無理にonUpdateとかonDeleteとか使わない方がすっきりするんじゃないかなー。


初回はconfigureなActivityから呼び出すのですが、そこは

            Intent updateIntent = new Intent( FooWidgetConfigActivity.this , FooWidgetProvider.class );
            updateIntent.setAction( AppWidgetManager.ACTION_APPWIDGET_UPDATE );
            updateIntent.putExtra( AppWidgetManager.EXTRA_APPWIDGET_IDS , new int[] { _appWidgetId } );
            sendBroadcast( updateIntent );

こんな感じ。