AlarmManager---帮助理解AIDL

2023-12-06 09:50

本文主要是介绍AlarmManager---帮助理解AIDL,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!



AlarmManager研究

侯 亮

 原文地址:http://blog.csdn.net/codefly/article/details/17058425

1.概述

        在Android系统中,闹钟和唤醒功能都是由Alarm Manager Service控制并管理的。我们所熟悉的RTC闹钟以及定时器都和它有莫大的关系。为了便于称呼,我常常也把这个service简称为ALMS。

        另外,ALMS还提供了一个AlarmManager辅助类。在实际的代码中,应用程序一般都是通过这个辅助类来和ALMS打交道的。就代码而言,辅助类只不过是把一些逻辑语义传递给ALMS服务端而已,具体怎么做则完全要看ALMS的实现代码了。

        ALMS的实现代码并不算太复杂,主要只是在管理“逻辑闹钟”。它把逻辑闹钟分成几个大类,分别记录在不同的列表中。然后ALMS会在一个专门的线程中循环等待闹钟的激发,一旦时机到了,就“回调”逻辑闹钟对应的动作。

        以上只是一些概要性的介绍,下面我们来看具体的技术细节。

 

2.AlarmManager

        前文我们已经说过,ALMS只是服务端的东西。它必须向外提供具体的接口,才能被外界使用。在Android平台中,ALMS的外部接口为IAlarmManager。其定义位于frameworks\base\core\java\android\app\IAlarmManager.aidl脚本中,定义截选如下: 

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">interface</span> IAlarmManager {<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> set(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> triggerAtTime, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">in</span> PendingIntent operation);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setRepeating(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> triggerAtTime, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> interval, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">in</span> PendingIntent operation);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setInexactRepeating(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> triggerAtTime, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> interval, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">in</span> PendingIntent operation);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setTime(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> millis);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setTimeZone(String zone);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> remove(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">in</span> PendingIntent operation);
}

        在一般情况下,service的使用者会通过Service Manager Service接口,先拿到它感兴趣的service对应的代理I接口,然后再调用I接口的成员函数向service发出请求。所以按理说,我们也应该先拿到一个IAlarmManager接口,然后再使用它。可是,对Alarm Manager Service来说,情况略有不同,其最常见的调用方式如下:

manager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

其中,getSystemService()返回的不再是IAlarmManager接口,而是AlarmManager对象。 

 

        我们参考AlarmManager.java文件,可以看到AlarmManager类中聚合了一个IAlarmManager接口,

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">private</span> final IAlarmManager mService;

也就是说在执行实际动作时,AlarmManager只不过是把外界的请求转发给内部聚合的IAlarmManager接口而已。

 

2.1  AlarmManager的成员函数

        AlarmManager的成员函数有:

AlarmManager(IAlarmManager service)
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> set(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> triggerAtTime, PendingIntent operation)
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setRepeating(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> triggerAtTime, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> interval, 
PendingIntent operation)
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setInexactRepeating(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> triggerAtTime, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> interval,PendingIntent operation)
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> cancel(PendingIntent operation)
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setTime(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> millis)
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setTimeZone(String timeZone)

即1个构造函数,6个功能函数。基本上完全和IAlarmManager的成员函数一一对应。  

 

      另外,AlarmManager类中会以不同的公共常量来表示多种不同的逻辑闹钟,在Android 4.0的原生代码中有4种逻辑闹钟: 
1)  RTC_WAKEUP 
2)  RTC 
3)  ELAPSED_REALTIME_WAKEUP 
4)  ELAPSED_REALTIME

 

      应用侧通过调用AlarmManager对象的成员函数,可以把语义传递到AlarmManagerService,并由它进行实际的处理。

 

3.AlarmManagerService

        ALMS的重头戏在AlarmManagerService中,这个类继承于IAlarmManager.Stub,所以是个binder实体。它包含的重要成员如下:

其中,mRtcWakeupAlarms等4个ArrayList<Alarm>数组分别对应着前文所说的4种“逻辑闹钟”。为了便于理解,我们可以想象在底层有4个“实体闹钟”,注意,是4个,不是4类。上面每一类“逻辑闹钟”都会对应一个“实体闹钟”,而逻辑闹钟则可以有若干个,它们被存储在ArrayList中,示意图如下:

 ALMS_002

