天天看點

系統服務之定時服務(AlarmManager)概述鬧鈴類型設定時間設定鬧鈴取消鬧鈴獲得下一次鬧鈴事件常用時間定義源碼分析總結

概述

AlarmManager提供了對系統定時服務的通路接口,使得開發者可以安排在未來的某個時間運作應用。當到達鬧鈴設定時間,系統就會廣播鬧鈴之前注冊的Intent。如果此時目标應用沒有被啟動,系統還會幫你自動啟動目标應用。即使裝置已經進入睡眠已注冊的鬧鈴也會被保持,隻有當裝置關閉或是重新開機的時候會被清除。下面基于Android 8.0源碼來一起學習一下。

鬧鈴類型

AlarmManager中一共提供了四種鬧鐘類型,前兩種對應的System.currentTimeMillis()(系統目前時間)時間,後兩種對應SystemClock.elapsedRealtime()(系統運作時間)時間,以WAKEUP結尾的類型能夠喚醒裝置,其他的類型不能喚醒裝置,直到裝置被喚醒才能出發警報提醒。

public static final int RTC_WAKEUP = ;
    public static final int RTC = ;
    public static final int ELAPSED_REALTIME_WAKEUP = ;
    public static final int ELAPSED_REALTIME = ;
           

設定時間

在AlarmMananger中提供了setTime和setTimeZone方法分别用來設定系統時間和系統預設時區。其中,設定系統時間需要”android.permission.SET_TIME”權限。

傳回值 公開方法
void setTime(long millis)
void setTimeZone(String timeZone)

設定鬧鈴

傳回值 公開方法
void set(int type, long triggerAtMillis, PendingIntent operation)
void set(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler)
void setAlarmClock(AlarmManager.AlarmClockInfo info, PendingIntent operation)
void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)
void setExact(int type, long triggerAtMillis, PendingIntent operation)
void setExact(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler)
void setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)
void setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)
void setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)
void setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation)
void setWindow(int type, long windowStartMillis, long windowLengthMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler)

以上是AlarmManager中提供的所有設定鬧鈴的方法,下面來詳細介紹一下。

  • set
傳回值 公開方法
void set(int type, long triggerAtMillis, PendingIntent operation)
void set(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler)

用于設定一次性鬧鈴,執行時間在設定時間附近,為非精确鬧鈴。方法一和方法二的差別:到達設定時間時方法一會廣播PendingIntent中設定的Intent,而方法二會直接回調OnAlarmListener 中的onAlarm()方法。

  • setExact
傳回值 公開方法
void setExact(int type, long triggerAtMillis, PendingIntent operation)
void setExact(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler)
void setAlarmClock(AlarmManager.AlarmClockInfo info, PendingIntent operation)

用于設定一次性鬧鈴,執行時間更為精準,為精确鬧鈴。方法一和二的差別參見上面set的差別。setAlarmClock方法等同于通過setExact方法設定的RTC_WAKEUP類型的鬧鈴,是以把他歸在setExact中介紹。其中AlarmClockInfo實作了Android序列化接口Parcelable,裡面包含了mTriggerTime(執行時間)和mShowIntent(執行動作)兩個成員變量,可以看做是對鬧鈴事件的一個封裝類。

  • setInexactRepeating和setRepeating
傳回值 公開方法
void setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)
void setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)

setInexactRepeating和setRepeating兩種方法都是用來設定重複鬧鈴的,setRepeating執行時間更為精準。在Android 4.4之後,Android系統為了省電把時間相近的鬧鈴打包到一起進行批量處理,這就使得setRepeating方法設定的鬧鈴不能被精确的執行,必須要使用setExact來代替。

  • setAndAllowWhileIdle和setExactAndAllowWhileIdle
傳回值 公開方法
void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)
void setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)

使用setAndAllowWhileIdle和setExactAndAllowWhileIdle方法設定一次鬧鈴,可以在低功耗模式下被執行,setExactAndAllowWhileIdle執行時間更為精準。手機滅屏以後會進入低功耗模式(low-power idle modes),這個時候你會發現通過setExact設定的鬧鈴也不是100%準确了,需要用setExactAndAllowWhileIdle方法來設定,鬧鈴才能在低功耗模式下被執行。

  • setWindow
傳回值 公開方法
void setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation)
void setWindow(int type, long windowStartMillis, long windowLengthMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler)

用于設定某個時間段内的一次鬧鈴。比如,我想在下午的2點到4點之間設定一次提醒。兩個方法的差別同set。

取消鬧鈴

傳回值 公開方法
void cancel(PendingIntent operation)
void cancel(AlarmManager.OnAlarmListener listener)

