前言
本文章旨在提供一種新的思路,在無需 Root 的情況下,實(shí)現(xiàn)自動化釘釘定時(shí)打卡,更多是為了自己方便而定制開發(fā),所以很多功能的實(shí)現(xiàn)局限性較大
MIUI 用戶可以直接使用該 APP 實(shí)現(xiàn)自動考勤
1.需求分析
公司考勤調(diào)整過后,需要使用釘釘一天打四次卡:08:30 前上班卡;12:00 后下班卡;13:30 前上班卡;18:00 后下班卡。防止中午午休時(shí)缺卡,現(xiàn)有如下需求:
到點(diǎn)自動啟動釘釘進(jìn)行打卡并息屏
需求具體化,梳理上班、下班考勤流程( 釘釘默認(rèn) 上班打卡 啟用 極速打卡,而 下班打卡 則是 手動打卡 )
涉及功能點(diǎn)
- 亮屏
- 息屏
- 屏幕解鎖
- 啟動釘釘
- 模擬用戶操作
- 保活
2.效果展示

圖中展示的下班打卡效果,上班打卡效果則更為簡單:喚醒屏幕并解鎖,啟動釘釘(觸發(fā)極速打卡)后息屏。
點(diǎn)擊跳轉(zhuǎn)下載安裝包

