zengjingfang / AndroidBox

Android开发知识、经验、资料等总结,作为个人的开发知识体系
Apache License 2.0
16 stars 3 forks source link

为什么Android系统闹钟会出现延迟的现象? #1

Open zengjingfang opened 6 years ago

zengjingfang commented 6 years ago

在IM的重连闹钟中出现了系统闹钟有延迟不准的情况

zengjingfang commented 6 years ago

关于闹钟的资料阅读

1、 Android Alarm Manager Workflow

AlarmMananger.java (6.0以上)

/** Like {@link #set(int, long, PendingIntent)}, but this alarm will be allowed to
  * execute even when the system is in low-power idle modes.*/
void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)

/** Like {@link #setExact(int, long, PendingIntent)}, but this alarm will be allowed to
 * execute even when the system is in low-power idle modes.*/
void setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)

2、 微信终端跨平台组件 Mars 系列 - 我们如约而至

平台特性优化。虽然 Mars 是跨平台的基础组件,但在很多设计上是需要结合各平台的特性的。例如为了尽量减少频繁的唤醒手机,引入了智能心跳,并且在智能心跳中考虑了 Android 的 alarm 对齐特性(具体实现见smart_heartbeat.cc)。再如在网络切换时,为了平滑切换的过程,使用了 iOS 中网络的特性,在 iOS 中做了延迟处理等。

smart_heartbeat.cc

void SmartHeartbeat::JudgeMIUIStyle() {
    static int test_total_count = 0;
    static uint64_t last_alarm_tick = 0;

    if (test_total_count >= MAX_JUDGE_TIMES) {
        return;
    }

    if (last_alarm_tick == 0) {
        last_alarm_tick = gettickcount();
        return;
    }

    uint64_t span = gettickspan(last_alarm_tick);
    last_alarm_tick = ::gettickcount();

    if (span < 10000)    // for case the same alarm
        return;

    if ((span % 300000) <= 10000 || (300000 - (span % 300000)) <= 10000) {  // judge if curTime is times of five minutes, 10 seconds as the max offset
        xiaomi_style_count_++;
        xinfo2(TSF"m_xiaomiStyleCount++ %0", xiaomi_style_count_);

        if (!current_net_heart_info_.is_stable_ && xiaomi_style_count_ >= 3) {
            xinfo2(TSF"judgeMIUIStyle: is MIUIStyle. xiaomiCount = %0 ", xiaomi_style_count_);
            current_net_heart_info_.is_stable_ = true;
            __SaveINI();
        }
    } else {
        xiaomi_style_count_ = 0;
    }

    test_total_count++;
}

3、小米论坛

第三方闹钟要设置成5的倍数才会准时响,要不然会延迟到5的倍数才响,或不会响。

4、Android定时任务详解

TimerTask方案

5、Android闹钟设置的解决方案

这里利用5.0以上的JobScheduler创建一个定时的任务,定时检测闹钟服务是否存在,没在存在则重新启动闹钟服务。(这里我设置每一分钟检测一次闹钟服务)。

为了让JobScheduler可以在6.0以上进入Doze模式工作,这里针对6.0以上的Doze模式做特殊的处理-忽略电池的优化。

6、Android Doze模式下的AlarmManager策略

Doze 模式的定义 Android 6.0引入了Doze模式,用户拔掉电源,在关闭手机屏幕并且不动的一段时间后,系统便会进入Doze模式。 此模式下通过延缓CPU和网络活动减少电量的消耗。阻止APP访问网络,推迟jobs,syncs,标准 alarms.定期系统会退出Doze模式一小段时间让app完成推迟的活动,此段时间称为 ‘maintenance window’(维护时段),在这段时间系统运行此前挂起的syncs,jobs,alarms,并且让app能够访问网络。

image

7、Android M doze特性预研

image

https://zhuanlan.zhihu.com/p/20323263 http://www.th7.cn/Program/Android/201702/1100512.shtml https://mp.weixin.qq.com/s?src=3&timestamp=1520405873&ver=1&signature=jpcd9lSdexjm3LQrE8ijClC1k*AJHpy0FBLzrEXsTKkrXgcLrLfQHLZE5GIBoMpjpq*w8JNrD78GymmAdkgKOQDUw8EHiEPDnBAVqaja5kfW3Twtq3j2fptER4rCJkeMG8rClb*bHGOm5uRdLbfjRg==

zengjingfang commented 6 years ago

闹钟源码解析

AlarmManagerService#setImpl


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) {

        final long maxElapsed;
        if (windowLength == AlarmManager.WINDOW_EXACT) {
            maxElapsed = triggerElapsed;
        } else if (windowLength < 0) {
            maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval);
            // Fix this window in place, so that as time approaches we don't collapse it.
            windowLength = maxElapsed - triggerElapsed;
        } else {
            maxElapsed = triggerElapsed + windowLength;
        }

}

