●指定した時間に処理を実行する

指定した時間にある処理を実行するにはAlarmManagerを使います。
実行してスケジュールを設定したアプリケーションが終了しても時間になれば処理が実行されます。
まさにアラームで処理をするという感じです。

まずは通常のアプリケーションに対してtest.javaというファイルを追加しました。



あまり変な所にファイルを追加するとややこしい事になります。
そのファイルの中に次の赤文字部分を書き込みます。
ただし、パッケージ名部分は自身のプロジェクトに合わせて書き換えてください。

package test.myapplication;

public class test extends android.content.BroadcastReceiver {
    public void onReceive(android.content.Context context, android.content.Intent intent) {
        android.media.ToneGenerator tg;
        tg = new android.media.ToneGenerator(android.media.AudioManager.STREAM_SYSTEM, android.media.ToneGenerator.MAX_VOLUME);
        tg.startTone(android.media.ToneGenerator.TONE_SUP_ERROR,50);
    }
}
上のプログラムでは少しの時間だけ音を鳴らす処理をしています。


次にAndroidManifest.xmlを開きます。
赤文字部分を追加してください。
赤文字部分がandroidから送られた信号を受信するレシーバーです。
レシーバー部分は必ず、<application>タグの中に書き込んでください。
他の部分に書き込んでも動作しませんので。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="test.myapplication">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <receiver android:name=".test" android:process=":remote" />


        <activity android:name=".MainActivity">
            <intent-filter>
            </intent-filter>
        </activity>
    </application>

</manifest>

これで、アラームを受信する準備は整いました。
次に、アラームを設定します。
AlarmManagerにスケジュールとして渡す時間は1970年1月1日からの経過ミリ秒で渡します。
現在時間がこれより大きい場合には即座にアラームが鳴ります。

//現在の時間を取得
java.util.Calendar calendar = java.util.Calendar.getInstance();

//実行先のクラスを指定
android.content.Intent intent = new android.content.Intent(getApplicationContext(), test.class);
android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast(getApplicationContext(), 0, intent, android.app.PendingIntent.FLAG_CANCEL_CURRENT);

//アラームをミリ秒+10000で10秒後にスケジュールする
android.app.AlarmManager am = (android.app.AlarmManager) getSystemService(ALARM_SERVICE);
am.set(android.app.AlarmManager.RTC, calendar.getTimeInMillis()+10000 , pendingIntent);
このプログラムを実行するとアプリを終了しても画面を消していても約10秒後に音が鳴るはずです。


■日時を指定してアラームを鳴らす

日時を指定するにはCalendarの日時を設定してミリ秒を渡すだけです。

java.util.Calendar calendar = java.util.Calendar.getInstance();

calendar.setTimeInMillis(0);
calendar.set(2017,6-1,24,21,40,10);

android.content.Intent intent = new android.content.Intent(getApplicationContext(), test.class);
android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast(getApplicationContext(), 0, intent, android.app.PendingIntent.FLAG_CANCEL_CURRENT);

android.app.AlarmManager am = (android.app.AlarmManager) getSystemService(ALARM_SERVICE);
am.set(android.app.AlarmManager.RTC, calendar.getTimeInMillis(), pendingIntent);
赤文字の部分で日時を指定しています
2017年6月24日21時40分10秒で設定しています。
ただし、月の部分は、1月を数字の0として表現しますので注意が必要です。


■Calendarの日時を文字列形式で取り出す

個々の値を数字として取り出すには、

int i = calendar.get(java.util.Calendar.SECOND);
int j = calendar.get(java.util.Calendar.MINUTE);

このようにすると簡単に取り出せますが、文字列で取り出すとなると、なんだか面倒です。
toString()をしてもうまく取り出せません。
そこで、SimpleDateFormatを使って目的のフォーマットに変更して取り出します

java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
String str = sdf.format(calendar.getTime());

このようにすると、2017/09/10 21:33:00 のような形式でフォーマットに従って時間の文字列が取り出せます。


■複数のアラームを設定する

上のプログラムではPendingIntent.getBroadcastの第四引数にFLAG_CANCEL_CURRENTを設定しているので、複数回アラームの設定をすると新しいものに更新されてしまい、 複数のアラームを設定できません。
そこで、PendingIntent.getBroadcastの第二引数のrequestCodeに設定毎に違う値を入れる事により複数のアラームを設定できます。

android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast(getApplicationContext(), 0, intent, android.app.PendingIntent.FLAG_CANCEL_CURRENT);
requestCodeはアプリケーション内で重複しないようにする必要があるだけで、他のアプリケーションと重複しても問題ありません。


■複数のアラームのキャンセル

PendingIntent.getBroadcastの第二引数のrequestCode毎に設定したアラームを個々にキャンセルできます。

android.content.Intent intent = new android.content.Intent(getApplicationContext(), test.class);
android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast(getApplicationContext(), 0, intent,0);
android.app.AlarmManager am = (android.app.AlarmManager) getSystemService(ALARM_SERVICE);
am.cancel(pendingIntent);