用于取消設定過的鬧鈴,分别對應于PendingIntent和AlarmManager.OnAlarmListener方式注冊的鬧鈴。

獲得下一次鬧鈴事件

傳回值 公開方法
AlarmManager.AlarmClockInfo getNextAlarmClock()

用于獲得下一次鬧鈴事件。

常用時間定義

AlarmManager類已經幫我們定義好了常用的時間常量。

public static final long INTERVAL_FIFTEEN_MINUTES =  *  * ;
    public static final long INTERVAL_HALF_HOUR = *INTERVAL_FIFTEEN_MINUTES;
    public static final long INTERVAL_HOUR = *INTERVAL_HALF_HOUR;
    public static final long INTERVAL_HALF_DAY = *INTERVAL_HOUR;
    public static final long INTERVAL_DAY = *INTERVAL_HALF_DAY;
           

源碼分析

設定鬧鈴

通過深入分析AlarmManager的源碼,發現上面提到的所有與鬧鈴設定有關的方法(setXXX)最終都會調用setImpl方法,差別在于不同的應用場景設定的參數不同。 setImpl方法的源碼如下:

private void setImpl(@AlarmType int type, long triggerAtMillis, long windowMillis,
            long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener,
            String listenerTag, Handler targetHandler, WorkSource workSource,
            AlarmClockInfo alarmClock) {
        // 處理非法時間的設定
        if (triggerAtMillis < ) {
            triggerAtMillis = ;
        }

        // 把 OnAlarmListener 封裝起來
        ListenerWrapper recipientWrapper = null;
        if (listener != null) {
            synchronized (AlarmManager.class) {
                if (sWrappers == null) {
                    sWrappers = new ArrayMap<OnAlarmListener, ListenerWrapper>();
                }

                recipientWrapper = sWrappers.get(listener);
                // no existing wrapper => build a new one
                if (recipientWrapper == null) {
                    recipientWrapper = new ListenerWrapper(listener);
                    sWrappers.put(listener, recipientWrapper);
                }
            }

            final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler;
            recipientWrapper.setHandler(handler);
        }
        // 調用Service的set方法
        try {
            mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
                    operation, recipientWrapper, listenerTag, workSource, alarmClock);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
           

那麼這個mService是哪來的呢?通過搜尋在SystemServiceRegistry類中找到了一段靜态方法,SystemServiceRegistry是用來管理所有能由Context.getSystemService方法獲得系統服務的類,通過ServiceManager.getServiceOrThrow根據服務名字來查找到對應的IBinder,進而生成IAlarmManager執行個體并作為參數傳遞給AlarmManager。 mService就是這個IAlarmManager執行個體。

android-.0_r2/frameworks/base/core/java/android/app/SystemServiceRegistry.java
    static {  
           ...
       registerService(Context.ALARM_SERVICE, AlarmManager.class,
                new CachedServiceFetcher<AlarmManager>() {
            @Override
            public AlarmManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                IBinder b = ServiceManager.getServiceOrThrow(Context.ALARM_SERVICE);
                IAlarmManager service = IAlarmManager.Stub.asInterface(b);
                return new AlarmManager(service, ctx);
            }});
          ...
    }
           

接下來看看 IAlarmManager 是由誰實作的呢?熟悉Android源碼的同學自然而然就會想到有可能有一個AlarmManagerService類來提供具體的實作機制。搜一搜還真有這個類,進一步搜尋 IAlarmManager 檢視哪裡實作了這個接口類(當然,熟悉AIDL機制的同學也可以直接搜尋 IAlarmManager ,也能找到)。

