1. 程式人生 > >android 如何讓你的鬧鐘飛起來

android 如何讓你的鬧鐘飛起來

版本分類

鬧鐘使用離不開AlarmManager,但是android不同版本使用的方法也是不一樣的

  • API 19之前AlarmManager的常用方法:

(1)set(int type,long startTime,PendingIntent pi)//該方法用於設定一次性定時器,到達時間執行完結束。

(2)setRepeating(int type,long startTime,long intervalTime,PendingIntent pi)//該方法用於設定可重複執行的定時器。

(3)setInexactRepeating(int type,long startTime,long intervalTime,PendingIntent pi)//該方法用於設定可重複執行的定時器。與setRepeating相比,這個方法更加考慮系統電量,比如系統在低電量情況下可能不會嚴格按照設定的間隔時間執行鬧鐘,因為系統可以調整報警的交付時間,使其同時觸發,避免超過必要的喚醒裝置。

引數說明:

int type: 鬧鐘型別,常用有五個型別,說明如下:

型別 描述
AlarmManager.ELAPSED_REALTIME 表示鬧鐘在手機睡眠狀態下不可用,就是睡眠狀態下不具備喚醒CPU的能力(跟普通Timer差不多了),該狀態下鬧鐘使用相對時間,相對於系統啟動開始。
AlarmManager.ELAPSED_REALTIME_WAKEUP 表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘也使用相對時間
AlarmManager.RTC 表示鬧鐘在睡眠狀態下不可用,該狀態下鬧鐘使用絕對時間,即當前系統時間
AlarmManager.RTC_WAKEUP 表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘使用絕對時間
AlarmManager.POWER_OFF_WAKEUP 表示鬧鐘在手機關機狀態下也能正常進行提示功能,5個狀態中用的最多的狀態之一,該狀態下鬧鐘也是用絕對時間

long startTime: 鬧鐘的第一次執行時間,以毫秒為單位。需要注意的是,本屬性與第一個屬性(type)密切相關,如果第一個引數對應的鬧鐘使用的是相對時間(ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那麼本屬性就得使用相對時間,比如當前時間就表示為:SystemClock.elapsedRealtime();如果第一個引數對應的鬧鐘使用的是絕對時間 (RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那麼本屬性就得使用絕對時間,當前時間就表示 為:System.currentTimeMillis()。

long intervalTime: 表示兩次鬧鐘執行的間隔時間,也是以毫秒為單位。

PendingIntent pi: 到時間後執行的意圖。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)方法。

  • API>=19和API<23 AlarmManager的常用方法:

在這裡插入圖片描述

 am.setExact(AlarmManager.RTC_WAKEUP, time,  pendingIntent);
  • API>23 AlarmManager的常用方法:

在這裡插入圖片描述

  am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pendingIntent);

但是setExact(int type, long triggerAtMillis, PendingIntent operation)以及setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)方法都沒有重複提醒的設定,沒有setRepeating類似API,都是一次性的鬧鐘,重複鬧鐘的實現需要使用借住PendingIntent ,具體如下程式碼

  • 設定鬧鐘工具類
import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;

public class AlarmManagerUtils {

    private static final long TIME_INTERVAL = 5 * 1000;//鬧鐘執行任務的時間間隔
    private Context context;
    public static AlarmManager am;
    public static PendingIntent pendingIntent;

    //
    private AlarmManagerUtils(Context aContext) {
        this.context = aContext;
    }

    //餓漢式單例設計模式
    private static AlarmManagerUtils instance = null;

    public static AlarmManagerUtils getInstance(Context aContext) {
        if (instance == null) {
            synchronized (AlarmManagerUtils.class) {
                if (instance == null) {
                    instance = new AlarmManagerUtils(aContext);
                }
            }
        }
        return instance;
    }

    public void createGetUpAlarmManager() {
        am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent("tsou.cn.alarmclock.utils.AlarmManagerUtils");
        intent.setPackage(context.getPackageName());
        intent.putExtra("msg", "趕緊起床");
        pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);//每隔5秒傳送一次廣播
    }

    /**
     * 設定單個鬧鐘
     */
    @SuppressLint("NewApi")
    public void getUpAlarmManagerStartWork() {
        //版本適配
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0及以上
            am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
                    System.currentTimeMillis(), pendingIntent);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// 4.4及以上
            am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
                    pendingIntent);
        } else {
            am.setRepeating(AlarmManager.RTC_WAKEUP,
                    System.currentTimeMillis(), TIME_INTERVAL, pendingIntent);
        }
    }

    /**
     * 設定單個鬧鐘
     */
    @SuppressLint("NewApi")
    public void getUpAlarmManagerStartWork(long time) {
        if (System.currentTimeMillis() > time) {
            return;
        }
        //版本適配
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0及以上
            am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
                    time, pendingIntent);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// 4.4及以上
            am.setExact(AlarmManager.RTC_WAKEUP, time,
                    pendingIntent);
        } else {
            am.setRepeating(AlarmManager.RTC_WAKEUP,
                    time, TIME_INTERVAL, pendingIntent);
        }
    }

    /**
     * 設定鬧鐘重複
     */
    @SuppressLint("NewApi")
    public void getUpAlarmManagerWorkOnReceiver() {
        //高版本重複設定鬧鐘達到低版本中setRepeating相同效果
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0及以上
            am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
                    System.currentTimeMillis() + TIME_INTERVAL, pendingIntent);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// 4.4及以上
            am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
                    + TIME_INTERVAL, pendingIntent);
        }
    }

    /**
     * 取消鬧鐘
     */
    public void cancelAlarmManagerWork() {
        am.cancel(pendingIntent);
    }
}