当然,这里所说的“实体闹钟”只是个概念而已,其具体实现和底层驱动有关,在frameworks层不必过多关心。

 

         Frameworks层应该关心的是那几个ArrayList<Alarm>。这里的Alarm对应着逻辑闹钟。

 

3.1  逻辑闹钟

        Alarm是AlarmManagerService的一个内嵌类Alarm,定义截选如下:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">private</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">static</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">class</span> Alarm {<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> count;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> when;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> repeatInterval;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> PendingIntent operation;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> uid;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> pid;. . . . . .
其中记录了逻辑闹钟的一些关键信息。 
  • type域:记录着逻辑闹钟的闹钟类型,比如RTC_WAKEUP、ELAPSED_REALTIME_WAKEUP等;
  • count域:是个辅助域,它和repeatInterval域一起工作。当repeatInterval大于0时,这个域可被用于计算下一次重复激发alarm的时间,详细情况见后文;
  • when域:记录闹钟的激发时间。这个域和type域相关,详细情况见后文;
  • repeatInterval域:表示重复激发闹钟的时间间隔,如果闹钟只需激发一次,则此域为0,如果闹钟需要重复激发,此域为以毫秒为单位的时间间隔;
  • operation域:记录闹钟激发时应该执行的动作,详细情况见后文;
  • uid域:记录设置闹钟的进程的uid;
  • pid域:记录设置闹钟的进程的pid。

 

        总体来说还是比较简单的,我们先补充说明一下其中的count域。这个域是针对重复性闹钟的一个辅助域。重复性闹钟的实现机理是,如果当前时刻已经超过闹钟的激发时刻,那么ALMS会先从逻辑闹钟数组中摘取下Alarm节点,并执行闹钟对应的逻辑动作,然后进一步比较“当前时刻”和“Alarm理应激发的理想时刻”之间的时间跨度,从而计算出Alarm的“下一次理应激发的理想时刻”,并将这个激发时间记入Alarm节点,接着将该节点重新排入逻辑闹钟列表。这一点和普通Alarm不太一样,普通Alarm节点摘下后就不再还回逻辑闹钟列表了。

 

        “当前时刻”和“理应激发时刻”之间的时间跨度会随实际的运作情况而变动。我们分两步来说明“下一次理应激发时刻”的计算公式: 
1)  count  =  (时间跨度 /  repeatInterval ) + 1 ; 
2)  “下一次理应激发时刻” = “上一次理应激发时刻”+ count * repeatInterval ; 

 

        我们画一张示意图,其中绿色的可激发时刻表示“上一次理应激发时刻”,我们假定“当前时刻”分别为now_1处或now_2处,可以看到会计算出不同的“下一次理应激发时刻”,这里用桔红色表示。

 ALMS_003

 

        可以看到,如果当前时刻为now_1,那么它和“上一次理应激发时刻”之间的“时间跨度”是小于一个repeatInterval的,所以count数为1。而如果当前时刻为now_2,那么“时间跨度”与repeatInterval的商取整后为2,所以count数为3。另外,图中那两个虚线箭头对应的可激发时刻,只是用来做刻度的东西。

 

3.2  主要行为

        接下来我们来看ALMS中的主要行为,这些行为和AlarmManager辅助类提供的成员函数相对应。

 

3.2.1   设置alarm

        外界能接触的设置alarm的函数是set():

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> set(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> triggerAtTime, PendingIntent operation)
type:表示要设置的alarm类型。如前文所述,有4个alarm类型。 
triggerAtTime:表示alarm“理应激发”的时间。 
operation:指明了alarm闹铃激发时需要执行的动作,比如执行某种广播通告。

 

        设置alarm的动作会牵扯到一个发起者。简单地说,发起者会向Alarm Manager Service发出一个设置alarm的请求,而且在请求里注明了到时间后需要执行的动作。由于“待执行的动作”一般都不会马上执行,所以要表达成PendingIntent的形式。(PendingIntent的详情可参考其他文章)

 

         另外,triggerAtTime参数的意义也会随type参数的不同而不同。简单地说,如果type是和RTC相关的话,那么triggerAtTime的值应该是标准时间,即从1970 年 1 月 1 日午夜开始所经过的毫秒数。而如果type是其他类型的话,那么triggerAtTime的值应该是从本次开机开始算起的毫秒数。

 

