Android系统原生应用解析之桌面闹钟及相关原理应用之时钟任务的应用(一)

简介: 前段时间我一个朋友在面试回来问我:那个公司要5天之内完成一个项目,功能包括每天早上6点开始执行定时任务,大批量图片上传,大批量数据库同步。我心想,后两个功能还好说,可就是每天早上6点开始执行的这种定时任务如何搞定? 有了问题,自然要琢磨怎么解决,如果接触的知识面不够,或者没有系统的学习...

前段时间我一个朋友在面试回来问我:那个公司要5天之内完成一个项目,功能包括每天早上6点开始执行定时任务,大批量图片上传,大批量数据库同步。我心想,后两个功能还好说,可就是每天早上6点开始执行的这种定时任务如何搞定?


有了问题,自然要琢磨怎么解决,如果接触的知识面不够,或者没有系统的学习Android API,例如不知道AlarmManager,自然是不知道如何启动定时任务的,当时我也不知道这个的存在,突然心头一闪,那手机上的闹钟可不就是定时任务吗?


多亏了这心头一闪,知道从系统闹钟看看一个闹钟这种标准的定时任务是如何完成的,正好手中刚刚下载完完整的安卓源码,也编译通过了,在源码的目录/packages/中找到了DeskClock文件夹,一看便知是闹钟了。


为了不破坏原生系统的完整性,我将这个工程拷了出来,导入了Studio进行分析,看看如何启动一个定时任务(我自己心里是觉得应该不会有一个服务在后台一直跑着用来监控时间),导入Studio之后进行简单的环境配置编译,跑了起来:

不得不说原生应用还是很漂亮的,为了达到我们的研究目的,我们只选择一个闹钟是如何被创建以及是如何被响应的。


首先我们需要找到一个闹钟任务是如何被创建及打开的,我没有直接去找闹钟是如何创建的,我去找了闹钟是如何被打开的,因为在item上有个开关,我找到了那个开关:

这个开关位于com.android.deskclock.AlarmClockFragment内,AlarmClockFragment内含有一个Adapter内部类,在Adapter的getView方法中找到了这个小开关的触发事件:

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {

			...

			View v;
			if (convertView == null) {
				v = newView(mContext, getCursor(), parent);
			} else {
				v = convertView;
			}
			bindView(v, mContext, getCursor());
			return v;
		}

		...

		@Override
		public void bindView(final View view, Context context, final Cursor cursor) {
			final Alarm alarm = new Alarm(cursor);
			Object tag = view.getTag();
			
			...

			final CompoundButton.OnCheckedChangeListener onOffListener = new CompoundButton.OnCheckedChangeListener() {
				@Override
				public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
					if (checked != alarm.enabled) {
						setDigitalTimeAlpha(itemHolder, checked);
						alarm.enabled = checked;
						asyncUpdateAlarm(alarm, alarm.enabled);
					}
				}
			};

			...

			itemHolder.onoff.setOnCheckedChangeListener(onOffListener);

			...

		}
onOffListener引用的对象便是闹钟开关的实现逻辑了,它调用了asyncUpdateAlarm方法:

		private void asyncUpdateAlarm(final Alarm alarm, final boolean popToast) {
			final Context context = AlarmClockFragment.this.getActivity().getApplicationContext();
			final AsyncTask<Void, Void, AlarmInstance> updateTask = new AsyncTask<Void, Void, AlarmInstance>() {
				@Override
				protected AlarmInstance doInBackground(Void... parameters) {
					ContentResolver cr = context.getContentResolver();

					// Dismiss all old instances
					AlarmStateManager.deleteAllInstances(context, alarm.id);

					// Update alarm
					Alarm.updateAlarm(cr, alarm);
					if (alarm.enabled) {
						return setupAlarmInstance(context, alarm);
					}

					return null;
				}

				@Override
				protected void onPostExecute(AlarmInstance instance) {
					if (popToast && instance != null) {
						AlarmUtils.popAlarmSetToast(context, instance.getAlarmTime().getTimeInMillis());
					}
				}
			};
			updateTask.execute();
		}
它内部执行了一个异步任务,任务的核心调用的是setupAlarmInstance:

		private static AlarmInstance setupAlarmInstance(Context context, Alarm alarm) {
			ContentResolver cr = context.getContentResolver();
			AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance());
			newInstance = AlarmInstance.addInstance(cr, newInstance);
			// Register instance to state manager
			AlarmStateManager.registerInstance(context, newInstance, true);
			return newInstance;
		}
这里的意思是,将闹钟数据添加到ContentProvider中,以便将数据共享给其它应用。接下来调用了AlarmStateManager.registerInstance:

		public static void registerInstance(Context context, AlarmInstance instance,
			boolean updateNextAlarm) {

			...

			// The caller prefers to handle updateNextAlarm for optimization
			if (updateNextAlarm) {
			    updateNextAlarm(context);
			}
		}
这段代码中本来有很长的一段代码,用来判断闹钟的各个时间段的执行情况,为了避免干扰我们的主流程,对代码进行了删减处理,我们从上一段代码可知,这里的updateNextAlarm值为true,进入到updateNextAlarm:

    public static void updateNextAlarm(Context context) {
        
	...

        AlarmNotifications.registerNextAlarmWithAlarmManager(context, nextAlarm);
    }

    ...

    public static void registerNextAlarmWithAlarmManager(Context context, AlarmInstance instance)  {
        // Sets a surrogate alarm with alarm manager that provides the AlarmClockInfo for the
        // alarm that is going to fire next. The operation is constructed such that it is ignored
        // by AlarmStateManager.

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

        int flags = instance == null ? PendingIntent.FLAG_NO_CREATE : 0;
        PendingIntent operation = PendingIntent.getBroadcast(context, 0 /* requestCode */,
                AlarmStateManager.createIndicatorIntent(context), flags);

        if (instance != null) {
            long alarmTime = instance.getAlarmTime().getTimeInMillis();

            // Create an intent that can be used to show or edit details of the next alarm.
            PendingIntent viewIntent = PendingIntent.getActivity(context, instance.hashCode(),
                    createViewAlarmIntent(context, instance), PendingIntent.FLAG_UPDATE_CURRENT);

            AlarmManager.AlarmClockInfo info =
                    new AlarmManager.AlarmClockInfo(alarmTime, viewIntent);
            alarmManager.setAlarmClock(info, operation);
        } else if (operation != null) {
            alarmManager.cancel(operation);
        }
    }
