AlarmManager(鬧鐘服務)


本節引言:

本節帶來的Android中的AlarmManager(鬧鐘服務),聽名字我們知道可以透過它開發手機鬧鐘類別的APP, 而在文件中的解釋是:在特定的時刻為我們廣播一個指定的Intent,簡單說就是我們自己定一個時間, 然後當到時間時,AlarmManager會為我們廣播一個我們設定好的Intent,例如時間到了,可以指向某個 Activity或Service!另外官方文件中有一些要注意的地方:

1.png
        另外要注意一點的是,AlarmManager主要是用來在某個時刻運行你的程式碼的,即時你的APP在那個特定 時間並沒有運作!還有,從API 19開始,Alarm的機制都是非精確傳遞的,作業系統將會轉換鬧鐘 ,來最小化喚醒和電池的使用!某些新的API會支援嚴格準確的傳遞,見 setWindow(int, long, long, PendingIntent)和setExact(int, long, PendingIntent)。 targetSdkVersion在API 19之前應用仍將繼續使用先前的行為,所有的鬧鐘在要求準確傳遞的情況 下都會準確傳遞。更多詳情可見官方API文件:AlarmManager

#1.Timer類別與AlarmManager類別區別:

如果你學過J2SE的話,那麼你對Timer肯定不會陌生,定時器嘛,一般寫定時任務的時候 肯定離不開他,但是在Android裡,他有一個短板,不太適合那些需要長時間在後台運行的 定時任務,因為Android設備有自己的休眠策略,當長時間的無操作,設備會自動讓CPU進入 休眠狀態,這樣就可能導致Timer中的定時任務無法正常運作!而AlarmManager則不存在 這種情況,因為他具有喚醒CPU的功能,可以確保每次需要執行特定任務時CPU都能正常運作, 或說當CPU處於休眠時註冊的鬧鐘會被保留(可以喚醒CPU),但如果裝置被關閉,或重新 啟動的話,鬧鐘將被清除! (Android手機關機鬧鐘不響...)


2.取得AlarmManager實例物件:

AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);

3.相關方法講解:

  • set(int type,long startTime,PendingIntent pi):一次鬧鐘
  • setRepeating(int type,long startTime,long intervalTime,PendingIntent pi): 重複性鬧鐘,和3有差別,3鬧鐘間隔時間不固定
  • setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi): 重複性鬧鐘,時間不固定
  • cancel(PendingIntent pi):取消AlarmManager的定時服務
  • getNextAlarmClock():得到下一個鬧鐘,傳回值AlarmManager.AlarmClockInfo
  • setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) 和set方法類似,這個鬧鐘運行在系統處於低電模式時有效
  • setExact#(int type, long triggerAtMillis, PendingIntent operation): 在規定的時間精確的執行鬧鐘,比set方法設定的精度更高
  • setTime(long millis):設定係統牆上的時間
  • # setTimeZone(String timeZone):設定係統持續的預設時區
  • setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation): 設定一個鬧鐘在給定的時間窗觸發。類似於set,該方法允許應用程式精確地控製作業系統調 整個鬧鐘觸發時間的程度。