3.2.2   重复性alarm

        另一个设置alarm的函数是setRepeating():

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setRepeating(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> triggerAtTime, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> interval,PendingIntent operation)

其参数基本上和set()函数差不多,只是多了一个“时间间隔”参数。事实上,在Alarm Manager Service一侧,set()函数内部也是在调用setRepeating()的,只不过会把interval设成了0。 

 

         setRepeating()的实现函数如下:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setRepeating(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> triggerAtTime, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> interval,PendingIntent operation) 
{<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (operation == <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">null</span>) {Slog.w(TAG, <span class="str" style="padding: 0px; margin: 0px; color: rgb(0, 96, 128);">"set/setRepeating ignored because there is no intent"</span>);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">return</span>;}synchronized (mLock) {Alarm alarm = <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">new</span> Alarm();alarm.type = type;alarm.when = triggerAtTime;alarm.repeatInterval = interval;alarm.operation = operation;<span class="rem" style="padding: 0px; margin: 0px; color: rgb(0, 128, 0);">// Remove this alarm if already scheduled.</span>removeLocked(operation);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (localLOGV) Slog.v(TAG, <span class="str" style="padding: 0px; margin: 0px; color: rgb(0, 96, 128);">"set: "</span> + alarm);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> index = addAlarmLocked(alarm);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (index == 0) {setLocked(alarm);}}
}

 

        代码很简单,会创建一个逻辑闹钟Alarm,而后调用addAlarmLocked()将逻辑闹钟添加到内部逻辑闹钟数组的某个合适位置。

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">private</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> addAlarmLocked(Alarm alarm) {ArrayList<Alarm> alarmList = getAlarmList(alarm.type);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> index = Collections.binarySearch(alarmList, alarm, mIncreasingTimeOrder);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (index < 0) {index = 0 - index - 1;}<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (localLOGV) Slog.v(TAG, <span class="str" style="padding: 0px; margin: 0px; color: rgb(0, 96, 128);">"Adding alarm "</span> + alarm + <span class="str" style="padding: 0px; margin: 0px; color: rgb(0, 96, 128);">" at "</span> + index);alarmList.add(index, alarm);. . . . . .<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">return</span> index;
}

 

        逻辑闹钟列表是依据alarm的激发时间进行排序的,越早激发的alarm,越靠近第0位。所以,addAlarmLocked()在添加新逻辑闹钟时,需要先用二分查找法快速找到列表中合适的位置,然后再把Alarm对象插入此处。

 ALMS_004

如果所插入的位置正好是第0位,就说明此时新插入的这个逻辑闹钟将会是本类alarm中最先被激发的alarm,而正如我们前文所述,每一类逻辑闹钟会对应同一个“实体闹钟”,此处我们在第0位设置了新的激发时间,明确表示我们以前对“实体闹钟”设置的激发时间已经不准确了,所以setRepeating()中必须重新调整一下“实体闹钟”的激发时间,于是有了下面的句子:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (index == 0) {setLocked(alarm);
}

 

        setLocked()内部会调用native函数set():

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">private</span> native <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> set(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> fd, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> type, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> seconds, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> nanoseconds);

重新设置“实体闹钟”的激发时间。这个函数内部会调用ioctl()和底层打交道。具体代码可参考frameworks/base/services/jni/com_android_server_AlarmManagerService.cpp文件:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">static</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jint fd, 
jint type, jlong seconds, jlong nanoseconds)
{<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">struct</span> timespec ts;ts.tv_sec = seconds;ts.tv_nsec = nanoseconds;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> result = ioctl(fd, ANDROID_ALARM_SET(type), &ts);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (result < 0){ALOGE(<span class="str" style="padding: 0px; margin: 0px; color: rgb(0, 96, 128);">"Unable to set alarm to %lld.%09lld: %s\n"</span>, seconds, nanoseconds, strerror(errno));}
}

 

         我们知道,PendingIntent只是frameworks一层的概念,和底层驱动是没有关系的。所以向底层设置alarm时只需要type信息以及激发时间信息就可以了。

 