由上述代码可知,是为了计算maxElapsed,存在如下三种情况:

AlarmManagerService#maxTriggerTime

    // Apply a heuristic to { recurrence interval, futurity of the trigger time } to
    // calculate the end of our nominal delivery window for the alarm.
    static long maxTriggerTime(long now, long triggerAtTime, long interval) {
        // Current heuristic: batchable window is 75% of either the recurrence interval
        // [for a periodic alarm] or of the time from now to the desired delivery time,
        // with a minimum delay/interval of 10 seconds, under which we will simply not
        // defer the alarm.
        long futurity = (interval == 0)
                ? (triggerAtTime - now)
                : interval;
        if (futurity < MIN_FUZZABLE_INTERVAL) {
            futurity = 0;
        }
        return triggerAtTime + (long)(.75 * futurity);
    }

假如nowElapsed=1,triggerElapsed=2,interval=0。则返回 2+(0.75*(2-1))=2.75。这里实际就是增加了闹钟时间的0.75倍,比如说设置了10分钟的闹钟,那么增大7.5分钟。但是如果值小于10秒,则为0。

AlarmManagerService#setImplLocked

    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) {
        // New一个Alarm实体对象
        Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
                operation, directReceiver, listenerTag, workSource, flags, alarmClock,
                callingUid, callingPackage);
       // 省略代码
        removeLocked(operation, directReceiver);
      // 设置闹钟时间
        setImplLocked(a, false, doValidate);
    }

private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
        // 找有没有存在的batch可以存入本次的 alarm
         int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0)
                ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
        if (whichBatch < 0) {
          // 没有则new一个新的batch,并保存到mAlarmBatches里面去
            Batch batch = new Batch(a);
            addBatchLocked(mAlarmBatches, batch);
        } else {
            // 找到一个已存在的batch,并存入刷新mAlarmBatches
            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);
            }
        }
      // 省略代码
      rescheduleKernelAlarmsLocked();
      updateNextAlarmClockLocked();
}

AlarmManagerService#Batch

// Batch 的构造方法
Batch(Alarm seed) {
            start = seed.whenElapsed;// Batch的起始点
            end = seed.maxWhenElapsed;//Batch的结束点
            flags = seed.flags;
            alarms.add(seed);// 本Batch的alarms列表
}

// 根据Batch的边界条件判断该batch是否可以hold住传入的alarm
boolean canHold(long whenElapsed, long maxWhen) {
        // 新的alarm 必须要在旧的alarm的范围内,才能加入
        return (end >= whenElapsed) && (start <= maxWhen);
}

// 给一个batch添加一个新的alarm,并且该batch根据新的alarm的边界取交集,作为batch新的边界
        boolean add(Alarm alarm) {
            boolean newStart = false;
            // narrows the batch if necessary; presumes that canHold(alarm) is true
            int index = Collections.binarySearch(alarms, alarm, sIncreasingTimeOrder);
            if (index < 0) {
                index = 0 - index - 1;
            }
            alarms.add(index, alarm);
            if (DEBUG_BATCH) {
                Slog.v(TAG, "Adding " + alarm + " to " + this);
            }
           // 其实点取大的
            if (alarm.whenElapsed > start) {
                start = alarm.whenElapsed;
                newStart = true;
            }
           //结束点取小的
            if (alarm.maxWhenElapsed < end) {
                end = alarm.maxWhenElapsed;
            }
            flags |= alarm.flags;

            if (DEBUG_BATCH) {
                Slog.v(TAG, "    => now " + this);
            }
            return newStart;
        }

AlarmManagerService.rescheduleKernelAlarmsLocked

    void rescheduleKernelAlarmsLocked() {
        // Schedule the next upcoming wakeup alarm.  If there is a deliverable batch
        // prior to that which contains no wakeups, we schedule that as well.
        long nextNonWakeup = 0;
        if (mAlarmBatches.size() > 0) {
           // 拿到第一个存在wakeup的batch,
            final Batch firstWakeup = findFirstWakeupBatchLocked();
          // 拿到第一个batch
            final Batch firstBatch = mAlarmBatches.get(0);
          // 如果存在wakeup的batch,则设置系统闹钟为第一个batch的时间
            if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
                mNextWakeup = firstWakeup.start;
                mLastWakeupSet = SystemClock.elapsedRealtime();
               // 设置到系统,并这个type会唤醒系统
                setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
            }
            if (firstBatch != firstWakeup) {
                nextNonWakeup = firstBatch.start;
            }
        }
      // 对于不是wakeup的闹钟处理
        if (mPendingNonWakeupAlarms.size() > 0) {
            if (nextNonWakeup == 0 || mNextNonWakeupDeliveryTime < nextNonWakeup) {
                nextNonWakeup = mNextNonWakeupDeliveryTime;
            }
        }
        if (nextNonWakeup != 0 && mNextNonWakeup != nextNonWakeup) {
            mNextNonWakeup = nextNonWakeup;
            setLocked(ELAPSED_REALTIME, nextNonWakeup);
        }
    }

