Giter VIP home page Giter VIP logo

Comments (4)

zengjingfang avatar zengjingfang commented on June 20, 2024

关于闹钟的资料阅读

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方案

  • Java层实现
  • 手机熄屏后,CPU会进入休眠状态,原来的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==

from androidbox.

zengjingfang avatar zengjingfang commented on June 20, 2024

闹钟源码解析

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,存在如下三种情况:

  • windowLength == AlarmMananger.WINDOW_EXACT: 直接返回为触发的时间
    +windowLength< 0,进行计算:
    • interval == 0 : triggerAtTime +0.75倍闹钟时间,若闹钟时间小于10秒,则就为triggerAtTime
    • interval != 0 : 直接返回triggerAtTime

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

from androidbox.

zengjingfang avatar zengjingfang commented on June 20, 2024

闹钟对齐策略的核心设计

image

  • 相关数据结构:
    • AlarmManagerService对应一个mAlarmBatches,包含了闹钟服务所有的Alarm Batch
    • 一个Batch包含了符合条件的所有Alarm
    • Alarm是一个闹钟的单元,内包含的信息主要由外部接口设定,比如:
      • int type:闹钟类型 ELAPSED_REALTIME、RTC、RTC_WAKEUP等
      • boolean wakeup:闹钟休眠后,是要要醒来,即type为XXX_WAKEUP
      • PendingIntent operation:闹钟触发醒来后的行为
      • long when:触发时间 UTC类型,绝对时间,System.currentTimeMillis()获取
      • long windowLength:浮动窗口,限制maxWhenElapsed到whenElapsed的范围
      • long whenElapsed:相对触发时间,SystemClock.elapsedRealtime()获取
      • long maxWhenElapsed:最大触发时间
      • long repeatInterval:时间间隔,用于重复的时间闹钟
  • 对齐设计**:大致的方案就就是把相近的闹钟放到同一个batch里面批量处理。
  • Batch的规则:
    • 第一个Alarm: 假如设定了一个10min后的闹钟,则start1=当前时间+10,end1=1.75xstart1
    • Add一个Alarm: 如果alarm2的start和end时间在start1的区间内,则放到同一个batch。
    • 更新该Batch: 这个batch的start更新为start2。即这个batch的所有闹钟将在start2这个时间点醒来。
  • AlarmThread是个死循环,一直阻塞在系统的waitForAlarm,一旦有回应则继续往下执行,然后遍历这个batch的alarm,发送alarm的PendingIntent.逐步处理每个醒来的Batch。

from androidbox.

zengjingfang avatar zengjingfang commented on June 20, 2024

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/

from androidbox.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.