综合案例:开发一个省电助手App

好了,到了这一章,咱们要把前面学的那些零散知识点串起来。说白了,就是动手做一个真正的省电助手App。我平时做项目最怕的就是纸上谈兵,今天咱们就来点实在的。

这个App会集成三块核心功能:Wake Lock检测Doze模式适配JobScheduler任务调度。你想想看,这三个东西正好覆盖了Android电源管理的三个关键环节——谁在耗电、系统怎么省电、我们怎么聪明地干活。

省电助手App核心架构 省电助手App Wake Lock 检测 • 检测持锁应用 • 统计持锁时长 • 提供释放建议 Doze 模式适配 • 白名单管理 • 省电策略配置 • 待机状态检测 JobScheduler 调度 • 延迟任务合并 • 网络条件约束 • 空闲时段执行 目标:让App更省电,让用户更安心

1. 项目结构概览

我个人习惯先把架子搭好再填肉。这个App我打算用三个核心模块来组织:

  • PowerMonitorService —— 负责监控Wake Lock和Doze状态
  • PowerOptimizerFragment —— 展示省电建议和优化选项
  • TaskSchedulerFragment —— 管理JobScheduler任务

嗯,这里要注意,咱们不搞复杂的MVVM,就用简单的Fragment+Service架构,够用就行。

2. Wake Lock 检测模块

先说说Wake Lock检测。我记得有一次线上反馈说某个App待机一晚掉电30%,查了半天发现是第三方SDK偷偷持了个Partial Wake Lock没释放。从那以后,我对Wake Lock检测就特别上心。

核心思路:通过PowerManager的API获取当前活跃的Wake Lock列表,然后分析哪些应用持有锁、持了多久。

// WakeLockMonitor.java
public class WakeLockMonitor {
    private static final String TAG = "WakeLockMonitor";
    
    public List<WakeLockInfo> getActiveWakeLocks(Context context) {
        List<WakeLockInfo> result = new ArrayList<>();
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        
        // 通过dump获取Wake Lock信息
        // 注意:这需要android.permission.DUMP权限
        try {
            String dumpOutput = getDumpOutput(context, "power");
            result = parseWakeLocksFromDump(dumpOutput);
        } catch (Exception e) {
            Log.e(TAG, "获取Wake Lock信息失败", e);
        }
        return result;
    }
    
    private List<WakeLockInfo> parseWakeLocksFromDump(String dump) {
        List<WakeLockInfo> locks = new ArrayList<>();
        // 解析dump输出的逻辑
        // 实际项目中这里需要正则匹配
        return locks;
    }
}

避坑指南:我曾经在Android 10以上的设备上踩过坑——dumpsys power的输出格式每个厂商都可能改。所以解析逻辑一定要加try-catch,解析失败时不要崩溃,直接返回空列表就好。

3. Doze 模式适配模块

Doze模式是Android 6.0引入的省电大招。说白了,就是手机静止不动时,系统帮你把后台活动都冻结了。但有些App需要例外处理,比如闹钟、即时通讯。

咱们的省电助手要做的,就是帮用户管理这些例外。

// DozeOptimizer.java
public class DozeOptimizer {
    
    /**
     * 检查App是否在白名单中
     */
    public boolean isInPowerWhitelist(Context context, String packageName) {
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        return pm.isIgnoringBatteryOptimizations(packageName);
    }
    
    /**
     * 请求加入白名单
     * 注意:系统会弹窗让用户确认
     */
    public void requestWhitelist(Context context, String packageName) {
        Intent intent = new Intent(
            Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
        intent.setData(Uri.parse("package:" + packageName));
        context.startActivity(intent);
    }
    
    /**
     * 获取当前Doze状态
     */
    public String getDozeStatus(Context context) {
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (pm.isDeviceIdleMode()) {
                return "设备处于Doze深度休眠";
            } else if (pm.isPowerSaveMode()) {
                return "省电模式已开启";
            } else {
                return "正常模式";
            }
        }
        return "不支持Doze检测";
    }
}