PendingIntent在這裡使用的是BroadcastReceiver,具體看廣播的實現

  • 接收資料
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import tsou.cn.alarmclock.service.MyIntentService;

public class MyBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "huangxiaoguo";

    @SuppressLint("NewApi")
    @Override
    public void onReceive(Context context, Intent intent) {
        //高版本重複設定鬧鐘達到低版本中setRepeating相同效果
//        AlarmManagerUtils.getInstance(context).getUpAlarmManagerWorkOnReceiver();
        //
        String extra = intent.getStringExtra("msg");
        Log.i(TAG, "extra ========================" + extra);
        context.startService(new Intent(context,MyIntentService.class));
    }
}

註冊廣播

  <receiver
            android:name=".receiver.MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="tsou.cn.alarmclock.utils.AlarmManagerUtils" />
            </intent-filter>
        </receiver>

BroadcastReceiver 接收到資料一般需要進行操作,一般都是比較耗時工作所以放到IntentService裡面操作,BroadcastReceiver +IntentService這一般是android中的絕配

  • 對資料進行操作
import android.app.IntentService;
import android.content.Intent;
import android.os.SystemClock;
import android.util.Log;

import tsou.cn.alarmclock.utils.SoundUtils;
import tsou.cn.alarmclock.utils.VibrateUtils;

public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // VibrateUtils.Vibrate(this,2000);
        long[] pattern = new long[]{0, 3000, 2000, 3000, 2000, 3000};
        VibrateUtils.Vibrate(pattern, false);
        SoundUtils.startAlarm();
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(7000);
                VibrateUtils.VibrateCancel();
                SoundUtils.stopAlarm();
            }
        }).start();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("huangxiaoguo", "MyIntentService-----------onDestroy");
    }
}

註冊服務

  <service
            android:name=".service.MyIntentService"
            android:process=":alarmclock" />
  • 震動

import android.annotation.SuppressLint;
import android.app.Service;
import android.os.Vibrator;
import android.util.Log;

public class VibrateUtils {


    private static Vibrator vib;

    /**
     * 。兩個Vibrate函式的引數簡單介紹如下:
     * Context  :呼叫該方法的Context例項
     * long milliseconds :震動的時長,單位是毫秒
     * long[] pattern  :自定義震動模式 。陣列中數字的含義依次是[靜止時長,震動時長,靜止時長,震動時長。。。]
     * 時長的單位是毫秒
     * boolean isRepeat : 是否反覆震動,如果是true,反覆震動,如果是false,只震動一次
     *
     * @param milliseconds
     */
    @SuppressLint("MissingPermission")
    public static void Vibrate(long milliseconds) {

        vib = (Vibrator) UIUtils.getContext().getSystemService(Service.VIBRATOR_SERVICE);
        vib.vibrate(milliseconds);
    }

    @SuppressLint("MissingPermission")
    public static void Vibrate(long[] pattern, boolean isRepeat) {
        vib = (Vibrator) UIUtils.getContext().getSystemService(Service.VIBRATOR_SERVICE);
        vib.vibrate(pattern, isRepeat ? 1 : -1);

    }

    /**
     * 震動取消
     */
    @SuppressLint("MissingPermission")
    public static void VibrateCancel() {
        if (vib != null && vib.hasVibrator()) {
            vib.cancel();
            vib=null;
        }

    }
}

  • 聲音

import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;

public class SoundUtils {

    private static MediaPlayer mMediaPlayer;

    //播放預設鈴聲
    public static void startAlarm() {

        Uri mediaUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
        mMediaPlayer = MediaPlayer.create(UIUtils.getContext(),
                mediaUri);
        mMediaPlayer.setLooping(false);
        mMediaPlayer.start();

    }

    //停止預設鈴聲
    public static void stopAlarm() {
        if (mMediaPlayer != null){
            if (mMediaPlayer.isPlaying()){
                mMediaPlayer.stop();
            }
            mMediaPlayer.release();
            mMediaPlayer=null;
        }
    }
}

  • 測試效果
	 @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @HxgOnClick(R.id.btn_alarm)
    private void btnAlarmClick(MyButton view) {
        alarmManagerUtils.getUpAlarmManagerStartWork();
        view.setText("鬧鐘設定完成");
    }

    @HxgOnClick(R.id.btn_cancel)
    private void btnCancelClick(MyButton view) {
        alarmManagerUtils.cancelAlarmManagerWork();
    }

不過正在的效果需要自己去嘗試

  • 許可權
<uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"
        tools:ignore="ProtectedPermissions" />
  • 讓鬧鐘飛起來

結合推送進行操作,這樣就可以更加自由的新增鬧鐘

  private BroadcastReceiver localReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                case "tsou.cn.alarmclock.receiver.MyReceiver":
                    String time_alarm = intent.getStringExtra("time_alarm");
                    Log.e("huangxiaoguo", time_alarm);
                    long stringToDate = DataUtils.getStringToDate(time_alarm, "yyyy-MM-dd HH:mm:ss");
                    alarmManagerUtils.getUpAlarmManagerStartWork(stringToDate * 1000);
                    break;
            }
        }
    };

這裡是接收到推送資料時間,對鬧鐘進行設定

備註:如果你加上保活機制,這樣的話你的鬧鐘就逆天了(保活機制請自行查閱資料),一言不合半夜叫你…