Android的延遲實(shí)現(xiàn)的幾種解決方案以及原理分析

寫這篇文章的目的量九,是看到群里有人在實(shí)現(xiàn)延遲的時(shí)候,用如下的第四種方法颂碧,個(gè)人感覺有點(diǎn)不妥荠列,為了防止更多的人有這種想法,所以自己抽空深入分析载城,就分析的結(jié)果肌似,寫下此文,希望對部分人有啟示作用诉瓦。

1.實(shí)現(xiàn)延遲的幾種方法川队?

答:
1.java.util.Timer類的:

public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        sched(task, System.currentTimeMillis()+delay, 0);
    }

2.android.os.Handler類:

public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

3.android.app.AlarmManager類:

    @SystemApi
    @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
    public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
            long intervalMillis, OnAlarmListener listener, Handler targetHandler,
            WorkSource workSource) {
        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
                targetHandler, workSource, null);
    }
            

4.Thread.sleep()然后在一定時(shí)間之后再執(zhí)行想執(zhí)行的代碼:

new Thread(new Runnable(){
    Thead.sleep(4*1000);
    doTask();
}).start()

2.他們的各自的實(shí)現(xiàn)原理?

答:

1.Timer的實(shí)現(xiàn)睬澡,是通過內(nèi)部開啟一個(gè)TimerThread:

private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // Wait for queue to become non-empty
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die

                    // Queue nonempty; look at first evt and do the right thing
                    long currentTime, executionTime;
                    task = queue.getMin();
                    synchronized(task.lock) {
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) {
                            if (task.period == 0) { // Non-repeating, remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // Task hasn't yet fired; wait
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // Task fired; run it, holding no locks
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }

是通過wait和延遲時(shí)間到達(dá)的時(shí)候固额,調(diào)用notify來喚起線程繼續(xù)執(zhí)行,這樣來實(shí)現(xiàn)延遲的話煞聪,我們可以會開啟一個(gè)新的線程斗躏,貌似為了個(gè)延遲沒必要這樣吧,等到需要定時(shí)昔脯,頻繁執(zhí)行的任務(wù)啄糙,再考慮這個(gè)吧。

2.Handler的postDelay是通過設(shè)置Message的when為delay的時(shí)間云稚,我們知道當(dāng)我們的應(yīng)用開啟的時(shí)候隧饼,會同步開啟Looper.loop()方法循環(huán)的,不停的通過MeassgeQueue的next方法:

Message next() {
        ......
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
        ......
        }
    }

當(dāng)我們向MessageQueue插入一條延遲的Message的時(shí)候静陈,Looper在執(zhí)行l(wèi)oop方法燕雁,底層會調(diào)用epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);其中的timeoutMillis參數(shù)指定了在沒有事件發(fā)生的時(shí)候epoll_wait調(diào)用阻塞的毫秒數(shù)(milliseconds)。這樣我們在之前的時(shí)間內(nèi)這個(gè)時(shí)候阻塞了是會釋放cpu的資源窿给,等到延遲的時(shí)間到了時(shí)候贵白,再監(jiān)控到事件發(fā)生。在這里可能有人會有疑問崩泡,一直阻塞禁荒,那我接下來的消息應(yīng)該怎么執(zhí)行呢?我們可以看到當(dāng)我們插入消息的時(shí)候的方法:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

阻塞了有兩種方式喚醒角撞,一種是超時(shí)了呛伴,一種是被主動(dòng)喚醒了勃痴,在上面我們可以看到當(dāng)有消息進(jìn)入的時(shí)候,我們會喚醒繼續(xù)執(zhí)行热康,所以我們的即時(shí)消息在延遲消息之后插入是沒有關(guān)系的沛申。然后在延遲時(shí)間到了的時(shí)候,我們也會被喚醒姐军,執(zhí)行對應(yīng)的消息send铁材,以達(dá)到延遲時(shí)間執(zhí)行某個(gè)任務(wù)的目的。
優(yōu)勢:這種延遲在阻塞的時(shí)候奕锌,是會釋放cpu的鎖著觉,不會過多地占用cpu的資源。

3.AlarmManager的延遲的實(shí)現(xiàn)原理惊暴,是通過一個(gè)AlarmManager的set方法饼丘,然后

IAlarmManager mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
                    operation, recipientWrapper, listenerTag, workSource, alarmClock);