小技巧:我建议在UI上用一个状态指示灯来展示Doze状态——绿色表示正常,黄色表示省电模式,红色表示深度休眠。用户一看就明白。

4. JobScheduler 任务调度模块

最后说说JobScheduler。你想想看,如果每个App都按自己的性子来干活,系统得多乱?JobScheduler就是那个「交通警察」,帮我们把任务安排在合适的时间执行。

咱们的省电助手会提供一个任务调度器,让用户把那些不紧急的任务(比如日志上传、数据同步)交给系统统一调度。

// SmartJobScheduler.java
public class SmartJobScheduler {
    private static final int JOB_ID_SYNC = 1001;
    
    public void scheduleOptimizedTask(Context context) {
        ComponentName serviceComponent = 
            new ComponentName(context, DataSyncJobService.class);
        
        JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_SYNC, serviceComponent);
        
        // 设置约束条件
        builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); // 仅WiFi
        builder.setRequiresCharging(true);  // 仅充电时
        builder.setRequiresDeviceIdle(true); // 仅设备空闲时
        
        // 设置重试策略
        builder.setBackoffCriteria(
            JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS,
            JobInfo.BACKOFF_POLICY_LINEAR);
        
        JobScheduler scheduler = 
            (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
        scheduler.schedule(builder.build());
    }
}

// 任务执行服务
public class DataSyncJobService extends JobService {
    @Override
    public boolean onStartJob(JobParameters params) {
        // 在这里执行实际任务
        new Thread(() -> {
            // 执行数据同步
            syncData();
            // 任务完成,通知系统
            jobFinished(params, false);
        }).start();
        return true; // 表示任务正在执行
    }
    
    @Override
    public boolean onStopJob(JobParameters params) {
        // 系统要求停止任务时调用
        return false; // 不重试
    }
}

关键点:JobScheduler的约束条件越具体,系统调度就越智能。我一般会至少设置网络类型和充电状态两个条件,这样既能完成任务,又不会影响用户体验。

5. 整合到UI中

好了,三个模块都写好了,怎么把它们串起来呢?我习惯用一个主Fragment来做仪表盘,把三个功能模块的状态都展示出来。

// PowerDashboardFragment.java (关键部分)
public class PowerDashboardFragment extends Fragment {
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_power_dashboard, container, false);
        
        // 初始化三个模块
        wakeLockMonitor = new WakeLockMonitor();
        dozeOptimizer = new DozeOptimizer();
        jobScheduler = new SmartJobScheduler();
        
        // 更新UI
        updateWakeLockStatus();
        updateDozeStatus();
        updateJobStatus();
        
        return view;
    }
    
    private void updateWakeLockStatus() {
        List<WakeLockInfo> locks = wakeLockMonitor.getActiveWakeLocks(getContext());
        if (locks.isEmpty()) {
            textViewWakeLock.setText("✓ 无异常Wake Lock");
            textViewWakeLock.setTextColor(Color.GREEN);
        } else {
            textViewWakeLock.setText("⚠ 发现 " + locks.size() + " 个活跃Wake Lock");
            textViewWakeLock.setTextColor(Color.RED);
        }
    }
}

UI设计建议:我建议用卡片式布局来展示三个模块,每个卡片左上角放一个状态图标(绿色对勾/红色警告),右上角放一个「详情」按钮。这样用户扫一眼就知道整体情况。

6. 避坑总结

做这个项目时,有几个坑我替你们踩过了:

坑点 原因 解决方案
dumpsys输出格式变化 厂商定制ROM修改了输出 加try-catch,解析失败返回空
白名单请求被拒绝 用户可能点「拒绝」 做好引导,不要反复弹窗
JobScheduler任务不执行 约束条件太严格 设置合理的超时和重试策略
Android 12以上权限收紧 新增了后台权限限制 使用PendingIntent.getForegroundService()

嗯,差不多就这些了。这个省电助手App虽然简单,但把电源管理的三个核心知识点都串起来了。你做完之后,可以装到自己的手机上跑一跑,看看哪些App在偷偷耗电——我保证你会吓一跳。

公众号:蓝海资料掘金营,微信deep3321