3.功能實(shí)現(xiàn)
3.1 亮屏
當(dāng)觸發(fā)打卡功能時(shí),首先需要判斷屏幕是否處于亮屏狀態(tài),非亮屏狀態(tài)則喚醒屏幕
通過 PowerManager 實(shí)現(xiàn),需要聲明權(quán)限:
<!-- 允許應(yīng)用程序使用電源管理器的屏幕鎖功能 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
示例代碼:
private fun wakeUpScreen(context: Context, tag: String) {
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
//是否需要亮屏喚醒屏幕
if (!powerManager.isInteractive) {
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
val wl = pm.newWakeLock(
PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_DIM_WAKE_LOCK,
tag
)
wl.acquire(60 * 1000L /*1 minutes*/)
wl.release()
}
}
3.2 息屏
通過 DevicePolicyManager 實(shí)現(xiàn)
1.自定義設(shè)備管理器啟用狀態(tài)監(jiān)聽器(點(diǎn)擊跳轉(zhuǎn)查看源碼)
class LockScreenDeviceAdminReceiver : DeviceAdminReceiver() {
override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent)
//TODO 設(shè)備管理器已啟用
}
override fun onDisabled(context: Context, intent: Intent) {
super.onDisabled(context, intent)
//TODO 設(shè)備管理器已禁用
}
}
2.配置清單注冊監(jiān)聽器(點(diǎn)擊跳轉(zhuǎn)查看源碼)
<!--設(shè)備管理器-->
<receiver
android:name=".service.LockScreenDeviceAdminReceiver"
android:description="@string/app_name"
android:label="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<meta-data
android:name="android.app.device_admin"
android:resource="@xml/lock_screen" />
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
其中 lock_screen 資源文件為 申請?jiān)O(shè)備管理器的具體權(quán)限
<?xml version="1.0" encoding="UTF-8"?>
<!--該文件路徑 res/xml/lock_screen.xml -->
<device-admin xmlns:android="http://schemas.android.com/apk/res/android" >
<uses-policies>
<!-- 鎖定屏幕 -->
<force-lock />
</uses-policies>
</device-admin>
3.判斷是否啟用設(shè)備管理器 / 啟用設(shè)備管理器
/**
* 判斷是否啟用設(shè)備管理器
*/
fun isAdminActive(context: Context): Boolean {
val devicePolicyManager =
context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
return devicePolicyManager.isAdminActive(ComponentName(context, LockScreenDeviceAdminReceiver::class.java))
}
/**
* 申請啟用設(shè)備管理器
*/
fun enableAdmin(context: Context){
val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
intent.putExtra(
DevicePolicyManager.EXTRA_DEVICE_ADMIN,
ComponentName(context, LockScreenDeviceAdminReceiver::class.java)
)
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "鎖屏")
context.startActivity(intent)
}
4.使用設(shè)備管理器進(jìn)行鎖屏
/**
* 鎖屏
*/
fun lockScreen(context: Context) {
val devicePolicyManager =
context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
if (devicePolicyManager.isAdminActive(
ComponentName(
context,
LockScreenDeviceAdminReceiver::class.java
)
)
) {
devicePolicyManager.lockNow()
}
}
3.3 屏幕解鎖(通過模擬用戶操作實(shí)現(xiàn))
使用 AccessibilityService 模擬用戶操作,實(shí)現(xiàn)解鎖功能
基于筆者的手機(jī)系統(tǒng) MIUI 12 20.7.9 示例,解鎖流程為:喚醒屏幕 → 進(jìn)入負(fù)一屏 → 點(diǎn)擊“萬能遙控”按鈕 → 喚起解鎖密碼輸入頁面 → 輸入解鎖密碼 → 完成解鎖
1.自定義 AccessibilityService (點(diǎn)擊跳轉(zhuǎn)查看源碼)
/**
* 自定義 AccessibilityService 重寫 onAccessibilityEvent 方法
* 根據(jù) AccessibilityEvent 的 eventType 監(jiān)聽系統(tǒng)變化動作:如通知變動、頁面變動、點(diǎn)擊、長按等
* 再通過 AccessibilityService 來分析當(dāng)前頁面節(jié)點(diǎn),模擬用戶操作:滑動、點(diǎn)擊/長按指定按鈕
*/
class SelfAccessibilityService : AccessibilityService() {
override fun onInterrupt() {
TODO("Not yet implemented")
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
//只會監(jiān)聽在 accessibility-service 的 android:packageNames 中聲明的 packageName
val packageName = event!!.packageName.toString()
val eventType = event.eventType
if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
//前臺頁面出現(xiàn)變動:回到桌面、啟動新的頁面等
if ("com.android.systemui" == packageName) {
//該頁面屬于系統(tǒng)頁面
} else if ("com.alibaba.android.rimet" == packageName) {
//該頁面屬于釘釘
//當(dāng)前頁面節(jié)點(diǎn)信息
val nodeInfo = this.rootInActiveWindow
//目標(biāo) view id ,通過 uiautomatorviewer 工具分析布局可以知道第三方 APP 指定頁面的 view 的 id、text 及 description
val aimsId = "com.alibaba.android.rimet:id/session_title"
//查詢到結(jié)果節(jié)點(diǎn)集合
val list = nodeInfo
.findAccessibilityNodeInfosByViewId(aimsId)
for (n in list) {
val text = n.text
val description = n.contentDescription
//TODO 與目標(biāo) view 的 text、description 進(jìn)行匹配,匹配成功得到指定 view 節(jié)點(diǎn)
//TODO 匹配成功后,調(diào)用 AccessibilityNodeInfo 的 performAction(int action) 方法執(zhí)行相關(guān)操作:如點(diǎn)擊、長按
}
}
}
}
}
2.配置清單注冊無障礙服務(wù)(點(diǎn)擊跳轉(zhuǎn)查看源碼)???????
<!--無障礙服務(wù)-->
<service
android:name=".service.SelfAccessibilityService"
android:label="@string/accessibility_service"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessible_service_config" />
</service>
其中 accessible_service_config 資源文件為該 無障礙服務(wù)的具體配置
<?xml version="1.0" encoding="utf-8"?>
<!--該文件路徑 res/xml/accessible_service_config.xml -->
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_service_description"
android:notificationTimeout="100"
android:packageNames="com.android.systemui,com.alibaba.android.rimet" />
3.跳轉(zhuǎn)無障礙服務(wù)頁面,讓用戶手動啟用無障礙服務(wù)
/**
* 跳轉(zhuǎn)系統(tǒng)無障礙服務(wù)設(shè)置頁面
*/
fun openAccessibilityService(context: Context) {
context.startActivity(Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS))
}
在 更多已下載的服務(wù) 中找到當(dāng)前 APP 提供的無障礙服務(wù)并啟用

息屏、亮屏及解鎖功能測試與效果展示(解鎖流程的滑動點(diǎn)擊及密碼輸入動作皆借助 AccessibilityService 實(shí)現(xiàn))