private final IBinder mService = new IAlarmManager.Stub() {
        // 設定鬧鈴的方法
        @Override
        public void set(String callingPackage,
                int type, long triggerAtTime, long windowLength, long interval, int flags,
                PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
                WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) {
            final int callingUid = Binder.getCallingUid();

            // make sure the caller is not lying about which package should be blamed for
            // wakelock time spent in alarm delivery
            mAppOps.checkPackage(callingUid, callingPackage);

            // Repeating alarms must use PendingIntent, not direct listener
            if (interval != ) {
                if (directReceiver != null) {
                    throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers");
                }
            }

            if (workSource != null) {
                getContext().enforcePermission(
                        android.Manifest.permission.UPDATE_DEVICE_STATS,
                        Binder.getCallingPid(), callingUid, "AlarmManager.set");
            }

            // No incoming callers can request either WAKE_FROM_IDLE or
            // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate.
            flags &= ~(AlarmManager.FLAG_WAKE_FROM_IDLE
                    | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);

            // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
            // manager when to come out of idle mode, which is only for DeviceIdleController.
            if (callingUid != Process.SYSTEM_UID) {
                flags &= ~AlarmManager.FLAG_IDLE_UNTIL;
            }

            // If this is an exact time alarm, then it can't be batched with other alarms.
            if (windowLength == AlarmManager.WINDOW_EXACT) {
                flags |= AlarmManager.FLAG_STANDALONE;
            }

            // If this alarm is for an alarm clock, then it must be standalone and we will
            // use it to wake early from idle if needed.
            if (alarmClock != null) {
                flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;

            // If the caller is a core system component or on the user's whitelist, and not calling
            // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
            // This means we will allow these alarms to go off as normal even while idle, with no
            // timing restrictions.
            } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
                    || callingUid == mSystemUiUid
                    || Arrays.binarySearch(mDeviceIdleUserWhitelist,
                            UserHandle.getAppId(callingUid)) >= )) {
                flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
                flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
            }
            // 最終會調用AlarmManagerService的setImpl方法
            setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
                    listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
        }
        ...
    };
           

從上面這段源碼可以看出,set方法最終會調用AlarmManagerService的setImpl方法。在setImpl中,根據傳遞的參數經過一系列的計算,傳遞給setImplLocked方法進行下一步處理。

void setImpl(int type, long triggerAtTime, long windowLength, long interval,
            PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
            int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
            int callingUid, String callingPackage) {
        ...
        synchronized (mLock) {
            ...
            setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
                    interval, operation, directReceiver, listenerTag, flags, true, workSource,
                    alarmClock, callingUid, callingPackage);
        }
    }
           

setImplLocked方法會把傳遞過來的參數封裝成一個Alarm對象,調用setImplLocked的另一個重載方法。在setImplLocked中,會去計算Alarm所屬的批次(Batch),然後根據批次進行重新打包,打包後對核心Alarm進行重新規劃,更新下一個Alarm時間。

private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
            long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
            String listenerTag, int flags, boolean doValidate, WorkSource workSource,
            AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
        // 參數封裝成一個Alarm對象
        Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
                operation, directReceiver, listenerTag, workSource, flags, alarmClock,
                callingUid, callingPackage);
        try {
            if (ActivityManager.getService().isAppStartModeDisabled(callingUid, callingPackage)) {
                Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
                        + " -- package not allowed to start");
                return;
            }
        } catch (RemoteException e) {
        }
        removeLocked(operation, directReceiver);
        setImplLocked(a, false, doValidate);
    }

    private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
        // 計算alarm所屬的批次
        int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != )
                ? - : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
        if (whichBatch < ) {
            Batch batch = new Batch(a);
            addBatchLocked(mAlarmBatches, batch);
        } else {
            Batch batch = mAlarmBatches.get(whichBatch);
            if (batch.add(a)) {
                // The start time of this batch advanced, so batch ordering may
                // have just been broken.  Move it to where it now belongs.
                mAlarmBatches.remove(whichBatch);
                addBatchLocked(mAlarmBatches, batch);
            }
        }
       ...
        if (!rebatching) {
            ...
            if (needRebatch) {
                // 重新打包所有的Alarm
                rebatchAllAlarmsLocked(false);
            }
            // 重新規劃核心的Alarm
            rescheduleKernelAlarmsLocked();
            // 更新下一個Alarm的時間
            updateNextAlarmClockLocked();
        }
    }
           

在rescheduleKernelAlarmsLocked方法中會調用setLocked方法,setLocked方法内部會去調用native方法set,最終把Alarm設定到核心中去。

private void setLocked(int type, long when) {
        if (mNativeData != ) {
            // The kernel never triggers alarms with negative wakeup times
            // so we ensure they are positive.
            long alarmSeconds, alarmNanoseconds;
            if (when < ) {
                alarmSeconds = ;
                alarmNanoseconds = ;
            } else {
                alarmSeconds = when / ;
                alarmNanoseconds = (when % ) *  * ;
            }
            // native方法
            set(mNativeData, type, alarmSeconds, alarmNanoseconds);
        } else {
            Message msg = Message.obtain();
            msg.what = ALARM_EVENT;

            mHandler.removeMessages(ALARM_EVENT);
            mHandler.sendMessageAtTime(msg, when);
        }
    }
           

取消鬧鈴

在AlarmManager提供了兩個cancel方法來取消鬧鈴,調用時候需要傳遞一個PendingIntent或是OnAlarmListener執行個體作為參數,從此也可以看出鬧鈴服務内部是以PendingIntent或是OnAlarmListener作為區分不同鬧鈴的唯一辨別的。 cancel(PendingIntent operation) 和 cancel(OnAlarmListener listener) 的實作原理是差不多的,最終都會調用mService.remove方法來移除鬧鈴,這裡以 cancel(PendingIntent operation) 方法為例進行詳細分析。