3.2.3   取消alarm

        用户端是调用AlarmManager对象的cancel()函数来取消alarm的。这个函数内部其实是调用IAlarmManager的remove()函数。所以我们只来看AlarmManagerService的remove()就可以了。

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> remove(PendingIntent operation) 
{<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (operation == <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">null</span>) {<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">return</span>;}synchronized (mLock) {removeLocked(operation);}
}

       

       注意,在取消alarm时,是以一个PendingIntent对象作为参数的。这个PendingIntent对象正是当初设置alarm时,所传入的那个operation参数。我们不能随便创建一个新的PendingIntent对象来调用remove()函数,否则remove()是不会起作用的。PendingIntent的运作细节不在本文论述范围之内,此处我们只需粗浅地知道,PendingIntent对象在AMS(Activity Manager Service)端会对应一个PendingIntentRecord实体,而ALMS在遍历逻辑闹钟列表时,是根据是否指代相同PendingIntentRecord实体来判断PendingIntent的相符情况的。如果我们随便创建一个PendingIntent对象并传入remove()函数的话,那么在ALMS端势必找不到相符的PendingIntent对象,所以remove()必然无效。

 

         remove()中调用的removeLocked()如下:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> removeLocked(PendingIntent operation) 
{removeLocked(mRtcWakeupAlarms, operation);removeLocked(mRtcAlarms, operation);removeLocked(mElapsedRealtimeWakeupAlarms, operation);removeLocked(mElapsedRealtimeAlarms, operation);
}