AlarmManagerService#set

// jni到系统底层的闹钟服务
private native void set(long nativeData, int type, long seconds, long nanoseconds);

AlarmManagerService.java

 //
  // duration: alarm设置的时间间隔,比如是10分钟后醒来的闹钟
    static int fuzzForDuration(long duration) {
        if (duration < 15*60*1000) {
            // If the duration until the time is less than 15 minutes, the maximum fuzz
            // is the duration.
            return (int)duration;
        } else if (duration < 90*60*1000) {
            // If duration is less than 1 1/2 hours, the maximum fuzz is 15 minutes,
            return 15*60*1000;
        } else {
            // Otherwise, we will fuzz by at most half an hour.
            return 30*60*1000;
        }
    }
 // 针对非wakeup的alarm的延时处理
   long currentNonWakeupFuzzLocked(long nowELAPSED) {
        long timeSinceOn = nowELAPSED - mNonInteractiveStartTime;
        if (timeSinceOn < 5*60*1000) {
            // If the screen has been off for 5 minutes, only delay by at most two minutes.
            return 2*60*1000;
        } else if (timeSinceOn < 30*60*1000) {
            // If the screen has been off for 30 minutes, only delay by at most 15 minutes.
            return 15*60*1000;
        } else {
            // Otherwise, we will delay by at most an hour.
            return 60*60*1000;
        }
    }

AlarmManagerService.AlarmThread

 private class AlarmThread extends Thread
    {
        public AlarmThread()
        {
            super("AlarmManager");
        }

        public void run()
        {
            ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
            //  死循环,一直监听系统的闹钟
            while (true)
            {
                // 系统层阻塞方法,等着闹钟醒来
                int result = waitForAlarm(mNativeData);
                mLastWakeup = SystemClock.elapsedRealtime();
               // 省略代码
                if (result != TIME_CHANGED_MASK) {
                        boolean hasWakeup = triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
                        if (!hasWakeup && checkAllowNonWakeupDelayLocked(nowELAPSED)) {
                              // 省略代码
                        } else {
                            // now deliver the alarm intents; if there are pending non-wakeup
                            // alarms, we need to merge them in to the list.  note we don't
                            // just deliver them first because we generally want non-wakeup
                            // alarms delivered after wakeup alarms.
                            rescheduleKernelAlarmsLocked();
                            updateNextAlarmClockLocked();
                           // 省略代码
                          //  终于确定要分发这个batch所有的alarm了
                            deliverAlarmsLocked(triggerList, nowELAPSED);
                        }
                    }

                } else {
                    // Just in case -- even though no wakeup flag was set, make sure
                    // we have updated the kernel to the next alarm time.
                    synchronized (mLock) {
                        rescheduleKernelAlarmsLocked();
                    }
                }
            }
        }
    }

void deliverAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED) {
        mLastAlarmDeliveryTime = nowELAPSED;
        for (int i=0; i<triggerList.size(); i++) {
            Alarm alarm = triggerList.get(i);
            // 分发alarm
           mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);

        }
    }

AlarmManagerService#DeliveryTracker#deliverLocked

public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
     // 发送alarm的PendingIntent
    alarm.operation.send(getContext(), 0,
    mBackgroundIntent.putExtra(
    Intent.EXTRA_ALARM_COUNT, alarm.count),
     mDeliveryTracker, mHandler, null,
     allowWhileIdle ? mIdleOptions : null);
    } 
   // 最后要通知到ams
   if (alarm.type == ELAPSED_REALTIME_WAKEUP
                    || alarm.type == RTC_WAKEUP) {
                bs.numWakeup++;
                fs.numWakeup++;
                if (alarm.workSource != null && alarm.workSource.size() > 0) {
                    for (int wi=0; wi<alarm.workSource.size(); wi++) {
                        final String wsName = alarm.workSource.getName(wi);
                        ActivityManager.noteWakeupAlarm(
                                alarm.operation, alarm.workSource.get(wi),
                                (wsName != null) ? wsName : alarm.packageName,
                                alarm.statsTag);
                    }
                } else {
                    ActivityManager.noteWakeupAlarm(
                            alarm.operation, alarm.uid, alarm.packageName, alarm.statsTag);
                }
       }              
}

http://blog.csdn.net/singwhatiwanna/article/details/18448997

zengjingfang commented 6 years ago

闹钟对齐策略的核心设计

image

zengjingfang commented 6 years ago

http://jellybins.github.io/2016/01/26/Android%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E8%AF%A6%E8%A7%A3/