這里是通過aidl與AlarmManagerService的所在進(jìn)程進(jìn)行通信,具體的實(shí)現(xiàn)是在AlarmManagerService類里面:

 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();
            if (interval != 0) {
                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");
            }
       
            flags &= ~(AlarmManager.FLAG_WAKE_FROM_IDLE
                    | AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED); DeviceIdleController.
            if (callingUid != Process.SYSTEM_UID) {
                flags &= ~AlarmManager.FLAG_IDLE_UNTIL;
            }

            if (windowLength == AlarmManager.WINDOW_EXACT) {
                flags |= AlarmManager.FLAG_STANDALONE;
            }
            if (alarmClock != null) {
                flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
            } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
                    || Arrays.binarySearch(mDeviceIdleUserWhitelist,
                            UserHandle.getAppId(callingUid)) >= 0)) {
                flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
                flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
            }

            setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
                    listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
        }
    }
}

雖然有人覺得用AlarmManager能夠在應(yīng)用關(guān)閉的情況下辽话,定時(shí)器還能再喚起肄鸽,經(jīng)過自己的測試,當(dāng)殺掉應(yīng)用程序的進(jìn)程油啤,AlarmManager的receiver也是接收不到消息的典徘,但是我相信在這里定時(shí)器肯定是發(fā)送了,但是作為接收方的應(yīng)用程序進(jìn)程被殺掉了村砂,執(zhí)行不了對應(yīng)的代碼烂斋。不過有人也覺得AlarmManager更耗電,是因?yàn)槲覀儓?zhí)行定時(shí)任務(wù)的情況會頻繁喚起cpu础废,但是如果只是用來只是執(zhí)行延遲任務(wù)的話汛骂,個(gè)人覺得和Handler.postDelayed()相比應(yīng)該也不會耗電多的。

2.在上面的第四種方法评腺,達(dá)到的延遲會一直通過Thread.sleep來達(dá)到延遲的話帘瞭,會一直占用cpu的資源,這種方法不贊同使用蒿讥。

3.總結(jié)

如上面我們看到的這樣蝶念,如果是單純的實(shí)現(xiàn)一個(gè)任務(wù)的延遲的話,我們可以用Handler.postDelayed()和AlarmManager.set()來實(shí)現(xiàn)芋绸,用(4)的方法Thread.sleep()的話媒殉,首先開啟一個(gè)新的線程,然后會持有cpu的資源摔敛,用(1)的方法廷蓉,Timer,會開啟一個(gè)死循環(huán)的線程马昙,這樣在資源上面都有點(diǎn)浪費(fèi)桃犬。

如果大家還有更好的延遲解決方案刹悴,可以拿出來大家探討,要是文章有不對的地方攒暇,歡迎拍磚土匀。

如果你們覺得文章對你有啟示作用,希望你們幫忙點(diǎn)個(gè)贊或者關(guān)注下形用,謝謝

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末就轧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子田度,更是在濱河造成了極大的恐慌钓丰,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件每币,死亡現(xiàn)場離奇詭異琢歇,居然都是意外死亡李茫,警方通過查閱死者的電腦和手機(jī)魄宏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門味榛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搏色,“玉大人频轿,你說我怎么就攤上這事航邢〗居” “怎么了乞榨?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵考榨,是天一觀的道長冀惭。 經(jīng)常有香客問我散休,道長戚丸,這世上最難降的妖魔是什么限府? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮署穗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘麻养。我一直安慰自己春贸,他們只是感情好萍恕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著类垫,像睡著了一般悉患。 火紅的嫁衣襯著肌膚如雪售躁。 梳的紋絲不亂的頭發(fā)上回窘,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天啡直,我揣著相機(jī)與錄音,去河邊找鬼。 笑死县忌,一個(gè)胖子當(dāng)著我的面吹牛症杏,可吹牛的內(nèi)容都是我干的厉颤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼资溃!你這毒婦竟也來了溶锭?” 一聲冷哼從身側(cè)響起暖途,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤更米,失蹤者是張志新(化名)和其女友劉穎迟几,沒想到半個(gè)月后类腮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丁眼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年蹂风,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了足淆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巧号。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡靠欢,死狀恐怖门怪,靈堂內(nèi)的尸體忽然破棺而出肋殴,到底是詐尸還是另有隱情酿傍,我是刑警寧澤氯析,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站可霎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏宴杀。R本人自食惡果不足惜癣朗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旺罢。 院中可真熱鬧旷余,春花似錦、人聲如沸扁达。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跪解。三九已至炉旷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窘行。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工饥追, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人罐盔。 一個(gè)月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓但绕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惶看。 傳聞我的和親對象是個(gè)殘疾皇子捏顺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容