`
从此醉
  • 浏览: 1128231 次
  • 性别: Icon_minigender_1
  • 来自: US
社区版块
存档分类
最新评论

Android源码分析-Alarm机制与Binder的交互

 
阅读更多

转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/18448997

前言

本次给大家分析的是Android中Alarm的机制以及它和Binder的交互,所用源码为最新的Android4.4。因为Alarm的功能都是通过Binder来完成的,所以,介绍Alarm之前必须要先介绍下它是如何调用Binder来完成定时功能的。由于内容较多,本文会比较长,在文章结构安排上是这样的:首先简单介绍如何使用Alarm并给出其工作原理,接着分析Alarm和Timer以及Handler在完成定时任务上的差别,然后分析Alarm与Binder的交互,最后分析Alarm机制的源码。

什么是Alarm

Alarm是android提供的用于完成闹钟式定时任务的类,系统通过AlarmManager来管理所有的Alarm,Alarm支持一次性定时任务和循环定时任务,它的使用方式很简单,这里不多做介绍,只给出一个简单的示例:

AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(getApplicationContext(), TestActivity.class);
PendingIntent pendIntent = PendingIntent.getActivity(getApplicationContext(),
		0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
//5秒后发送广播,只发送一次
int triggerAtTime = SystemClock.elapsedRealtime() + 5 * 1000;
alarmMgr.set(AlarmManager.ELAPSED_REALTIME, triggerAtTime, pendIntent);

Alarm和Timer以及Handler在定时任务上的区别

相同点

三者都可以完成定时任务,都支持一次性定时和循环定时(注:Handler可以间接支持循环定时任务)

不同点

Handler和Timer在定时上是类似的,二者在系统休眠的情况下无法正常工作,定时任务不会按时触发。Alarm在系统休眠的情况下可以正常工作,并且还可以决定是否唤醒系统,同时Alarm在自身不启动的情况下仍能正常收到定时任务提醒,但是当系统重启或者应用被杀死的情况下,Alarm定时任务会被取消。另外,从Android4.4开始,Alarm事件默认采用非精准方式,即定时任务可能会有小范围的提前或延后,当然我们可以强制采用精准方式,而在此之前,Alarm事件都是精准方式。

Alarm与Binder的交互

Alarm由AlarmManager来管理,从使用方式来看,AlarmManager很简单,我们只要得到了AlarmManager的对象,就可以调用set方法来设定定时任务了,而如何得到AlarmManager对象呢?也很简单,AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);下面我们去看看AlarmManager的set方法,当然AlarmManager还有setRepeating方法,但是二者是类似的。为了更好地理解下面的内容,需要你了解AIDL,如果你还不了解,请参看android跨进程通信(IPC):使用AIDL

code:AlarmManager#set

public void set(int type, long triggerAtMillis, PendingIntent operation) {
	setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null);
}

public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
		PendingIntent operation, WorkSource workSource) {
	setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource);
}

private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
		PendingIntent operation, WorkSource workSource) {
	if (triggerAtMillis < 0) {
		/* NOTYET
		if (mAlwaysExact) {
			// Fatal error for KLP+ apps to use negative trigger times
			throw new IllegalArgumentException("Invalid alarm trigger time "
					+ triggerAtMillis);
		}
		*/
		triggerAtMillis = 0;
	}

	try {
		//定时任务实际上都有mService来完成,也就是说AlarmManager只是一个空壳
		//从下面的构造方法可以看出,这个mService是IAlarmManager类型的,而IAlarmManager是一个接口
		//如果大家了解AIDL就应该知道IAlarmManager应该是一个AIDL接口
		mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,
				workSource);
	} catch (RemoteException ex) {
	}
}

AlarmManager(IAlarmManager service, Context ctx) {
	mService = service;

	final int sdkVersion = ctx.getApplicationInfo().targetSdkVersion;
	mAlwaysExact = (sdkVersion < Build.VERSION_CODES.KITKAT);
}

说明:我对代码进行了注释,从注释可以看出,现在我们需要去找到这个mService,其实我已经帮大家找到了,它就是AlarmManagerService,看下它的类的声明:

class AlarmManagerService extends IAlarmManager.Stub

很显然,AlarmManagerService的确实现了IAlarmManager接口,为什么是显然呢?因为按照AIDL的规范,IAlarmManager.Stub是按照如下这种方式声明的:

public static abstract class Stub extends Binder implements IAlarmManager {

	public static IAlarmManager asInterface(IBinder obj)
	...
}

可见这个Stub类就是一个普通的Binder,只不过它实现了IAlarmManager接口。它还有一个静态方法asInterface,这个方法很有用,通过它,我们就可以将IBinder对象转换成IAlarmManager的实例,进而通过实例来调用其方法。什么是Binder?这个还真不好说,但是我们要知道Binder在Android系统中有大量的应用,大部分Manager都通过Binder来实现(包括AlarmManager),而Service和AIDL也是通过Binder来实现调用的。至于Binder和IBinder的关系,很简单,就是Binder实现了IBinder接口。由于AlarmManagerService继承了IAlarmManager.Stub,所以AlarmManagerService也相当于实现了IAlarmManager接口,所以很显然,AlarmManagerService就是AlarmManager中用于和其交互的mService。不过,还没有完,因为上面的结论不是我瞎猜的,是有代码层面的依据的,下面我将带领大家一起去探索寻找mService的过程,通过这个过程,我们会对Binder机制有更加深刻的认识。

各种Manager和Binder服务的对应关系

首先Dalvik虚拟机会在SystemServer中创建一个叫做ServerThread的线程并调用它的initAndLoop方法,在initAndLoop方法中会创建主线程Looper和初始化各种Manager所对应的Binder服务,我们所常见的Binder服务如WindowManagerService、AlarmManagerService、PowerManagerService等均在这里创建并加入到ServiceManager中进行统一管理。而我们通过getSystemService方式来得到各种Manager的工作主要是在ContextImpl中完成的,不过LayoutInflater、WindowManager以及SearchManager除外。通过ContextImpl我们可以知道各种Manager和Binder服务的一一对应关系,比如AlarmManager对应AlarmManagerService、WindowManager对应WindowManagerService。

上面只是结论,为了真正搞清楚各种Manager所对应的Binder服务,下面将要看一系列代码,首先看SystemServer的代码:

code:SystemServer

public class SystemServer {
    private static final String TAG = "SystemServer";

    public static final int FACTORY_TEST_OFF = 0;
    public static final int FACTORY_TEST_LOW_LEVEL = 1;
    public static final int FACTORY_TEST_HIGH_LEVEL = 2;

    static Timer timer;
    static final long SNAPSHOT_INTERVAL = 60 * 60 * 1000; // 1hr

    // The earliest supported time.  We pick one day into 1970, to
    // give any timezone code room without going into negative time.
    private static final long EARLIEST_SUPPORTED_TIME = 86400 * 1000;

    /**
     * Called to initialize native system services.
	 * 初始化本地系统服务,jni方法
     */
    private static native void nativeInit();

	//main方法,由底层调用
    public static void main(String[] args) {
        if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) {
            // If a device's clock is before 1970 (before 0), a lot of
            // APIs crash dealing with negative numbers, notably
            // java.io.File#setLastModified, so instead we fake it and
            // hope that time from cell towers or NTP fixes it
            // shortly.
            Slog.w(TAG, "System clock is before 1970; setting to 1970.");
            SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);
        }

        if (SamplingProfilerIntegration.isEnabled()) {
            SamplingProfilerIntegration.start();
            timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    SamplingProfilerIntegration.writeSnapshot("system_server", null);
                }
            }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
        }

        // Mmmmmm... more memory!
        dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();

        // The system server has to run all of the time, so it needs to be
        // as efficient as possible with its memory usage.
        VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);

        Environment.setUserRequired(true);

        System.loadLibrary("android_servers");

        Slog.i(TAG, "Entered the Android system server!");

        // 初始化本地服务.
        nativeInit();

		//这里是关键,ServerThread被创建,同时其initAndLoop被调用
        ServerThread thr = new ServerThread();
        thr.initAndLoop();
    }
}

接着看ServerThread的initAndLoop方法,该方法中,主线程Looper会被创建,各种Binder服务会被创建。该方法太长,我进行了截断,只展出我们所关心的代码。

code:ServerThread#initAndLoop

public void initAndLoop() {
	EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN,
		SystemClock.uptimeMillis());
	//主线程Looper被创建
	Looper.prepareMainLooper();

	android.os.Process.setThreadPriority(
			android.os.Process.THREAD_PRIORITY_FOREGROUND);

	BinderInternal.disableBackgroundScheduling(true);
	android.os.Process.setCanSelfBackground(false);
	...此处省略
	//下面是各种Binder服务,从名字我们应该能够大致看出它们所对应的Manager
	Installer installer = null;
	AccountManagerService accountManager = null;
	ContentService contentService = null;
	LightsService lights = null;
	PowerManagerService power = null;
	DisplayManagerService display = null;
	BatteryService battery = null;
	VibratorService vibrator = null;
	AlarmManagerService alarm = null;
	MountService mountService = null;
	NetworkManagementService networkManagement = null;
	NetworkStatsService networkStats = null;
	NetworkPolicyManagerService networkPolicy = null;
	ConnectivityService connectivity = null;
	WifiP2pService wifiP2p = null;
	WifiService wifi = null;
	NsdService serviceDiscovery= null;
	IPackageManager pm = null;
	Context context = null;
	WindowManagerService wm = null;
	BluetoothManagerService bluetooth = null;
	DockObserver dock = null;
	UsbService usb = null;
	SerialService serial = null;
	TwilightService twilight = null;
	UiModeManagerService uiMode = null;
	RecognitionManagerService recognition = null;
	NetworkTimeUpdateService networkTimeUpdater = null;
	CommonTimeManagementService commonTimeMgmtService = null;
	InputManagerService inputManager = null;
	TelephonyRegistry telephonyRegistry = null;
	ConsumerIrService consumerIr = null;
	...此处省略
	Slog.i(TAG, "Alarm Manager");
	//这里AlarmManager对应的Binder服务被创建
	alarm = new AlarmManagerService(context);
	//将AlarmManagerService加入ServiceManager中统一管理
	ServiceManager.addService(Context.ALARM_SERVICE, alarm);

	Slog.i(TAG, "Init Watchdog");
	Watchdog.getInstance().init(context, battery, power, alarm,
			ActivityManagerService.self());
	Watchdog.getInstance().addThread(wmHandler, "WindowManager thread");

	Slog.i(TAG, "Input Manager");
	inputManager = new InputManagerService(context, wmHandler);

	Slog.i(TAG, "Window Manager");
	//这里WindowManager所对应的Binder服务被创建
	wm = WindowManagerService.main(context, power, display, inputManager,
			wmHandler, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,
			!firstBoot, onlyCore);
	//将WindowManagerService加入ServiceManager中统一管理
	ServiceManager.addService(Context.WINDOW_SERVICE, wm);
	ServiceManager.addService(Context.INPUT_SERVICE, inputManager);

	ActivityManagerService.self().setWindowManager(wm);
	...此处省略
}

说明:针对上述代码,我要说明一下,首先其创建的各种Binder服务其实并不是真正的服务,说它们是Binder比较恰当,因为它们的确继承自Binder而不是Service;另一点就是ServiceManager其实也仅仅是个壳子,真正的工作是通过其Binder服务ServiceManagerNative来完成的,ServiceManager提供的工厂方法addService和getService均在ServiceManagerNative中通过代理来实现。

到此为止,我们已经知道各种Binder服务的创建过程,下面我们要看一下Manager是如何和其Binder服务关联上的,再回到getSystemService方法。首先我们要知道Activity的继承关系,如下图所示:


再看如下代码,观察下它们中的getSystemService方法是如何实现的

code:各种getSystemService方法

//#Context
public abstract Object getSystemService(String name);

//#ContextWrapper
@Override
public Object getSystemService(String name) {
	return mBase.getSystemService(name);
}

//#ContextThemeWrapper	
@Override 
public Object getSystemService(String name) {
	if (LAYOUT_INFLATER_SERVICE.equals(name)) {
		if (mInflater == null) {
			mInflater = LayoutInflater.from(mBase).cloneInContext(this);
		}
		return mInflater;
	}
	return mBase.getSystemService(name);
}

//#Activity
@Override
public Object getSystemService(String name) {
	if (getBaseContext() == null) {
		throw new IllegalStateException(
				"System services not available to Activities before onCreate()");
	}

	if (WINDOW_SERVICE.equals(name)) {
		return mWindowManager;
	} else if (SEARCH_SERVICE.equals(name)) {
		ensureSearchManager();
		return mSearchManager;
	}
	return super.getSystemService(name);
}

说明:通过上述代码可以看出LayoutInflater、WindowManager以及SearchManager的处理比较特殊,直接在方法中返回对象,剩下的所有Manager将通过mBase.getSystemService(name)返回,现在问题转移到mBase上面,mBase是什么呢?我已经查清楚了,Activity的mBase就是ContextImpl对象,何以见得?请看下面分析

ContextImpl:Activity的mBase

不知道大家对我写的另外一篇源码分析是否有印象:Android源码分析-Activity的启动过程,在这篇文章中我指出:Activity的最终启动过程由ActivityThread中的performLaunchActivity方法来完成,在performLaunchActivity中,Activity的mBase将被赋值为ContextImpl对象,下面通过代码来说明:

code:mBase的赋值过程

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
	...
	if (activity != null) {
		//这里的appContext就是ContextImpl对象
		Context appContext = createBaseContextForActivity(r, activity);
		
		CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
		Configuration config = new Configuration(mCompatConfiguration);
		if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
				+ r.activityInfo.name + " with config " + config);
		//通过Activity的attach方法将ContextImpl对象赋值给mBase
		activity.attach(appContext, this, getInstrumentation(), r.token,
				r.ident, app, r.intent, r.activityInfo, title, r.parent,
				r.embeddedID, r.lastNonConfigurationInstances, config);
		...
	}
	...
}

private Context createBaseContextForActivity(ActivityClientRecord r,
		final Activity activity) {
	//很显然,此方法返回的就是ContextImpl对象
	ContextImpl appContext = new ContextImpl();
	appContext.init(r.packageInfo, r.token, this);
	appContext.setOuterContext(activity);
	Context baseContext = appContext;
	...
	return baseContext;
}

final void attach(Context context, ActivityThread aThread,
		Instrumentation instr, IBinder token, int ident,
		Application application, Intent intent, ActivityInfo info,
		CharSequence title, Activity parent, String id,
		NonConfigurationInstances lastNonConfigurationInstances,
		Configuration config) {
	//将context赋值给mBase,这里的context就是performLaunchActivity中的appContext,即ContextImpl对象
	attachBaseContext(context);

	mFragments.attachActivity(this, mContainer, null);
	
	mWindow = PolicyManager.makeNewWindow(this);
	mWindow.setCallback(this);
	...
}

@Override protected void attachBaseContext(Context newBase) {
	super.attachBaseContext(newBase);
	//这里很显然,对mBase进行赋值
	mBase = newBase;
}

说明:看了上面的代码,我们已经知道,mBase的确是ContextImpl对象。上面我提到:除了LayoutInflater、WindowManager以及SearchManager,剩下的所有Manager将通过mBase.getSystemService(name)返回,那么现在,我们去看下ContextImpl中的getSystemService方法。

code:ContextImpl#getSystemService

class ContextImpl extends Context {
	...
	@Override
    public Object getSystemService(String name) {
		//首先从SYSTEM_SERVICE_MAP根据服务名得到一个fetcher对象
		//其中SYSTEM_SERVICE_MAP是一个HashMap,然后再通过fetcher去取service
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }
	...
}

说明:看了ContextImpl的getSystemService方法,发现失望了,还没有找到真正的实现,看来还要去看这个fetcher是怎么回事,下面请看代码:

code:服务注册过程和fetcher

//一个哈希表,用来根据服务名存储对应服务的ServiceFetcher(可以理解为通过ServiceFetcher可以得到服务)
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
		new HashMap<String, ServiceFetcher>();

//注册服务,将服务的fetcher存到哈希表中
private static void registerService(String serviceName, ServiceFetcher fetcher) {
	if (!(fetcher instanceof StaticServiceFetcher)) {
		fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
	}
	SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}
//静态代码块,注册各种服务
//也就是说,ContextImpl这个类被加载的时候就会把如下的各种服务的fetcher加入到哈希表中
//这样我们通过getSystemService就可以得到一个服务的fetcher,再通过fetcher去得到服务的对象
static {
	registerService(ACCESSIBILITY_SERVICE, new ServiceFetcher() {
			public Object getService(ContextImpl ctx) {
				return AccessibilityManager.getInstance(ctx);
			}});

	registerService(CAPTIONING_SERVICE, new ServiceFetcher() {
			public Object getService(ContextImpl ctx) {
				return new CaptioningManager(ctx);
			}});

	registerService(ACCOUNT_SERVICE, new ServiceFetcher() {
			public Object createService(ContextImpl ctx) {
				IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);
				IAccountManager service = IAccountManager.Stub.asInterface(b);
				return new AccountManager(ctx, service);
			}});

	registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
			public Object createService(ContextImpl ctx) {
				return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
			}});

	//这里是Alarm服务的注册
	registerService(ALARM_SERVICE, new ServiceFetcher() {
			public Object createService(ContextImpl ctx) {
				/**还记得ALARM_SERVICE吗?
				 * alarm = new AlarmManagerService(context);
				 * 将AlarmManagerService加入ServiceManager中统一管理
				 * ServiceManager.addService(Context.ALARM_SERVICE, alarm);
				 */
				//通过ServiceManager的getService得到Alarm服务,很显然,下面的b就是AlarmManagerService对象
				IBinder b = ServiceManager.getService(ALARM_SERVICE);
				//还记得AlarmManager中的mService吗?就是这里的service,很显然它是一个Binder服务
				//分析到这里,事实已经得出:AlarmManager所对应的Binder服务就是AlarmManagerService
				IAlarmManager service = IAlarmManager.Stub.asInterface(b);
				return new AlarmManager(service, ctx);
			}});

	registerService(AUDIO_SERVICE, new ServiceFetcher() {
			public Object createService(ContextImpl ctx) {
				return new AudioManager(ctx);
			}});
	...省略:下面还有许多服务
}
说明:通过上述代码的分析,相信大家已经很明确Manager是如何和Binder服务一一对应的,然后Manager的各种功能将会交由Binder服务来完成。尽管我只详细分析了AlarmManager和AlarmManagerService的对应过程,但是其它Manager的对应过程是几乎完全一样的。好了,到了这里,我们已经把Manager和Binder服务的对应过程进行了深入地分析,下面开始我们的最后一个主题:Alarm机制的源码分析。

Alarm机制分析

通过上面的一系列分析,我们知道AlarmManager的所有功能都是通过AlarmManagerService来完成的,在分析源码之前,我先来描述下Alarm的工作原理:从Android4.4开始,Alarm默认为非精准模式,除非显示指定采用精准模式。在非精准模式下,Alarm是批量提醒的,每个alarm根据其触发时间和最大触发时间的不同会被加入到不同的batch中,同一个batch的不同alarm是同时发生的,这样就无法实现精准闹钟,官方的解释是批量处理可以减少设备被唤醒次数以及节约电量,不过针对精准闹钟,官方预留的方法是setExact和setWindow,二者都是通过将时间窗口定义为0来实现精准闹钟的,因为时间窗口为0,意味着触发时间和最大触发时间是一样的,因为典型的情况下:最大触发时间= 触发时间 + 时间窗口。同时所有的batch是按开始时间升序排列的,在一个batch内部,不同的闹钟也是按触发时间升序排列的,所以闹钟的唤醒顺序是按照batch的排序依次触发的,而同一个batch中的alarm是同时触发的,可以用下面这个示意图来描述:

上图是示意图,系统中可以有多个batch,每个batch中可以有多个alarm。下面我们分析一下AlarmManagerService中的代码。其入口方法为set,set又调用了setImplLocked,所以我们直接看setImplLocked。

code:AlarmManagerService#setImplLocked

private void setImplLocked(int type, long when, long whenElapsed, long maxWhen, long interval,
		PendingIntent operation, boolean isStandalone, boolean doValidate,
		WorkSource workSource) {
	/**创建一个alarm,其中各参数的含义如下:
	 * type 闹钟类型 ELAPSED_REALTIME、RTC、RTC_WAKEUP等
	 * when 触发时间 UTC类型,绝对时间,通过System.currentTimeMillis()得到
	 * whenElapsed 相对触发时间,自开机算起,含休眠,通过SystemClock.elapsedRealtime()得到
	 * maxWhen 最大触发时间
	 * interval 触发间隔,针对循环闹钟有效
	 * operation 闹钟触发时的行为,PendingIntent类型
	 */
	Alarm a = new Alarm(type, when, whenElapsed, maxWhen, interval, operation, workSource);
	//根据PendingIntent删除之前已有的同一个闹钟
	removeLocked(operation);

	boolean reschedule;
	//尝试将alarm加入到合适的batch中,如果alarm是独立的或者无法找到合适的batch去容纳此alarm,返回-1
	int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);
	if (whichBatch < 0) {
		//没有合适的batch去容纳alarm,则新建一个batch
		Batch batch = new Batch(a);
		batch.standalone = isStandalone;
		//将batch加入mAlarmBatches中,并对mAlarmBatches进行排序:按开始时间升序排列
		reschedule = addBatchLocked(mAlarmBatches, batch);
	} else {
		//如果找到合适了batch去容纳此alarm,则将其加入到batch中
		Batch batch = mAlarmBatches.get(whichBatch);
		//如果当前alarm的加入引起了batch开始时间和结束时间的改变,则reschedule为true
		reschedule = batch.add(a);
		if (reschedule) {
			//由于batch的起始时间发生了改变,所以需要从列表中删除此batch并重新加入、重新对batch列表进行排序
			mAlarmBatches.remove(whichBatch);
			addBatchLocked(mAlarmBatches, batch);
		}
	}

	if (DEBUG_VALIDATE) {
		if (doValidate && !validateConsistencyLocked()) {
			Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when
					+ " when(hex)=" + Long.toHexString(when)
					+ " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen
					+ " interval=" + interval + " op=" + operation
					+ " standalone=" + isStandalone);
			rebatchAllAlarmsLocked(false);
			reschedule = true;
		}
	}

	if (reschedule) {
		rescheduleKernelAlarmsLocked();
	}
}

说明:通过上述代码可以看出,当我们创建一个alarm的时候,仅仅是将这个alarm加入到某个batch中,系统中有一个batch列表,专门用于存储所有的alarm。可是仅仅把alarm加入到batch中还不行,系统还必须提供一个类似于Looper的东西一直去遍历这个列表,一旦它发现有些alarm的时间已经到达就要把它取出来去执行。事实上,AlarmManagerService中的确有一个类似于Looper的东西去干这个事情,只不过它是个线程,叫做AlarmThread。下面看它的代码:

code:AlarmManagerService#AlarmThread

private class AlarmThread extends Thread
{
	public AlarmThread()
	{
		super("AlarmManager");
	}
	
	public void run()
	{
		//当前时间触发的alarm列表
		ArrayList<Alarm> triggerList = new ArrayList<Alarm>();

		while (true)
		{
			//jni方法,顾名思义,阻塞式方法,当有alarm的时候会被唤醒
			int result = waitForAlarm(mDescriptor);

			triggerList.clear();

			if ((result & TIME_CHANGED_MASK) != 0) {
				if (DEBUG_BATCH) {
					Slog.v(TAG, "Time changed notification from kernel; rebatching");
				}
				remove(mTimeTickSender);
				//将所有的alarm重新排序
				rebatchAllAlarms();
				mClockReceiver.scheduleTimeTickEvent();
				Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
				intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
						| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
				mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
			}
			
			synchronized (mLock) {
				final long nowRTC = System.currentTimeMillis();
				final long nowELAPSED = SystemClock.elapsedRealtime();
				if (localLOGV) Slog.v(
					TAG, "Checking for alarms... rtc=" + nowRTC
					+ ", elapsed=" + nowELAPSED);

				if (WAKEUP_STATS) {
					if ((result & IS_WAKEUP_MASK) != 0) {
						long newEarliest = nowRTC - RECENT_WAKEUP_PERIOD;
						int n = 0;
						for (WakeupEvent event : mRecentWakeups) {
							if (event.when > newEarliest) break;
							n++; // number of now-stale entries at the list head
						}
						for (int i = 0; i < n; i++) {
							mRecentWakeups.remove();
						}

						recordWakeupAlarms(mAlarmBatches, nowELAPSED, nowRTC);
					}
				}
				//这个方法会把batch列表中的第一个batch取出来然后加到触发列表中
				//当然,前提是此batch的开始时间不大于当前时间
				//同时,如果是循环闹钟,则会对下次任务进行再次定时
				triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
				rescheduleKernelAlarmsLocked();

				// 遍历触发列表,发送PendingIntent
				for (int i=0; i<triggerList.size(); i++) {
					Alarm alarm = triggerList.get(i);
					try {
						if (localLOGV) Slog.v(TAG, "sending alarm " + alarm);
						//这里PendingIntent会被send,结果就是我们的定时任务被执行了
						alarm.operation.send(mContext, 0,
								mBackgroundIntent.putExtra(
										Intent.EXTRA_ALARM_COUNT, alarm.count),
								mResultReceiver, mHandler);
						
						// we have an active broadcast so stay awake.
						if (mBroadcastRefCount == 0) {
							setWakelockWorkSource(alarm.operation, alarm.workSource);
							mWakeLock.acquire();
						}
						final InFlight inflight = new InFlight(AlarmManagerService.this,
								alarm.operation, alarm.workSource);
						mInFlight.add(inflight);
						mBroadcastRefCount++;

						final BroadcastStats bs = inflight.mBroadcastStats;
						bs.count++;
						if (bs.nesting == 0) {
							bs.nesting = 1;
							bs.startTime = nowELAPSED;
						} else {
							bs.nesting++;
						}
						final FilterStats fs = inflight.mFilterStats;
						fs.count++;
						if (fs.nesting == 0) {
							fs.nesting = 1;
							fs.startTime = nowELAPSED;
						} else {
							fs.nesting++;
						}
						if (alarm.type == ELAPSED_REALTIME_WAKEUP
								|| alarm.type == RTC_WAKEUP) {
							bs.numWakeup++;
							fs.numWakeup++;
							//针对能唤醒设备的闹钟,这里会做一些唤醒设备的事情
							ActivityManagerNative.noteWakeupAlarm(
									alarm.operation);
						}
					} catch (PendingIntent.CanceledException e) {
						if (alarm.repeatInterval > 0) {
							// This IntentSender is no longer valid, but this
							// is a repeating alarm, so toss the hoser.
							remove(alarm.operation);
						}
					} catch (RuntimeException e) {
						Slog.w(TAG, "Failure sending alarm.", e);
					}
				}
			}
		}
	}
}
说明:上述代码中,AlarmThread会一直循环的跑着,一旦有新的alarm触发,它就会取出一个batch然后逐个发送PendingIntent,具体alarm的触发是由底层来完成的,我没法再继续分析下去。还有就是Alarm中有一些细节,我没有进行很具体的分析,实际上很简单,大家一看就懂。到此为止,Alarm机制的主要流程也分析完了。

总结

本文没有详细介绍如何使用Alarm,因为很简单,看一下官方文档或者网上搜一下,到处都是。关于Alarm,有一点需要强调一下:当手机重启或者应用被杀死的时候,Alarm会被删除,因此,如果想通过Alarm来完成长久定时任务是不可靠的,如果非要完成长久定时任务,可以这样:将应用的所有Alarm信息存到数据库中,每次应用启动的时候都重新注册Alarm并更新Alarm的触发时间,通过这种方式就不存在Alarm丢失的情况了。本文很长,耗时8个小时才完成的,感谢大家阅读本文,希望本文能给大家带来一点帮助。

分享到:
评论

相关推荐

    Android 底层驱动原理

    【Android系统架构及其驱动研究】 Android系统架构是一个分层的...以上是对Android底层驱动原理的详细解析,涵盖了系统架构、驱动类别、实例分析等多个方面,为Android开发者提供了深入理解硬件与软件交互的基础知识。

    Android底层开发技术实战详解--内核、移植和驱动.(电子工业.王振丽).part3

    在每一章中,重点介绍了与Android驱动开发相关的底层知识,并对Android源码进行了剖析。 本书适合Android研发人员及Android爱好者学习,也可以作为相关培训学校和大专院校相关专业的教学用书。 全书压缩打包成3...

    Android底层开发技术实战详解--内核、移植和驱动.(电子工业.王振丽).part1

    在每一章中,重点介绍了与Android驱动开发相关的底层知识,并对Android源码进行了剖析。 本书适合Android研发人员及Android爱好者学习,也可以作为相关培训学校和大专院校相关专业的教学用书。 全书压缩打包成3...

    Android底层开发技术实战详解--内核、移植和驱动.(电子工业.王振丽).part2

    在每一章中,重点介绍了与Android驱动开发相关的底层知识,并对Android源码进行了剖析。 本书适合Android研发人员及Android爱好者学习,也可以作为相关培训学校和大专院校相关专业的教学用书。 全书压缩打包成3...

    OFDM、OOK、PPM、QAM 的误码率模拟【绘制不同调制方案的误码率曲线】附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    8c71b76fb2ec10cf50fc6b0308d3dcfc_9545878e2b97a84b2e089ece58da9e82.png

    8c71b76fb2ec10cf50fc6b0308d3dcfc_9545878e2b97a84b2e089ece58da9e82

    Android SO逆向-对象的拷贝构造函数.pdf

    Android逆向过程学习

    基于S7-200 PLC的糖果包装控制系统设计与实现

    内容概要:本文详细介绍了基于西门子S7-200 PLC的糖果包装控制系统的设计与实现。首先阐述了PLC在工业自动化领域的优势及其在糖果包装生产线中的重要性。接着深入探讨了系统的硬件连接方式,包括传感器、执行机构与PLC的具体接口配置。随后展示了关键的编程实现部分,如糖果计数、包装执行、送膜控制、称重判断以及热封温度控制等具体梯形图代码片段。此外,还分享了一些实用的经验技巧,如防止信号抖动、PID参数优化、故障诊断方法等。最后总结了该系统的优势,强调其对提高生产效率和产品质量的重要作用。 适合人群:从事工业自动化控制、PLC编程的技术人员,尤其是对小型PLC系统感兴趣的工程师。 使用场景及目标:适用于糖果制造企业,旨在提升包装生产线的自动化程度,确保高效稳定的生产过程,同时降低维护成本并提高产品一致性。 其他说明:文中不仅提供了详细的理论讲解和技术指导,还结合实际案例进行了经验分享,有助于读者更好地理解和掌握相关知识。

    PLC与WinCC实现三部十层电梯协同控制及优化技巧

    内容概要:本文详细介绍了参与西门子杯比赛中关于三部十层电梯系统的博图V15.1程序设计及其WinCC画面展示的内容。文中不仅展示了电梯系统的基本架构,如抢单逻辑、方向决策、状态机管理等核心算法(采用SCL语言编写),还分享了许多实际调试过程中遇到的问题及解决方案,例如未初始化变量导致的异常行为、状态机遗漏空闲状态、WinCC画面动态显示的挑战以及通信配置中的ASCII码解析错误等问题。此外,作者还特别提到一些创意性的设计,如电梯同时到达同一层时楼层显示器变为闪烁爱心的效果,以及节能模式下电梯自动停靠中间楼层的功能。 适合人群:对PLC编程、工业自动化控制、电梯调度算法感兴趣的工程技术人员,尤其是准备参加类似竞赛的学生和技术爱好者。 使用场景及目标:适用于希望深入了解PLC编程实践、掌握电梯群控系统的设计思路和技术要点的人士。通过学习本文可以更好地理解如何利用PLC进行复杂的机电一体化项目的开发,提高解决实际问题的能力。 其他说明:文章风格幽默诙谐,将严肃的技术话题融入轻松的生活化比喻之中,使得原本枯燥的专业知识变得生动有趣。同时,文中提供的经验教训对于从事相关领域的工作者来说非常宝贵,能够帮助他们少走弯路并激发更多创新思维。

    慧荣量产工具合集.zip

    慧荣量产工具合集.zip

    永磁同步电机FOC控制与SVPWM算法仿真模型解析

    内容概要:本文详细介绍了永磁同步电机(PMSM)的FOC(磁场定向控制)和SVPWM(空间矢量脉宽调制)算法的仿真模型。首先解释了FOC的基本原理及其核心的坐标变换(Clark变换和Park变换),并给出了相应的Python代码实现。接下来探讨了SVPWM算法的工作机制,包括扇区判断和占空比计算的方法。此外,文章还讨论了电机的PI双闭环控制结构,即速度环和电流环的设计与实现。文中不仅提供了详细的理论背景,还分享了一些实用的编程技巧和注意事项,帮助读者更好地理解和应用这些算法。 适合人群:电气工程专业学生、从事电机控制系统开发的技术人员以及对永磁同步电机控制感兴趣的科研人员。 使用场景及目标:① 学习和掌握永磁同步电机的FOC控制和SVPWM算法的具体实现;② 提供丰富的代码示例和实践经验,便于快速搭建和调试仿真模型;③ 探讨不同参数设置对电机性能的影响,提高系统的稳定性和效率。 其他说明:文章强调了在实际应用中需要注意的一些细节问题,如坐标变换中的系数选择、SVPWM算法中的扇区判断优化以及PI控制器的参数调整等。同时,鼓励读者通过动手实验来加深对各个模块的理解。

    spring-ai-qianfan-1.0.0-M5.jar中文文档.zip

    # 压缩文件中包含: 中文文档 jar包下载地址 Maven依赖 Gradle依赖 源代码下载地址 # 本文件关键字: jar中文文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;

    Android安全之旅系列博客导读.pdf

    Android逆向过程学习

    【图像处理】基于双目视觉的物体体积测量算法研究附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    3dmax插件按面积分离.ms

    3dmax插件

    spring-ai-autoconfigure-vector-store-qdrant-1.0.0-M7.jar中文文档.zip

    # 【spring-ai-autoconfigure-vector-store-qdrant-1.0.0-M7.jar中文文档.zip】 中包含: 中文文档:【spring-ai-autoconfigure-vector-store-qdrant-1.0.0-M7-javadoc-API文档-中文(简体)版.zip】 jar包下载地址:【spring-ai-autoconfigure-vector-store-qdrant-1.0.0-M7.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【spring-ai-autoconfigure-vector-store-qdrant-1.0.0-M7.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【spring-ai-autoconfigure-vector-store-qdrant-1.0.0-M7.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【spring-ai-autoconfigure-vector-store-qdrant-1.0.0-M7-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: spring-ai-autoconfigure-vector-store-qdrant-1.0.0-M7.jar中文文档.zip,java,spring-ai-autoconfigure-vector-store-qdrant-1.0.0-M7.jar,org.springframework.ai,spring-ai-autoconfigure-vector-store-qdrant,1.0.0-M7,org.springframework.ai.vectorstore.qdr

    【ARIMA-WOA-LSTM】差分自回归移动平均方法-鲸鱼优化算法-LSTM预测研究附python代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    永磁同步电机控制系统中平方根容积卡尔曼滤波(SRCKF)的应用及优化

    内容概要:本文详细介绍了平方根容积卡尔曼滤波(SRCKF)在永磁同步电机(PMSM)控制系统中的应用及其相对于传统CKF的优势。文章首先指出传统CKF在处理协方差矩阵时存在的数值不稳定性和非正定问题,导致系统性能下降。接着,作者通过引入SRCKF,利用Cholesky分解和QR分解来确保协方差矩阵的正定性,从而提高状态估计的精度和稳定性。文中展示了具体的电机模型和状态方程,并提供了详细的代码实现,包括状态预测、容积点生成以及观测更新等关键步骤。此外,文章还分享了实际调试过程中遇到的问题及解决方案,如选择合适的矩阵分解库和处理电机参数敏感性。最终,通过实验数据对比,证明了SRCKF在突加负载情况下的优越表现。 适合人群:从事永磁同步电机控制研究的技术人员、研究生及以上学历的研究者。 使用场景及目标:适用于需要高精度状态估计的永磁同步电机控制系统的设计与优化,特别是在处理非线性问题和提高数值稳定性方面。 其他说明:文章引用了相关领域的权威文献,如Arasaratnam的TAC论文和Zhong的《PMSM无传感器控制综述》,并强调了实际工程实践中代码调试的重要性。

    tokenizers-0.31.1.jar中文文档.zip

    # 【tokenizers-***.jar***文档.zip】 中包含: ***文档:【tokenizers-***-javadoc-API文档-中文(简体)版.zip】 jar包下载地址:【tokenizers-***.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【tokenizers-***.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【tokenizers-***.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【tokenizers-***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: tokenizers-***.jar***文档.zip,java,tokenizers-***.jar,ai.djl.huggingface,tokenizers,***,ai.djl.engine.rust,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,djl,huggingface,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【tokenizers-***.jar***文档.zip】,再解压其中的 【tokenizers-***-javadoc-API文档-中文(简体)版.zip】,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件; # Maven依赖: ``` <dependency> <groupId>ai.djl.huggingface</groupId> <artifactId>tokenizers</artifactId> <version>***</version> </dependency> ``` # Gradle依赖: ``` Gradle: implementation group: 'ai.djl.huggingface', name: 'tokenizers', version: '***' Gradle (Short): implementation 'ai.djl.huggingface:tokenizers:***' Gradle (Kotlin): implementation("ai.djl.huggingface:tokenizers:***") ``` # 含有的 Java package(包): ``` ai.djl.engine.rust ai.djl.engine.rust.zoo ai.djl.huggingface.tokenizers ai.djl.huggingface.tokenizers.jni ai.djl.huggingface.translator ai.djl.huggingface.zoo ``` # 含有的 Java class(类): ``` ai.djl.engine.rust.RsEngine ai.djl.engine.rust.RsEngineProvider ai.djl.engine.rust.RsModel ai.djl.engine.rust.RsNDArray ai.djl.engine.rust.RsNDArrayEx ai.djl.engine.rust.RsNDArrayIndexer ai.djl.engine.rust.RsNDManager ai.djl.engine.rust.RsSymbolBlock ai.djl.engine.rust.RustLibrary ai.djl.engine.rust.zoo.RsModelZoo ai.djl.engine.rust.zoo.RsZooProvider ai.djl.huggingface.tokenizers.Encoding ai.djl.huggingface.tokenizers.HuggingFaceTokenizer ai.djl.huggingface.tokenizers.HuggingFaceTokenizer.Builder ai.djl.hu

    3.png

    3

Global site tag (gtag.js) - Google Analytics