一、项目介绍
在某些嵌入式 Android、物联网盒子或特殊工业场景中,需要在预设时间自动开机或关机,以便节能或定时执行任务。标准 Android 手机并不支持任意时刻自动开机,但部分设备厂商或定制固件提供了相关接口。通常思路包括:
-
开机时间写入底层 RTC:在关机前通过
AlarmManager
或直接写入/sys/class/rtc/rtc0/wakealarm
实现; -
关机定时:使用
PowerManager.reboot()
或发送关机广播; -
用户界面:提供设置界面,选择开、关机时刻,并在后台调度任务;
-
权限与系统签名:关机、重启等操作通常需要系统权限或签名,普通应用需借助 ROOT 或系统应用。
本教程假设设备已授予系统签名权限或 ROOT 环境,可执行开关机命令;并演示如何:
-
在应用内 设置开/关机时间
-
使用 AlarmManager 安排关机或写入 Wake Alarm
-
调用 PowerManager 或 Runtime.exec() 发送关机命令
-
处理重复定时和 取消
-
UI 采用 TimePicker 选择时间,并保存到 SharedPreferences
二、相关技术与知识
-
AlarmManager
-
用于安排在未来某个时间发送广播或启动
Service
。 -
setExactAndAllowWhileIdle()
保证在 Doze 模式下精准触发。
-
-
RTC Wake Alarm
-
通过向
/sys/class/rtc/rtc0/wakealarm
写入时间戳,设置下次开机唤醒。 -
Android 系统原生支持
AlarmManager.setRtcWakeup()
,但底层实现可兼容写文件。
-
-
关机/重启命令
-
普通应用:
Intent
调用ACTION_REQUEST_SHUTDOWN
(需权限),可唤起关机界面; -
系统应用/ROOT:通过
PowerManager.reboot(null)
或Runtime.getRuntime().exec("reboot -p")
强制关机;
-
-
TimePicker & SharedPreferences
-
TimePickerDialog
选时; -
将选择结果保存到
SharedPreferences
,以便重启后读取计划。
-
-
Foreground Service
-
如果需要在后台长期运行,可使用前台服务维持进程不被杀死;提供通知栏入口。
-
-
设备兼容性
-
不同 Android 版本对关机命令权限控制不同,需根据设备定制;
-
部分设备在未获得系统签名时,无法写
/sys
或调用PowerManager.reboot()
。
-
三、实现思路
-
UI 设计
-
两个按钮:设置“定时开机”和“定时关机”;
-
分别弹出
TimePickerDialog
供用户选择时分; -
列表展示当前已设置的任务,并可取消。
-
-
数据存储
-
使用
SharedPreferences
存储bootTime
和shutdownTime
(24h格式 “HH:mm”); -
可扩展为多条计划,使用
Room
存储。
-
-
定时调度
-
关机:在用户设定的
shutdownTime
到来时,AlarmManager
发送广播到ShutdownReceiver
,在onReceive
中调用关机逻辑; -
开机:在关机前写入 RTC Wake Alarm,或在系统启动后读取
bootTime
并再次设定下一次关机等。
-
-
执行开/关机
-
关机:如果是系统应用,可直接
PowerManager pm = getSystemService(PowerManager.class); pm.reboot(null);
或exec("reboot -p")
; -
开机:由底层通过 RTC 唤醒,不在应用层执行;应用可在
BOOT_COMPLETED
广播中监听,并在开机时执行初始化任务。
-
-
权限申请
-
运行时申请
RECEIVE_BOOT_COMPLETED
、REQUEST_SHUTDOWN
等; -
关机权限
android.permission.SHUTDOWN
通常为系统权限。
-
四、完整代码
// ==============================================
// 文件:MainActivity.java
// 功能:定时开关机设置、调度与执行示例
// 包含:布局 XML、Manifest、Receiver、一处整合
// ==============================================
package com.example.timerpower;
import android.app.*;
import android.content.*;
import android.os.*;
import android.provider.Settings;
import android.widget.*;
import androidx.annotation.*;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private Button btnSetBoot, btnSetShutdown;
private TextView tvInfo;
private SharedPreferences prefs;
@Override
protected void onCreate(Bundle s) {
super.onCreate(s);
setContentView(R.layout.activity_main);
btnSetBoot = findViewById(R.id.btnSetBoot);
btnSetShutdown = findViewById(R.id.btnSetShutdown);
tvInfo = findViewById(R.id.tvInfo);
prefs = getSharedPreferences("timer_power", MODE_PRIVATE);
updateInfo();
btnSetBoot.setOnClickListener(v -> showTimePicker(true));
btnSetShutdown.setOnClickListener(v -> showTimePicker(false));
}
private void showTimePicker(boolean isBoot) {
Calendar c = Calendar.getInstance();
new TimePickerDialog(this,
(view, h, m) -> {
String key = isBoot ? "bootTime" : "shutdownTime";
prefs.edit().putString(key,
String.format("%02d:%02d", h, m)).apply();
schedule(isBoot, h, m);
updateInfo();
},
c.get(Calendar.HOUR_OF_DAY),
c.get(Calendar.MINUTE), true).show();
}
private void updateInfo() {
String boot = prefs.getString("bootTime", "--:--");
String off = prefs.getString("shutdownTime", "--:--");
tvInfo.setText("开机: " + boot + "\n关机: " + off);
}
private void schedule(boolean isBoot, int h, int m) {
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent intent = new Intent(this,
isBoot ? BootReceiver.class : ShutdownReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this,
isBoot? 0:1, intent, 0);
Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, h);
c.set(Calendar.MINUTE, m);
c.set(Calendar.SECOND, 0);
long trigger = c.getTimeInMillis();
if (trigger < System.currentTimeMillis())
trigger += 24*3600*1000; // 次日
am.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP, trigger, pi);
}
}
// ------- 关机 Receiver -------
public class ShutdownReceiver extends BroadcastReceiver {
@Override public void onReceive(Context ctx, Intent i) {
try {
// 系统关机
Process p = Runtime.getRuntime()
.exec(new String[]{"su","-c","reboot -p"});
p.waitFor();
} catch (Exception e) { e.printStackTrace(); }
}
}
// ------- 开机 Receiver -------
public class BootReceiver extends BroadcastReceiver {
@Override public void onReceive(Context ctx, Intent i) {
// 在系统启动后可执行初始化逻辑
Toast.makeText(ctx,
"定时开机已到", Toast.LENGTH_LONG).show();
}
}
/*
=========================== res/layout/activity_main.xml ===========================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:padding="16dp"
android:layout_width="match_parent" android:layout_height="match_parent">
<TextView android:id="@+id/tvInfo"
android:text="--" android:textSize="18sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button android:id="@+id/btnSetBoot"
android:layout_marginTop="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="设置定时开机"/>
<Button android:id="@+id/btnSetShutdown"
android:layout_marginTop="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="设置定时关机"/>
</LinearLayout>
*/
/*
=========================== AndroidManifest.xml ===========================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.timerpower">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- 如果需要 su 权限 -->
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<application ...>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver android:name=".ShutdownReceiver"/>
<receiver android:name=".BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
*/
五、方法解读
-
AlarmManager & RTC_WAKEUP:利用系统闹钟在指定时间触发广播,结合
su reboot -p
强制关机。 -
BootCompleted:设备每次开机后,应用无需启动即可接收广播,执行后续逻辑。
-
TimePickerDialog:在 UI 层获取用户设定的时分,并保存到
SharedPreferences
。 -
权限与兼容:执行关机命令需 ROOT 或系统签名权限;普通应用仅可唤起关机界面。
六、项目总结
-
适用场景:定制化设备、物联网、智能盒子等需定时开关机的环境;
-
限制:绝大多数普通 Android 手机不支持应用层定时开机,需底层厂商支持;
-
扩展:可增加“重复”属性(每天/每周)、多条计划列表、取消计划功能;
-
安全:Root/系统权限操作需谨慎,避免恶意使用;
提示:请根据目标设备特性(是否支持 su、定制固件)调整方案。