編輯:關於Android編程
android官方文檔:AlarmManager
在Android平台,除了使用AlarmManger外,還可以使用Timer或者Handler來實現定時任務,但這兩種方式定時並不會太准確;因此如果我們需要實現精准定時任務,使用AlarmManger更為合理。
AlarmManager類提供對系統鬧鐘服務(或稱為定時器服務)的訪問接口,使用它既可以指定單次執行的定時任務,也可以指定重復運行的任務。
當鬧鐘指定觸發時間到達時,實際上是系統發出為這個鬧鐘注冊的廣播,因此我們需要實現一個針對特定鬧鐘事件的廣播接口器。
從API 19開始,alarm的機制都是非准確傳遞,操作系統將會轉換鬧鐘,來最小化喚醒和電池使用。
Note: Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.
本文中的代碼使用Kotlin來編寫,Kotlin是JVM上的一種編程語言,可以與Java無縫對接,由JetBrains公司研發,官方介紹:
Statically typed programming language for the JVM, Android and the browser
100% interoperable with Java?
剛實現定時任務我就寫了本文,因此對這方面的知識並沒有完全掌握,文中可能會有錯誤,有機會還會更新。
本文沒有將完整的示例代碼貼出來,只是貼了關鍵代碼,主要是鬧鐘的設置和取消,以及與Settings部分的交互。
首先定義幾個常量,包括用於Settings部分的key、用於PendingIntent的requestCode和用於Intent中的action,代碼如下:
const val ACTION_ALARM_REPLENISH_STOCK = "witmoon.auto.replenish.stock.action"
const val ACTION_ALARM_SYNCHRONIZE = "witmoon.auto.synchronize.action"
const val ALARM_REPLENISH_STOCK_CODE = 11
const val ALARM_SYNCHRONIZE_CODE = 12
const val KEY_SETTING_AUTO_SYNCHRONIZE = "auto_synchronize_status"
const val KEY_SETTING_SYNCHRONIZE_TIME = "auto_synchronize_time"
const val KEY_SETTING_AUTO_SUBMIT = "auto_submit_status"
const val KEY_SETTING_AUTO_SUBMIT_TIME = "auto_submit_time"
接下來是鬧鐘事件廣播接收器,作為示例代碼非常簡單,收到廣播事件後首先判斷action,根據不同的action使用Toast打印出不同的內容:
/**
* 鬧鐘廣播接收器
* Created by chuxin on 2016/5/4.
*/
class AlarmBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
var action = intent?.action
when {
ACTION_ALARM_SYNCHRONIZE.equals(action) -> doSynchronizeAction(context)
ACTION_ALARM_REPLENISH_STOCK.equals(action) -> doReplenishStockAction(context)
}
}
private fun doSynchronizeAction(context: Context?) {
Toast.makeText(context!!, "同步", Toast.LENGTH_SHORT).show()
}
/**
* 執行補充庫存動作, 即下單/定貨
*/
private fun doReplenishStockAction(context: Context?) {
Toast.makeText(context!!, "定貨", Toast.LENGTH_SHORT).show()
}
}
不要忘記在AndroidManifest.xml文件中注冊接收器:
由於我需要在App啟動時就設置定時任務,因此我將代碼放置到了Application中,並結合了Settings部分重新設置任務執行時間(每天一次),關於Settings部分後面說,此處只關心設置鬧鐘。
class HomeDoorApplication : Application() {
companion object {
// 自定義委托實現單例
var instance: HomeDoorApplication by DelegatesExt.notNullSingleValue()
}
override fun onCreate() {
super.onCreate()
instance = this
// 啟動定時服務
startAlarm()
}
/**
* 啟動定時器(使用系統鬧鈴服務)
*/
private fun startAlarm() {
var preferences = PreferenceManager.getDefaultSharedPreferences(this)
// 判斷是否需要啟動定時提交任務
var isAutoSubmitEnable = preferences.getBoolean(KEY_SETTING_AUTO_SUBMIT, true)
if (isAutoSubmitEnable) {
var hourMinuteArray = preferences.getString(KEY_SETTING_AUTO_SUBMIT_TIME, "00:00").split(":")
setAlarm(AlarmManager.RTC_WAKEUP, hourMinuteArray[0].toInt(), hourMinuteArray[1].toInt(),
AlarmManager.INTERVAL_DAY, ALARM_REPLENISH_STOCK_CODE, ACTION_ALARM_REPLENISH_STOCK)
}
// 判斷是否需要啟動定時同步任務
var isAutoSynchronizeEnable = preferences.getBoolean(KEY_SETTING_AUTO_SYNCHRONIZE, true)
if (isAutoSynchronizeEnable) {
var hourMinuteArray = preferences.getString(KEY_SETTING_SYNCHRONIZE_TIME, "00:00").split(":")
setAlarm(AlarmManager.RTC_WAKEUP, hourMinuteArray[0].toInt(), hourMinuteArray[1].toInt(),
AlarmManager.INTERVAL_DAY, ALARM_SYNCHRONIZE_CODE, ACTION_ALARM_SYNCHRONIZE)
}
}
/**
* 取消鬧鐘
*/
fun cancelAlarm(requestCode: Int, action: String) {
var alarmManager: AlarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(
PendingIntent.getBroadcast(this, requestCode, Intent(action), PendingIntent.FLAG_UPDATE_CURRENT))
}
fun setAlarm(type: Int, hour: Int, minute: Int, intervalMillis: Long, requestCode: Int, action: String) {
var now = Calendar.getInstance()
var targetTime = now.clone() as Calendar
targetTime.set(Calendar.HOUR_OF_DAY, hour)
targetTime.set(Calendar.MINUTE, minute)
targetTime.set(Calendar.SECOND, 0)
targetTime.set(Calendar.MILLISECOND, 0)
if (targetTime.before(now))
targetTime.add(Calendar.DATE, 1)
// 方便測試,這裡指定即時啟動,每20秒執行一次
setAlarm(type, 0, 20 * 1000, requestCode, action)
// setAlarm(type, targetTime.timeInMillis, intervalMillis, requestCode, action)
}
/**
* 設置鬧鐘
*/
fun setAlarm(type: Int, triggerAtMillis: Long, intervalMillis: Long, requestCode: Int, action: String) {
var alarmManager: AlarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setRepeating(type, triggerAtMillis, intervalMillis, PendingIntent.getBroadcast(this,
requestCode, Intent(action), PendingIntent.FLAG_UPDATE_CURRENT))
}
}
這段代碼實際上是同時設置了兩個定時任務,自動提交訂單和自動同步,為了更容易讀懂代碼,我們只看其中一個就可以。
在startAlarm方法中首先我讀取了Settings部分的設置,判斷是否需要啟動鬧鐘,如果設置值為true,則去讀取設置的任務執行時間;時間格式是每天的幾點幾分(如22:30表示每天晚上10點半執行)。
讀取時間後拆分為小時和分鐘,調用setAlarm方法,該方法參數包括鬧鐘類型、首次觸發時間、任務間隔、以及requestCode和Action;在該方法中我使用拆分出的小時和分鐘找到該時間點的毫秒數(如果時間在當前時間之前,即觸發時間已是過去,則到明天這個時間再觸發),調用setAlarm重載方法。
在setAlarm重載方法中,構建出PendingIntent對象並設置好鬧鐘。
首先看一下最終的設置界面
本文不介紹Settings部分內容,僅僅是記述重新設置任務執行時間的邏輯。
有關Android中Settings部分內容請參考官方文檔:Settings
設置界面XML文件,其中用到了一個自定義的Preference用於選擇時間,為了不顯得太亂這部分代碼不再貼出。
設置部分使用PreferenceFragment,在3.0以後官方建議使用PreferenceFragment來代替PreferenceActivity,定義好SettingFragment後將其放置到一個普通的Activity即可。
class SettingFragment : PreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.setting)
}
override fun onResume() {
super.onResume()
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
super.onPause()
preferenceScreen.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
}
// 設置改變響應
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String) {
when {
KEY_SETTING_AUTO_SYNCHRONIZE.equals(key) -> onAutoSynchronizeChanged(sharedPreferences, key)
KEY_SETTING_SYNCHRONIZE_TIME.equals(key) -> onSynchronizeTimeChanged(sharedPreferences, key)
KEY_SETTING_AUTO_SUBMIT.equals(key) -> onAutoSubmitChanged(sharedPreferences, key)
KEY_SETTING_AUTO_SUBMIT_TIME.equals(key) -> onSubmitTimeChanged(sharedPreferences, key)
}
}
private fun onSubmitTimeChanged(preferences: SharedPreferences?, key: String) {
// 取消定時任務
HomeDoorApplication.instance.cancelAlarm(ALARM_REPLENISH_STOCK_CODE, ACTION_ALARM_REPLENISH_STOCK)
var hourMinuteArray = preferences?.getString(KEY_SETTING_AUTO_SUBMIT_TIME, "00:00")!!.split(":")
HomeDoorApplication.instance.setAlarm(AlarmManager.RTC_WAKEUP, hourMinuteArray[0].toInt(),
hourMinuteArray[1].toInt(), AlarmManager.INTERVAL_DAY, ALARM_REPLENISH_STOCK_CODE,
ACTION_ALARM_REPLENISH_STOCK)
}
private fun onSynchronizeTimeChanged(preferences: SharedPreferences?, key: String) {
// 取消定時任務
HomeDoorApplication.instance.cancelAlarm(ALARM_SYNCHRONIZE_CODE, ACTION_ALARM_SYNCHRONIZE)
// 設置定時任務
var hourMinuteArray = preferences?.getString(KEY_SETTING_SYNCHRONIZE_TIME, "00:00")!!.split(":")
HomeDoorApplication.instance.setAlarm(AlarmManager.RTC_WAKEUP, hourMinuteArray[0].toInt(),
hourMinuteArray[1].toInt(), AlarmManager.INTERVAL_DAY, ALARM_SYNCHRONIZE_CODE,
ACTION_ALARM_SYNCHRONIZE)
}
// 自動提交訂單設置有變化,啟用則綁定鬧鐘,禁用則取消鬧鐘
private fun onAutoSubmitChanged(preferences: SharedPreferences?, key: String) {
var isAutoSubmit = preferences?.getBoolean(key, true)!!
if (isAutoSubmit) {
preferenceScreen.findPreference(KEY_SETTING_AUTO_SUBMIT_TIME).isEnabled = true
var hourMinuteArray = preferences?.getString(KEY_SETTING_AUTO_SUBMIT_TIME, "00:00")!!.split(":")
HomeDoorApplication.instance.setAlarm(AlarmManager.RTC_WAKEUP, hourMinuteArray[0].toInt(),
hourMinuteArray[1].toInt(), AlarmManager.INTERVAL_DAY, ALARM_REPLENISH_STOCK_CODE,
ACTION_ALARM_REPLENISH_STOCK)
} else {
preferenceScreen.findPreference(KEY_SETTING_AUTO_SUBMIT_TIME).isEnabled = false
// 取消定時任務
HomeDoorApplication.instance.cancelAlarm(ALARM_REPLENISH_STOCK_CODE, ACTION_ALARM_REPLENISH_STOCK)
}
}
private fun onAutoSynchronizeChanged(preferences: SharedPreferences?, key: String) {
var isAutoSynchronize = preferences?.getBoolean(key, true)!!
if (isAutoSynchronize) {
preferenceScreen.findPreference(KEY_SETTING_SYNCHRONIZE_TIME).isEnabled = true
// 設置定時任務
var hourMinuteArray = preferences?.getString(KEY_SETTING_SYNCHRONIZE_TIME, "00:00")!!.split(":")
HomeDoorApplication.instance.setAlarm(AlarmManager.RTC_WAKEUP, hourMinuteArray[0].toInt(),
hourMinuteArray[1].toInt(), AlarmManager.INTERVAL_DAY, ALARM_SYNCHRONIZE_CODE,
ACTION_ALARM_SYNCHRONIZE)
} else {
preferenceScreen.findPreference(KEY_SETTING_SYNCHRONIZE_TIME).isEnabled = false
// 取消定時任務
HomeDoorApplication.instance.cancelAlarm(ALARM_SYNCHRONIZE_CODE, ACTION_ALARM_SYNCHRONIZE)
}
}
}
SharedPreferences.OnSharedPreferenceChangeListener接口用於監聽設置項的改變,從代碼中可以看出,如果是關閉自動任務,則直接調用取消鬧鐘;如果是重新設置了任務執行時間,則先取消鬧鐘,再用新的時間重新設置鬧鐘。取消和設置都在前面的Application中定義了,這裡只是簡單地調用即可。
什麼是路由?說簡單點就是映射頁面跳轉關系的,當然它也包含跳轉相關的一切功能。路由框架的意義Android系統已經給我們提供了api來做頁面跳轉,比如startActivi
概述當以Bitmap作為畫布材料時,可以繪制出以下各種圖案:demo/** * 圖形圖像處理:在Bitmap上繪畫 */public class MyBitMapView
官網地址:https://developer.android.com/intl/zh-tw/training/material/theme.html 新的Material
Property Animation 該屬性動畫系統是一個強大的框架,可以讓您動畫幾乎任何事情。可以定義動畫隨時間而改變任何對象屬性,無論它繪制到屏幕或沒有。的屬性動畫改