简单地说就是,把4个逻辑闹钟数组都遍历一遍,删除其中所有和operation相符的Alarm节点。removeLocked()的实现代码如下:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">private</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> removeLocked(ArrayList<Alarm> alarmList,PendingIntent operation) 
{<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (alarmList.size() <= 0) {<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">return</span>;}<span class="rem" style="padding: 0px; margin: 0px; color: rgb(0, 128, 0);">// iterator over the list removing any it where the intent match</span>Iterator<Alarm> it = alarmList.iterator();<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">while</span> (it.hasNext()) {Alarm alarm = it.next();<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (alarm.operation.equals(operation)) {it.remove();}}
}

 

         请注意,所谓的取消alarm,只是删除了对应的逻辑Alarm节点而已,并不会和底层驱动再打什么交道。也就是说,是不存在针对底层“实体闹钟”的删除动作的。所以,底层“实体闹钟”在到时之时,还是会被“激发”出来的,只不过此时在frameworks层,会因为找不到符合要求的“逻辑闹钟”而不做进一步的激发动作。

 

3.2.4   设置系统时间和时区

        AlarmManager还提供设置系统时间的功能,设置者需要具有android.permission.SET_TIME权限。

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setTime(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> millis) 
{mContext.enforceCallingOrSelfPermission(<span class="str" style="padding: 0px; margin: 0px; color: rgb(0, 96, 128);">"android.permission.SET_TIME"</span>, <span class="str" style="padding: 0px; margin: 0px; color: rgb(0, 96, 128);">"setTime"</span>);SystemClock.setCurrentTimeMillis(millis);
}

 

         另外,还具有设置时区的功能:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> setTimeZone(String tz)

相应地,设置者需要具有android.permission.SET_TIME_ZONE权限。

 

3.3  运作细节

3.3.1   AlarmThread和Alarm的激发

        AlarmManagerService内部是如何感知底层激发alarm的呢?首先,AlarmManagerService有一个表示线程的mWaitThread成员:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">private</span> final AlarmThread mWaitThread = <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">new</span> AlarmThread();

在AlarmManagerService构造之初,就会启动这个专门的“等待线程”。

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> AlarmManagerService(Context context) 
{mContext = context;mDescriptor = init();. . . . . .. . . . . .<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (mDescriptor != -1) {mWaitThread.start();   <span class="rem" style="padding: 0px; margin: 0px; color: rgb(0, 128, 0);">// 启动线程!</span>} <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">else</span> {Slog.w(TAG, <span class="str" style="padding: 0px; margin: 0px; color: rgb(0, 96, 128);">"Failed to open alarm driver. Falling back to a handler."</span>);}
}

AlarmManagerService的构造函数一开始就会调用一个init()函数,该函数是个native函数,它的内部会打开alarm驱动,并返回驱动文件句柄。只要能够顺利打开alarm驱动,ALMS就可以走到mWaitThread.start()一句,于是“等待线程”就启动了。

 

3.3.1.1  AlarmThread中的run()

         AlarmThread本身是AlarmManagerService中一个继承于Thread的内嵌类:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">private</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">class</span> AlarmThread extends Thread<br style="padding: 0px; margin: 0px;" />其最核心的run()函数的主要动作流程图如下: 

 ALMS_005

我们分别来阐述上图中的关键步骤。

 

3.3.1.2  waitForAlarm()

        首先,从上文的流程图中可以看到,AlarmThread线程是在一个while(true)循环里不断调用waitForAlarm()函数来等待底层alarm激发动作的。waitForAlarm()是一个native函数:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">private</span> native <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> waitForAlarm(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> fd);

其对应的C++层函数是android_server_AlarmManagerService_waitForAlarm():

【com_android_server_AlarmManagerService.cpp】
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">static</span> jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env, jobject obj, jint fd)
{<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> result = 0;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">do</span>{result = ioctl(fd, ANDROID_ALARM_WAIT);} <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">while</span> (result < 0 && errno == EINTR);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (result < 0){ALOGE(<span class="str" style="padding: 0px; margin: 0px; color: rgb(0, 96, 128);">"Unable to wait on alarm: %s\n"</span>, strerror(errno));<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">return</span> 0;}<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">return</span> result;
}

 

       当AlarmThread调用到ioctl()一句时,线程会阻塞住,直到底层激发alarm。而且所激发的alarm的类型会记录到ioctl()的返回值中。这个返回值对外界来说非常重要,外界用它来判断该遍历哪个逻辑闹钟列表。

 

3.3.1.3  triggerAlarmsLocked()

        一旦等到底层驱动的激发动作,AlarmThread会开始遍历相应的逻辑闹钟列表:

ArrayList<Alarm> triggerList = <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">new</span> ArrayList<Alarm>();
. . . . . .
final <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> nowRTC = System.currentTimeMillis();
final <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">long</span> nowELAPSED = SystemClock.elapsedRealtime();
. . . . . .
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> ((result & RTC_WAKEUP_MASK) != 0)triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC);
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> ((result & RTC_MASK) != 0)triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC);
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> ((result & ELAPSED_REALTIME_WAKEUP_MASK) != 0)triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms, triggerList, nowELAPSED);
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> ((result & ELAPSED_REALTIME_MASK) != 0)triggerAlarmsLocked(mElapsedRealtimeAlarms, triggerList, nowELAPSED);

 

         可以看到,AlarmThread先创建了一个临时的数组列表triggerList,然后根据result的值对相应的alarm数组列表调用triggerAlarmsLocked(),一旦发现alarm数组列表中有某个alarm符合激发条件,就把它移到triggerList中。这样,4条alarm数组列表中需要激发的alarm就汇总到triggerList数组列表中了。

 

         接下来,只需遍历一遍triggerList就可以了:

Iterator<Alarm> it = triggerList.iterator();
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">while</span> (it.hasNext()) 
{Alarm alarm = it.next();. . . . . .alarm.operation.send(mContext, 0,mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),mResultReceiver, mHandler);<span class="rem" style="padding: 0px; margin: 0px; color: rgb(0, 128, 0);">// we have an active broadcast so stay awake.</span><span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (mBroadcastRefCount == 0) {setWakelockWorkSource(alarm.operation);mWakeLock.acquire();}mInFlight.add(alarm.operation);mBroadcastRefCount++;mTriggeredUids.add(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">new</span> Integer(alarm.uid));BroadcastStats bs = getStatsLocked(alarm.operation);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (bs.nesting == 0) {bs.startTime = nowELAPSED;} <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">else</span> {bs.nesting++;}<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP|| alarm.type == AlarmManager.RTC_WAKEUP) {bs.numWakeup++;ActivityManagerNative.noteWakeupAlarm(alarm.operation);}
}