3.4 啟動釘釘
/**
* 通過指定包名打開APP
* 釘釘包名 com.alibaba.android.rimet
* LaunchHomeActivity
*/
fun openRimet(context: Context): Boolean {
val packageManager = context.packageManager
val pi: PackageInfo?
val packageName = "com.alibaba.android.rimet"
pi = try {
packageManager.getPackageInfo(packageName, 0)
} catch (e: PackageManager.NameNotFoundException) {
//TODO 未安裝釘釘
return false
}
val resolveIntent = Intent(Intent.ACTION_MAIN, null)
resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER)
resolveIntent.setPackage(pi.packageName)
val apps =
packageManager.queryIntentActivities(resolveIntent, 0)
val resolveInfo = apps.iterator().next()
if (resolveInfo != null) {
val className = resolveInfo.activityInfo.name
val intent = Intent(Intent.ACTION_VIEW)
intent.addCategory(Intent.CATEGORY_LAUNCHER)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val cn = ComponentName(packageName, className)
intent.component = cn
context.startActivity(intent)
return true
} else {
//TODO resolveInfo = null
return false
}
}
3.5 保活
在系統(tǒng)資源管理監(jiān)控越來越嚴(yán)格的現(xiàn)狀下,同時(shí)為了避免雜草叢生的無良 APP 影響用戶使用體驗(yàn),提出?;钸@個(gè)需求,其實(shí)是 不合理 、 不推薦 和 不倡導(dǎo) 的。但確實(shí)有可以?;畹姆椒?,只是需要用戶手動授予更高的權(quán)限。
此處基于筆者的手機(jī)系統(tǒng) Android 10 MIUI 12 20.7.9 示例實(shí)現(xiàn)?;睿?br> 1.授予 APP 自啟動權(quán)限
2.啟用無障礙服務(wù)
當(dāng) APP 具有自啟動權(quán)限后,其已啟用的無障礙服務(wù)就不會隨著 APP 被殺死而關(guān)閉(退出APP 或 最近任務(wù)頁面滑除APP),從而實(shí)現(xiàn)?;?。
殺死APP后,前臺服務(wù)依然被無障礙服務(wù)喚醒
4.注意事項(xiàng)
- 目前該 APP 僅適配了 MIUI 系統(tǒng),僅支持 Android 7.0 或以上的 MIUI 系統(tǒng)
- 目前支持的釘釘版本為 5.1.16
- MIUI 系統(tǒng)下需要授予 APP 后臺彈出界面 權(quán)限(應(yīng)用詳情頁面手動授予)
- 實(shí)現(xiàn)保活效果,需要授予 APP 自啟動 權(quán)限(應(yīng)用詳情頁面手動授予)
- 默認(rèn)手機(jī)有設(shè)置 鎖屏密碼,且是純數(shù)字密碼
- 手機(jī) 負(fù)一屏 模式需設(shè)置為 標(biāo)準(zhǔn)模式
- 可能會因?yàn)槭謾C(jī)設(shè)置了 防誤觸,導(dǎo)致亮屏后無法解鎖(當(dāng)手機(jī)在口袋里的時(shí)候)
- 釘釘內(nèi)需將 消息 頁面的 智能工作助理 置頂
5.開始使用
- 啟動 APP,進(jìn)入 設(shè)置 頁面
- 點(diǎn)擊第一項(xiàng)(設(shè)置“ 后臺彈出界面 ”權(quán)限)下的 跳轉(zhuǎn)設(shè)置 進(jìn)入應(yīng)用詳情頁面,分別授予 自啟動 和 后臺彈出界面 權(quán)限后回到 APP
- 點(diǎn)擊第二項(xiàng)(獲得釘釘啟動權(quán)限)下的 啟動釘釘以獲取權(quán)限 ,允許啟動釘釘后回到 APP
- 啟用第三項(xiàng)(相關(guān)服務(wù)設(shè)置)下的 設(shè)備管理應(yīng)用 和 無障礙服務(wù) 后回到 APP
- 點(diǎn)擊 設(shè)置/更新鎖屏密碼 按鈕,輸入當(dāng)前手機(jī)的 鎖屏密碼 并確定(僅保存本地)
- 添加考勤時(shí)間
- 回到 APP 首頁,點(diǎn)擊 測試 按鈕,來到功能測試頁面,逐一測試功能是否實(shí)現(xiàn)
- 測試無誤后,回到 APP 首頁,點(diǎn)擊 啟用 按鈕,啟用自動考勤服務(wù)
6.最后
該 APP 為閑余時(shí)間做出來利于自己考勤用的,并未做過多的適配,目前僅適用于部分 MIUI 系統(tǒng)機(jī)型。如需適配別的手機(jī)系統(tǒng),可自行下載源碼調(diào)整。
- 項(xiàng)目使用 MVVM + Jetpack 架構(gòu) 開發(fā),參考博主 卻把清梅嗅 的開源項(xiàng)目 MVVM-Architecture 部分代碼搭建
- 源碼及 Demo 地址:https://github.com/ziwenL/self-service
- 如有更好的見解或建議,歡迎留言