■アラームと自動起動の違い

アラームは時間が来るとブロードキャストレシーバーを継承したクラスが起動します。
自動起動は電源オン時にブロードキャストレシーバーを継承したクラスが起動します。
つまり、対して違いが無いのです。

そのため、AndroidManifest.xmlのこの部分を
<receiver android:name=".test" android:process=":remote" />
自動起動と同じように
<receiver android:name=".test">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>
のように書き直しても同様に動作します。

そこで、自動起動とアラームを見分ける方法として、アラームマネジャに登録するintentに細工をします。

//現在の時間を取得
java.util.Calendar calendar = java.util.Calendar.getInstance();

//実行先のクラスを指定
android.content.Intent intent = new android.content.Intent(getApplicationContext(), timer.class);
intent.setType("alarm");
android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast(getApplicationContext(), 1, intent, android.app.PendingIntent.FLAG_CANCEL_CURRENT);

//アラームをミリ秒+10000で10秒後にスケジュールする
android.app.AlarmManager am = (android.app.AlarmManager) getSystemService(ALARM_SERVICE);
am.set(android.app.AlarmManager.RTC, calendar.getTimeInMillis()+2000 , pendingIntent);

次にBroadcastReceiverを継承したクラスのonReceiveの引数にて先ほどのintentのTypeにより識別します。

public class test extends android.content.BroadcastReceiver {
            if(intent.getType()!=null && intent.getType().equals("alarm")){
                //アラームマネージャからの処理
            }else if (intent.getAction().equals(android.content.Intent.ACTION_BOOT_COMPLETED)){
                //自動起動による処理
            }
}


■アラームが消える対策

アラームマネージャで登録したとしても設定が消える事があります。
例えば、何らかのツールで消された時など、これは何ともできません。
また、電源をOFFした時には、自動起動で再設定すればよさそうです。
他には、アプリをアンインストールした場合、これは削除されるわけですから考える必要がなさそうです。
しかし、上書きインストールした場合も消去されるそうです。

上書きインストールとは、アプリのアップデートなどもふくまれます。

そこで、PACKAGE_REPLACEDを受け取り、上書きインストールを検知することにします。
まず、AndroidManifest.xmlを開いてintent-filterを追加します。

<receiver android:name=".test">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.PACKAGE_REPLACED" />
        <data android:scheme="package" />
    </intent-filter>
</receiver>
自動起動などの下に新たなintent-filterを追加します。

次にBroadcastReceiverを継承したクラスのonReceiveの引数にてPACKAGE_REPLACEDを受け取ります。

public class timer extends android.content.BroadcastReceiver {
    public void onReceive(android.content.Context context, android.content.Intent intent) {
        if(intent.getType()!=null && intent.getType().equals("alarm")){
            //アラームマネージャからの処理);
        }else if (intent.getAction().equals(android.content.Intent.ACTION_BOOT_COMPLETED)){
            //自動起動による処理
        }else if (intent.getAction().equals(android.content.Intent.ACTION_PACKAGE_REPLACED)){
            if(intent.getDataString().equals("package:"+context.getPackageName())) {
                //自分自身のアプリが更新された
            }
        }
    }
}
自分自身のアプリが更新された後のタイミングで更新後の赤文字の部分が呼ばれます。
赤文字部分で自分自身のパッケージであるのか確認していますが、この処理を無くすと、 他のアプリの更新まで拾ってしまいます。


■一定時間毎に処理をする

1分後とか1時間後とかに一定のタイミングで処理を実行するには、現在の時間に対して未来の時間を再設定する必要があります。

1分毎に処理を実行するには、このようにすると実行できます。

public class timer extends android.content.BroadcastReceiver {
    public void onReceive(android.content.Context context, android.content.Intent intent) {
        //現在の時間を取得して1分後の時間を作る
        java.util.Calendar calendar = java.util.Calendar.getInstance();
        calendar.set(java.util.Calendar.SECOND,0);//秒のセットを0にして
        calendar.add(java.util.Calendar.MINUTE,1);//分に1を足す

        //アラームをセット
        android.app.PendingIntent pendingIntent = android.app.PendingIntent.getBroadcast(context, 0, intent, android.app.PendingIntent.FLAG_CANCEL_CURRENT);
        android.app.AlarmManager am = (android.app.AlarmManager) context.getSystemService(android.content.Context.ALARM_SERVICE);
        am.set(android.app.AlarmManager.RTC, calendar.getTimeInMillis() , pendingIntent);
    }
}
現在の時間から"分"が変わるタイミングの時間を作ってAlarmManagerにセットしています。

まあ、最初の一回だけはどこかで、AlarmManagerにクラスを設定しておく必要があります。
たとえば、メインアクティビティの中で、起動時に設定するなど工夫が必要です。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        android.content.Intent intent = new android.content.Intent(getApplicationContext(), timer.class);
        new timer().onReceive(getApplicationContext(),intent);
    }
}


▲トップページ > android