public void cancel(PendingIntent operation) {
        // 如果 PendingIntent 為空,在N和之後的版本會抛出空指針異常
        if (operation == null) {
            final String msg = "cancel() called with a null PendingIntent";
            if (mTargetSdkVersion >= Build.VERSION_CODES.N) {
                throw new NullPointerException(msg);
            } else {
                Log.e(TAG, msg);
                return;
            }
        }

        try {
            // mService是一個IBinder,由來及對應方法的實作同上面設定鬧鈴中的解析
            mService.remove(operation, null);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
           

mService.remove方法中首先會去判斷PendingIntent 和 IAlarmListener 是否都為空,有一個不為空則調用removeLocked繼續進行處理。

private final IBinder mService = new IAlarmManager.Stub() {
        ...
         // 取消鬧鈴的方法
        @Override
        public void remove(PendingIntent operation, IAlarmListener listener) {
            // PendingIntent 和 IAlarmListener 必須有一個不為空
            if (operation == null && listener == null) {
                Slog.w(TAG, "remove() with no intent or listener");
                return;
            }
            synchronized (mLock) {
                // 調用AlarmManagerService中的removeLocked方法
                removeLocked(operation, listener);
            }
        }
        ...
    };
           

在removeLocked方法中,會根據傳遞過來的參數在mAlarmBatches和mPendingWhileIdleAlarms兩個清單中查詢目前要删除的Alarm,如果比對到則删除。删除後會對所有鬧鈴重新打包,如果删除的是非低功耗模式下啟動的鬧鈴則需要重新整理非低功耗下啟動的鬧鈴設定,最後更新下一次鬧鈴時間。

private void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
        // 周遊查詢并删除比對的Alarms
        boolean didRemove = false;
        for (int i = mAlarmBatches.size() - ; i >= ; i--) {
            Batch b = mAlarmBatches.get(i);
            didRemove |= b.remove(operation, directReceiver);
            if (b.size() == ) {
                mAlarmBatches.remove(i);
            }
        }
        for (int i = mPendingWhileIdleAlarms.size() - ; i >= ; i--) {
            if (mPendingWhileIdleAlarms.get(i).matches(operation, directReceiver)) {
                // Don't set didRemove, since this doesn't impact the scheduled alarms.
                mPendingWhileIdleAlarms.remove(i);
            }
        }

        if (didRemove) {
            if (DEBUG_BATCH) {
                Slog.v(TAG, "remove(operation) changed bounds; rebatching");
            }
            boolean restorePending = false;
            if (mPendingIdleUntil != null && mPendingIdleUntil.matches(operation, directReceiver)) {
                mPendingIdleUntil = null;
                restorePending = true;
            }
            if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(operation, directReceiver)) {
                mNextWakeFromIdle = null;
            }
            // 重新打包所有的鬧鈴
            rebatchAllAlarmsLocked(true);
            if (restorePending) {
                // 重新存儲非低功耗下啟動的鬧鈴
                restorePendingWhileIdleAlarmsLocked();
            }
            // 更新下一次鬧鈴時間
            updateNextAlarmClockLocked();
        }
    }
           

最終在restorePendingWhileIdleAlarmsLocked方法中會調用rescheduleKernelAlarmsLocked和updateNextAlarmClockLocked 重新規劃核心的Alarm并更新下一個Alarm的時間。

void restorePendingWhileIdleAlarmsLocked() {
        ...
        // 重新規劃核心的Alarm
        rescheduleKernelAlarmsLocked();
        // 更新下一個Alarm的時間
        updateNextAlarmClockLocked();
        ...
    }
           

獲得下一次鬧鈴事件

AlarmManager提供了getNextAlarmClock方法來獲得下一次鬧鈴事件,該方法中會把目前的UserId作為查詢依據傳遞到AlarmManagerService中的getNextAlarmClockImpl方法,進而查詢出目前使用者所對應的下一次鬧鈴事件。

frameworks/base/core/java/android/app/AlarmManager.java
    public AlarmClockInfo getNextAlarmClock() {
        return getNextAlarmClock(UserHandle.myUserId());
    }

    public AlarmClockInfo getNextAlarmClock(int userId) {
        try {
            return mService.getNextAlarmClock(userId);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
    private final IBinder mService = new IAlarmManager.Stub() {
        ...
         // 獲得下次鬧鈴事件
        @Override
        public AlarmManager.AlarmClockInfo getNextAlarmClock(int userId) {
            userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                    Binder.getCallingUid(), userId, false /* allowAll */, false /* requireFull */,
                    "getNextAlarmClock", null);

            return getNextAlarmClockImpl(userId);
        }
        ...
    };

    AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
        synchronized (mLock) {
            return mNextAlarmClockForUser.get(userId);
        }
    }
           