updateNextAlarm方法中通过调用AlarmNotifications类中的registerNextAlarmWithAlarmManager方法将下一次的闹铃注册到AlarmManager,歪果仁的命名清晰易懂啊!registerNextAlarmWithAlarmManager的方法内部则是我们真正需要看到的,首先是获取到了系统中的AlarmManager对象,接着创建了一个PendingIntent对象operation,这个对象用来执行当闹钟时间到的时候,需要调用的广播类,我们看看AlarmStateManager.createIndicatorIntent(context)方法内部是如何实现的:

    /**
     * Creates an intent that can be used to set an AlarmManager alarm to set the next alarm
     * indicators.
     */
    public static Intent createIndicatorIntent(Context context) {
        return new Intent(context, AlarmStateManager.class).setAction(INDICATOR_ACTION);
    }

    public final class AlarmStateManager extends BroadcastReceiver {
	
	...

    }
内部则是简单的new了一个Intent,这个显式的意图指定的是AlarmStateManager,而AlarmStateManager则继承的是BroadcastReceiver,这时,我们很明白,当时钟任务触发的时候会调用我们这个AlarmStateManager的广播,其实AlarmStateManager这个类的内部是有很多代码了,这里被我删减了,以便看代码清晰。


回到上一段方法中继续往下走,又看到在创建PendingIntent的对象viewIntent,这个对象则是用来当时钟任务启动时显示的界面,我们在使用闹钟的时候会弹出一个界面让我们关掉,那与我们交互的Activity就是这里被设定的,createViewAlarmIntent方法内部创建的是一个显式的Activity,有兴趣的可以进去看看。


一切设定好之后,再通过AlarmManager的方法setAlarmClock将我们的时钟任务注册到系统,系统会在我们设定的时间到达之后调用相关的Intent对象。

除了可以使用setAlarmClock方法注册一个时钟任务之外,我们还可以通过cancel方法将这个任务取消。

好,这就是闹钟的基本实现原理。接下里详细描述一下AlarmManager的各种时钟任务应用。








目录
相关文章
|
22天前
|
数据采集 监控 API
告别手动埋点!Android 无侵入式数据采集方案深度解析
传统的Android应用监控方案需要开发者在代码中手动添加埋点,不仅侵入性强、工作量大,还难以维护。本文深入探讨了基于字节码插桩技术的无侵入式数据采集方案,通过Gradle插件 + AGP API + ASM的技术组合,实现对应用性能、用户行为、网络请求等全方位监控,真正做到零侵入、易集成、高稳定。
381 32
|
7月前
|
存储 Android开发
如何查看Flutter应用在Android设备上已被撤销的权限?
如何查看Flutter应用在Android设备上已被撤销的权限?
319 64
|
7月前
|
机器学习/深度学习 文字识别 监控
安全监控系统:技术架构与应用解析
该系统采用模块化设计,集成了行为识别、视频监控、人脸识别、危险区域检测、异常事件检测、日志追溯及消息推送等功能,并可选配OCR识别模块。基于深度学习与开源技术栈(如TensorFlow、OpenCV),系统具备高精度、低延迟特点,支持实时分析儿童行为、监测危险区域、识别异常事件,并将结果推送给教师或家长。同时兼容主流硬件,支持本地化推理与分布式处理,确保可靠性与扩展性,为幼儿园安全管理提供全面解决方案。
337 3
|
5月前
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
250 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
|
8月前
|
人工智能 API 开发者
HarmonyOS Next~鸿蒙应用框架开发实战:Ability Kit与Accessibility Kit深度解析
本书深入解析HarmonyOS应用框架开发,聚焦Ability Kit与Accessibility Kit两大核心组件。Ability Kit通过FA/PA双引擎架构实现跨设备协同,支持分布式能力开发;Accessibility Kit提供无障碍服务构建方案,优化用户体验。内容涵盖设计理念、实践案例、调试优化及未来演进方向,助力开发者打造高效、包容的分布式应用,体现HarmonyOS生态价值。
469 27
|
8月前
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
247 15
|
8月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
8月前
|
存储 弹性计算 安全
阿里云服务器ECS通用型规格族解析:实例规格、性能基准与场景化应用指南
作为ECS产品矩阵中的核心序列,通用型规格族以均衡的计算、内存、网络和存储性能著称,覆盖从基础应用到高性能计算的广泛场景。通用型规格族属于独享型云服务器,实例采用固定CPU调度模式,实例的每个CPU绑定到一个物理CPU超线程,实例间无CPU资源争抢,实例计算性能稳定且有严格的SLA保证,在性能上会更加稳定,高负载情况下也不会出现资源争夺现象。本文将深度解析阿里云ECS通用型规格族的技术架构、实例规格特性、最新价格政策及典型应用场景,为云计算选型提供参考。
|
8月前
|
数据采集 机器学习/深度学习 存储
可穿戴设备如何重塑医疗健康:技术解析与应用实战
可穿戴设备如何重塑医疗健康:技术解析与应用实战
309 4
|
8月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

热门文章

最新文章

推荐镜像

更多