關鍵參數講解

  • Type(鬧鐘類型): 有五個可選值: AlarmManager.ELAPSED_REALTIME: 鬧鐘在手機睡眠狀態下不可用,此狀態下鬧鐘使用相對時間(相對於系統啟動開始),狀態值為3; AlarmManager.ELAPSED_REALTIME_WAKEUP鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,在該狀態下鬧鐘也使用相對時間,狀態值為2; AlarmManager.RTC鬧鐘在睡眠狀態下不可用,該狀態下鬧鐘使用絕對時間,即目前系統時間,狀態值為1; AlarmManager.RTC_WAKEUP表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘使用絕對時間,狀態值為0; AlarmManager.POWER_OFF_WAKEUP表示鬧鐘在手機關機狀態下也能正常進行提示功能,所以是5個狀態中用的最多的狀態之一,該狀態下鬧鐘也是用絕對時間,狀態值為4 ;不過本狀態好像受SDK版本影響,某些版本並不支援;
  • startTime:鬧鐘的第一次執行時間,以毫秒為單位,可以自訂時間,不過一般使用當前時間。 需要注意的是,本屬性與第一個屬性(type)密切相關,如果第一個參數對應的鬧鐘使用的是相對時間 (ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那麼本屬性就得使用相對時間 (相對於系統啟動時間來說),例如當前時間就表示為:SystemClock.elapsedRealtime(); 如果第一個參數對應的鬧鐘使用的是絕對時間(RTC、RTC_WAKEUP、POWER_OFF_WAKEUP), 那麼本屬性就得使用絕對時間,例如當前時間就表示 為:System.currentTimeMillis()。
  • intervalTime:表示兩次鬧鐘執行的間隔時間,也是以毫秒為單位.
  • PendingIntent:綁定了鬧鐘的執行動作,例如發送一個廣播、給予提示等等。 PendingIntent是Intent的封裝類別。要注意的是,如果是透過啟動服務來實作鬧鐘提 示的話,PendingIntent物件的取得就應該採用Pending.getService (Context c,int i,Intent intent,int j)方法;如果是透過廣播來實現鬧鐘 提示的話,PendingIntent物件的取得就應該採用 PendingIntent.getBroadcast (Context c,int i,Intent intent,int j)方法;如果是採用Activity的方式來實 現鬧鐘提示的話,PendingIntent物件的取得就應該採用 PendingIntent.getActivity(Context c,int i,Intent intent,int j)方法。 如果這三種方法錯用了的話,雖然不會報錯,但看不到鬧鐘提示效果。

4.使用範例:一個簡單的定時任務

要說的是,此範例只在Android 4.4以下的系統可行, 5.0以上並不可行,後續如果有5.0 以上AlarmManager的解決方案,到時再補上!另外,這裡用set方法可能有點不準,如果要 更精確的話可以使用setExtra()方法來設定AlarmManager!

來執行效果圖

2.png3.png

##實作程式碼

首先一個簡單的佈局檔案:

activity_main.xml,另外在res建立一個raw資料夾,把音訊檔案丟進去! 另外建立一個只有外層佈局的activity_clock.xml作為鬧鐘響時Activity的佈局!沒東西,就不貼了

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_set"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="设置闹钟" />

    <Button
        android:id="@+id/btn_cancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="关闭闹钟"
        android:visibility="gone" /></LinearLayout>

接著是

MainActivity.java#,也很簡單:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button btn_set;
    private Button btn_cancel;
    private AlarmManager alarmManager;
    private PendingIntent pi;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindViews();
    }

    private void bindViews() {
        btn_set = (Button) findViewById(R.id.btn_set);
        btn_cancel = (Button) findViewById(R.id.btn_cancel);
        alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);

        Intent intent = new Intent(MainActivity.this, ClockActivity.class);
        pi = PendingIntent.getActivity(MainActivity.this, 0, intent, 0);

        btn_set.setOnClickListener(this);
        btn_cancel.setOnClickListener(this);

    }


    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_set:
                Calendar currentTime = Calendar.getInstance();
                new TimePickerDialog(MainActivity.this, 0,
                        new TimePickerDialog.OnTimeSetListener() {
                            @Override
                            public void onTimeSet(TimePicker view,
                                                  int hourOfDay, int minute) {
                                //设置当前时间
                                Calendar c = Calendar.getInstance();
                                c.setTimeInMillis(System.currentTimeMillis());
                                // 根据用户选择的时间来设置Calendar对象
                                c.set(Calendar.HOUR, hourOfDay);
                                c.set(Calendar.MINUTE, minute);
                                // ②设置AlarmManager在Calendar对应的时间启动Activity
                                alarmManager.set(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), pi);
                                Log.e("HEHE",c.getTimeInMillis()+"");   //这里的时间是一个unix时间戳
                                // 提示闹钟设置完毕:
                                Toast.makeText(MainActivity.this, "闹钟设置完毕~"+ c.getTimeInMillis(),
                                        Toast.LENGTH_SHORT).show();
                            }
                        }, currentTime.get(Calendar.HOUR_OF_DAY), currentTime
                        .get(Calendar.MINUTE), false).show();
                btn_cancel.setVisibility(View.VISIBLE);
                break;
            case R.id.btn_cancel:
                alarmManager.cancel(pi);
                btn_cancel.setVisibility(View.GONE);
                Toast.makeText(MainActivity.this, "闹钟已取消", Toast.LENGTH_SHORT)
                        .show();
                break;
        }
    }
}

然後是鬧鈴頁面的

ClockActivity.java

/**
 * Created by Jay on 2015/10/25 0025.
 */
public class ClockActivity extends AppCompatActivity {

    private MediaPlayer mediaPlayer;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_clock);
        mediaPlayer = mediaPlayer.create(this,R.raw.pig);
        mediaPlayer.start();
        //创建一个闹钟提醒的对话框,点击确定关闭铃声与页面
        new AlertDialog.Builder(ClockActivity.this).setTitle("闹钟").setMessage("小猪小猪快起床~")
                .setPositiveButton("关闭闹铃", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        mediaPlayer.stop();
                        ClockActivity.this.finish();
                    }
                }).show();
    }
}

程式碼非常簡單,核心流程如下:

    AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
  • 取得系統提供的AlarmManager服務的物件
  • Intent設定要啟動的元件: Intent intent = new Intent(MainActivity.this, ClockActivity.class);
  • PendingIntent物件設定動作,啟動的是Activity還是Service,又或者是廣播!PendingIntent pi = PendingIntent.getActivity還是Service,又或者是廣播!PendingIntent pi = PendingIntent.getActivity (MainActivity.this, 0, intent, 0);
  • 呼叫AlarmManager的set( )方法設定單次鬧鐘的鬧鐘類型,啟動時間以及PendingIntent物件!alarmManager.set(AlarmManager .RTC_WAKEUP,c.getTimeInMillis(), pi);

#另外假如出現鬧鈴無效的話,你可以從這些方面入手:

  1. #系統版本或手機,5.0以上基本沒戲,小米,自行百度吧~ 2.ClockActivity有註冊沒? 3.假如你用的是alarmManager發送廣播,廣播再啟動Activity的話,則需要為Intent設定一個flag: i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

  2. #4.

  3. 4.png



### ###這些地方沒寫錯吧~別把getActivity寫成了getService等哦~#########################另外,關於AlarmManager結合後來Service實作定時後台任務的例子,可見:###4.2.2 Service進階############5.程式碼範例下載:#########AlarmManagerDemo.zip## ####

本節小結:

好的,本節跟大家講解了Android中的AlarmManager(鬧鐘服務)的使用,除了可以像例子那樣定制 一個自己的鬧鐘,也可以結合Service,Thread來完成輪詢等,用法多多,還需各位自行探究,嗯 本節就到這裡,謝謝~5.gif