在上面的while循环中,每遍历到一个Alarm对象,就执行它的alarm.operation.send()函数。我们知道,alarm中记录的operation就是当初设置它时传来的那个PendingIntent对象,现在开始执行PendingIntent的send()操作啦。

 

         PendingIntent的send()函数代码是:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> send(Context context, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> code, Intent intent,OnFinished onFinished, Handler handler) throws CanceledException
{send(context, code, intent, onFinished, handler, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">null</span>);
}

调用了下面的send()函数:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> send(Context context, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> code, Intent intent,OnFinished onFinished, Handler handler, String requiredPermission)throws CanceledException 
{<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">try</span> {String resolvedType = intent != <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">null</span> ? intent.resolveTypeIfNeeded(context.getContentResolver()): <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">null</span>;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> res = mTarget.send(code, intent, resolvedType,onFinished != <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">null</span>? <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">new</span> FinishedDispatcher(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">this</span>, onFinished, handler): <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">null</span>,requiredPermission);<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (res < 0) {<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">throw</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">new</span> CanceledException();}} <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">catch</span> (RemoteException e) {<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">throw</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">new</span> CanceledException(e);}
}

mTarget是个IPendingIntent代理接口,它对应AMS(Activity Manager Service)中的某个PendingIntentRecord实体。需要说明的是,PendingIntent的重要信息都是在AMS的PendingIntentRecord以及PendingIntentRecord.Key对象中管理的。AMS中有一张哈希表专门用于记录所有可用的PendingIntentRecord对象。

 

        相较起来,在创建PendingIntent对象时传入的intent数组,其重要性并不太明显。这种intent数组主要用于一次性启动多个activity,如果你只是希望启动一个activity或一个service,那么这个intent的内容有可能在最终执行PendingIntent的send()动作时,被新传入的intent内容替换掉。

 

         AMS中关于PendingIntentRecord哈希表的示意图如下:

ALMS_006

AMS是整个Android平台中最复杂的一个核心service了,所以我们不在这里做过多的阐述,有兴趣的读者可以参考其他相关文档。

 

3.3.1.4  进一步处理“唤醒闹钟”

        在AlarmThread.run()函数中while循环的最后,会进一步判断,当前激发的alarm是不是“唤醒闹钟”。如果闹钟类型为RTC_WAKEUP或ELAPSED_REALTIME_WAKEUP,那它就属于“唤醒闹钟”,此时需要通知一下AMS:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP|| alarm.type == AlarmManager.RTC_WAKEUP) 
{bs.numWakeup++;ActivityManagerNative.noteWakeupAlarm(alarm.operation);
}

这两种alarm就是我们常说的0型和2型闹钟,它们和我们手机的续航时间息息相关。 

 

         AMS里的noteWakeupAlarm()比较简单,只是在调用BatteryStatsService服务的相关动作,但是却会导致机器的唤醒:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> noteWakeupAlarm(IIntentSender sender) 
{<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (!(sender instanceof PendingIntentRecord)) {<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">return</span>;}BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();synchronized (stats) {<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (mBatteryStatsService.isOnBattery()) {mBatteryStatsService.enforceCallingPermission();PendingIntentRecord rec = (PendingIntentRecord)sender;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> MY_UID = Binder.getCallingUid();<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;BatteryStatsImpl.Uid.Pkg pkg = stats.getPackageStatsLocked(uid, rec.key.packageName);pkg.incWakeupsLocked();}}
}

 

         好了,说了这么多,我们还是画一张AlarmThread示意图作为总结:

 ALMS_007

 

3.3.2   说说AlarmManagerService中的mBroadcastRefCount

        下面我们说说AlarmManagerService中的mBroadcastRefCount,之所以要说它,仅仅是因为我在修改AlarmManagerService代码的时候,吃过它的亏。

         我们先回顾一下处理triggerList列表的代码,如下:

Iterator<Alarm> it = triggerList.iterator();
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">while</span> (it.hasNext()) 
{Alarm alarm = it.next();. . . . . .alarm.operation.send(mContext, 0,mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),mResultReceiver, mHandler);<span class="rem" style="padding: 0px; margin: 0px; color: rgb(0, 128, 0);">// we have an active broadcast so stay awake.</span><span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (mBroadcastRefCount == 0) {setWakelockWorkSource(alarm.operation);mWakeLock.acquire();}mInFlight.add(alarm.operation);mBroadcastRefCount++;. . . . . .. . . . . .
}