設定系統時間

設定系統時間的功能實作流程比較簡單,在AlarmManager提供的setTime方法中直接調用mService.setTime方法,進而通過AlarmManagerService中聲明的native方法setKernelTime把時間設定到底層核心中去。

frameworks/base/core/java/android/app/AlarmManager.java
    public void setTime(long millis) {
        try {
            mService.setTime(millis);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
    private final IBinder mService = new IAlarmManager.Stub() {
        ...
         // 設定系統時鐘的方法
        @Override
        public boolean setTime(long millis) {
            // 注意,設定系統時間需要"android.permission.SET_TIME"權限
            getContext().enforceCallingOrSelfPermission(
                    "android.permission.SET_TIME",
                    "setTime");

            if (mNativeData == ) {
                Slog.w(TAG, "Not setting time since no alarm driver is available.");
                return false;
            }

            synchronized (mLock) {
                // native 方法,直接設定到底層kernel中
                return setKernelTime(mNativeData, millis) == ;
            }
        }
        ...
    };
           

設定系統時區

在AlarmManager提供的setTimeZone方法中直接調用mService的setTimeZone方法,進而調用AlarmManagerService的setTimeZoneImpl方法,并由此方法完成整個系統時區設定的相關邏輯(包括系統屬性值修改、設定核心時區和廣播系統時區變化)。

frameworks/base/core/java/android/app/AlarmManager.java
    public void setTimeZone(String timeZone) {
        ...
        try {
            mService.setTimeZone(timeZone);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
    private final IBinder mService = new IAlarmManager.Stub() {
        ...
         // 設定系統預設時區的方法
        @Override
        public void setTimeZone(String tz) {
            getContext().enforceCallingOrSelfPermission(
                    "android.permission.SET_TIME_ZONE",
                    "setTimeZone");

            final long oldId = Binder.clearCallingIdentity();
            try {
                setTimeZoneImpl(tz);
            } finally {
                Binder.restoreCallingIdentity(oldId);
            }
        }
        ...
    };

    void setTimeZoneImpl(String tz) {
        ...
        boolean timeZoneWasChanged = false;
        synchronized (this) {
            String current = SystemProperties.get(TIMEZONE_PROPERTY);
            if (current == null || !current.equals(zone.getID())) {
                timeZoneWasChanged = true;
                // 設定SystemProperties中時區對應的字段值
                SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
            }

            int gmtOffset = zone.getOffset(System.currentTimeMillis());
            // native 方法,直接設定到底層kernel中
            setKernelTimezone(mNativeData, -(gmtOffset / ));
        }

        TimeZone.setDefault(null);
        // 廣播系統時區變化
        if (timeZoneWasChanged) {
            Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                    | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
            intent.putExtra("time-zone", zone.getID());
            getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
        }
    }
           

總結

本文基于Android 8.0源碼,首先列舉了鬧鈴種類和設定鬧鈴、取消鬧鈴、獲得下一次鬧鈴事件、設定系統時間、設定系統時區等相關方法,然後結合系統源碼詳細分析了各種方法的實作機制。使用過程中有以下幾點需要注意:

1、設定系統時間需要”android.permission.SET_TIME”權限。

2、每當有新的Alarm設定或删除定時服務都會重新計算所屬批次,把時間相近的Alarm打包到一個批次裡(Batch)一起執行,起到優化電池節省耗電的目的。這就是導緻非精确Alarm執行時間存在不确定誤差的根本原因。

3、如果想要在低耗電模式下觸發鬧鈴需要通過setAndAllowWhileIdle和setExactAndAllowWhileIdle方法來設定鬧鈴。

4、如果設定的鬧鈴時間已經過了,鬧鈴會被立即觸發。這個問題可以通過比較鬧鈴設定時間和目前時間來解決。

5、根據實際需求選擇是否設定精确鬧鈴以達到優化電池節省耗電的目的。

6、通過設定時區的源碼可知,如果想要擷取系統時區的相關資訊可以通過監聽Intent.ACTION_TIMEZONE_CHANGED廣播或是直接讀取系統屬性TIMEZONE_PROPERTY。

具體的使用方法可以參考下面兩個連結:

使用示例:Android定時器AlarmManager

不同版本的差異:關于使用AlarmManager的注意事項