可以看到,在AlarmThread.run()中,只要triggerList中含有可激发的alarm,mBroadcastRefCount就会执行加一操作。一开始mBroadcastRefCount的值为0,所以会进入上面那句if语句,进而调用mWakeLock.acquire()。

 

         后来我才知道,这个mBroadcastRefCount变量,是决定何时释放mWakeLock的计数器。AlarmThread的意思很明确,只要还有处于激发状态的逻辑闹钟,机器就不能完全睡眠。那么释放这个mWakeLock的地方又在哪里呢?答案就在alarm.operation.send()一句的mResultReceiver参数中。

 

         mResultReceiver是AlarmManagerService的私有成员变量:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">private</span> final ResultReceiver mResultReceiver = newResultReceiver();

类型为ResultReceiver,这个类实现了PendingIntent.OnFinished接口:

<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">class</span> ResultReceiver implements PendingIntent.OnFinished
当send()动作完成后,框架会间接回调这个对象的onSendFinished()成员函数。
<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">public</span> <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">void</span> onSendFinished(PendingIntent pi, Intent intent, <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">int</span> resultCode,String resultData, Bundle resultExtras) 
{. . . . . .. . . . . .<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (mBlockedUids.contains(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">new</span> Integer(uid))){mBlockedUids.remove(<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">new</span> Integer(uid));} <span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">else</span> {<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (mBroadcastRefCount > 0){mInFlight.removeFirst();mBroadcastRefCount--;<span class="kwrd" style="padding: 0px; margin: 0px; color: rgb(0, 0, 255);">if</span> (mBroadcastRefCount == 0) {mWakeLock.release();} . . . . . .} . . . . . .}. . . . . .
}

也就是说每当处理完一个alarm的send()动作,mBroadcastRefCount就会减一,一旦减为0,就释放mWakeLock。

 

         我一开始没有足够重视这个mBroadcastRefCount,所以把alarm.operation.send()语句包在了一条if语句中,也就是说在某种情况下,程序会跳过alarm.operation.send()一句,直接执行下面的语句。然而此时的mBroadcastRefCount还在坚定不移地加一,这直接导致mBroadcastRefCount再也减不到0了,于是mWakeLock也永远不会释放了。令人头痛的是,这个mWakeLock虽然不让手机深睡眠下去,却也不会点亮屏幕,所以这个bug潜藏了好久才被找到。还真是应了我说的那句话:“魔鬼总藏在细节中。”

这篇关于AlarmManager---帮助理解AIDL的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/461394

相关文章

深入理解Apache Airflow 调度器(最新推荐)

《深入理解ApacheAirflow调度器(最新推荐)》ApacheAirflow调度器是数据管道管理系统的关键组件,负责编排dag中任务的执行,通过理解调度器的角色和工作方式,正确配置调度器,并... 目录什么是Airflow 调度器?Airflow 调度器工作机制配置Airflow调度器调优及优化建议最

一文带你理解Python中import机制与importlib的妙用

《一文带你理解Python中import机制与importlib的妙用》在Python编程的世界里,import语句是开发者最常用的工具之一,它就像一把钥匙,打开了通往各种功能和库的大门,下面就跟随小... 目录一、python import机制概述1.1 import语句的基本用法1.2 模块缓存机制1.

深入理解C语言的void*

《深入理解C语言的void*》本文主要介绍了C语言的void*,包括它的任意性、编译器对void*的类型检查以及需要显式类型转换的规则,具有一定的参考价值,感兴趣的可以了解一下... 目录一、void* 的类型任意性二、编译器对 void* 的类型检查三、需要显式类型转换占用的字节四、总结一、void* 的

深入理解Redis大key的危害及解决方案

《深入理解Redis大key的危害及解决方案》本文主要介绍了深入理解Redis大key的危害及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、背景二、什么是大key三、大key评价标准四、大key 产生的原因与场景五、大key影响与危

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分