Android设备CPU休眠问题分析

1.减少CPU消耗即省电

CPU进入休眠状态,耗电就会减少

软件层面阻止CPU休眠的几个因素:

1.wake_lock
2.AlarmManager

可以使用电池分析工具Battery Historian分析软件耗电情况:

测试前准备工作:
dumpsys batterystats --reset
dumpsys batterystats --enable full-wake-history

然后拔掉USB开始休眠测试,进入休眠后停止测试,然后通过以下指令获取日志:
adb bugreport bugreport.zip
注意:使用该分析工具时,被分析设备不能连接USB,也不要连接adb
1.wake_lock对CPU休眠的影响分析:

在后台有某些任务必须要做,不希望进入休眠时,可以申请wake_lock锁,阻止CPU进入休眠:

    public static void requestWakeLock() {
        if (wakeLock == null) {
            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
            wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TESTAPP");
            wakeLock.setReferenceCounted(false);
        }
        wakeLock.acquire();
    }

看一下wake_lock未释放状态下对CPU休眠的影响:

Android设备CPU休眠问题分析_第1张图片

可以看到测试时没有释放wake_lock的状态下,灭屏后Long Wakelocks中统计到wake_lock锁一直被持有未释放,CPU一直在running状态,不能进入休眠。

看看释放wake_lock的状态下的统计情况:

    public static void releaseWakeLock() {
        if (wakeLock != null) {
            wakeLock.release();
            wakeLock = null;
        }
    }
Android设备CPU休眠问题分析_第2张图片

上图的Long Wakelocks可以看到时间正好是设置任务处理的时间,任务处理完后释放了wake_lock锁,所以CPU才能进入休眠状态。因此申请wake_lock锁时,在任务执行完毕后必须释放wake_lock锁,避免CPU耗电。

2.Alarm任务对CPU休眠的影响分析:

为了分析AlarmManager对CPU休眠的影响,通过如下测试代码测试:

package com.example.allen.testapp;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.SystemClock;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

public class MainActivity extends AppCompatActivity {
    private final static String TAG = "TESTAPP";
    private static Context mContext = null;

    private final static long TASK_TIME = 3 * 60 * 1000;    //假设任务需要3分钟时间处理
    private final static long ALARM_TIME = 5 * 60 * 1000;   //每隔5分钟测试一次唤醒

    private static PowerManager.WakeLock wakeLock;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
        mContext = this;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.start_wakelock) {
            requestWakeLock();
        }else if(id == R.id.cancle_wakelock){
            releaseWakeLock();
        }else if(id == R.id.startAlarm){
            setAlarm(ALARM_TIME);
        }else if(id == R.id.cancleAlarm){
            cancelAlarm();
        }
        return super.onOptionsItemSelected(item);
    }

    public static void requestWakeLock() {
        if (wakeLock == null) {
            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
            wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TESTAPP");
            wakeLock.setReferenceCounted(false);
        }
        wakeLock.acquire();
        doTask();
    }

    public static void releaseWakeLock() {
        if (wakeLock != null) {
            wakeLock.release();
            wakeLock = null;
        }
    }

    public static void setAlarm(long delay) {
        AlarmManager alarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
        /*
        Intent dailyIntent = new Intent(mContext, TestBroadcast.class);
        PendingIntent dailyOperation = PendingIntent.getBroadcast(mContext, 0, dailyIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        */
        Intent dailyIntent = new Intent(mContext, TestService.class);
        PendingIntent dailyOperation = PendingIntent.getService(mContext,0,dailyIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        alarmManager.cancel(dailyOperation);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() + delay, dailyOperation);
            //AlarmManager.AlarmClockInfo clock = new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + delay, dailyOperation);
           //alarmManager.setAlarmClock(clock,dailyOperation);
        } else
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            alarmManager.setExact(android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delay, dailyOperation);
        } else {
            alarmManager.set(android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delay, dailyOperation);
        }
        Log.d(TAG,"setAlarm in");
    }



    public static void cancelAlarm(){
        AlarmManager alarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
        /*
        Intent dailyIntent = new Intent(mContext, TestBroadcast.class);
        PendingIntent dailyOperation = PendingIntent.getBroadcast(mContext, 0, dailyIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        */
        Intent dailyIntent = new Intent(mContext, TestService.class);
        PendingIntent dailyOperation = PendingIntent.getService(mContext,0,dailyIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        alarmManager.cancel(dailyOperation);
        Log.d(TAG,"cancelAlarm in");
    }

    public static void doTask(){
        Log.d(TAG,"Test task in");
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG,"Thread task in");
                try{
                    long startTime = System.currentTimeMillis();
                    long count = 0;
                    while(true) {
                        Log.d(TAG,"retry task here :"+System.currentTimeMillis());
                        long enableTime = System.currentTimeMillis() - startTime;
                        if (enableTime > TASK_TIME) {
                            break;
                        }
                        count++;
                        Thread.sleep(10);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                Log.d(TAG,"Thread task finish");
                setAlarm(ALARM_TIME);
            }
        }).start();
    }

    public static class TestBroadcast extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            doTask();
        }
    }

    public static class TestService extends Service{
        @Override
        public void onCreate() {
            super.onCreate();
        }

        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            doTask();
            return super.onStartCommand(intent, flags, startId);
        }
    }
}

进入测试应用后配置Alarm任务,在Alarm任务开始之前灭屏准备休眠(在这个测试中是配置Alarm任务后的5分钟内):


阶段1
有这么几个时刻:
第一次设置Alarm任务的时间:

03-22 16:10:47.450 15051 15051 D TESTAPP : setAlarm in

灭屏时刻:

Android设备CPU休眠问题分析_第3张图片

(图中的时间和日志的时间有时区差别需要+8)可以看出是 16:10:52

Alarm任务的时刻:

03-22 16:20:33.243 15051 15051 D TESTAPP : Test task in

程序Alarm设置的5分钟,而执行间隔了10分钟,可见Alarm任务并没有唤醒CPU,而且这个任务在16:20:33.243的执行还是因为人为的点亮了屏幕唤醒CPU:

Android设备CPU休眠问题分析_第4张图片

从这里推测,CPU在16:15:47时刻之前已经休眠了。

阶段2
点亮屏幕后CPU被唤醒,Alarm任务继续进行。
第二次设置Alarm任务时刻:

03-22 16:23:33.259 15051 15620 D TESTAPP : setAlarm in

刚好是任务执行3分钟,符合程序预期。

第二次Alarm任务执行时刻:

03-22 16:24:50.259 15051 15051 D TESTAPP : Test task in
03-22 16:24:50.267 15051 27844 D TESTAPP : Thread task in
Android设备CPU休眠问题分析_第5张图片

一分钟后Alarm任务开始执行,符合程序预期。

03-22 16:31:10.271 15051 27844 D TESTAPP : Thread task finish
03-22 16:31:10.277 15051 27844 D TESTAPP : setAlarm in

本来应该在3分钟后结束的Alarm任务,并没有结束,这里也是推测CPU已经休眠。

Android设备CPU休眠问题分析_第6张图片

Alarm任务也是等到下一次亮屏CPU被唤醒后继续进行,这么来看还是符合程序设计预期的。

为了做个对比把Alarm设置为type:ELAPSED_REALTIME_WAKEUP进行测试:
阶段1
第一次Alarm任务在14:49:12设置:

屏幕在14:49:19关闭:

Android设备CPU休眠问题分析_第7张图片

Android设备CPU休眠问题分析_第8张图片

从时间点上看到14:49:1914:54:16期间屏幕是关闭的,CPU是陆陆续续running状态,在14:54:16时刻距离Alarm设置时间基本为5分钟(set接口没有setExtra接口精确,可参考Android接口介绍):

/**
     * Schedule an alarm to be delivered precisely at the stated time.
     *
     * 

* This method is like {@link #set(int, long, PendingIntent)}, but does not permit * the OS to adjust the delivery time. The alarm will be delivered as nearly as * possible to the requested trigger time. * *

* Note: only alarms for which there is a strong demand for exact-time * delivery (such as an alarm clock ringing at the requested time) should be * scheduled as exact. Applications are strongly discouraged from using exact * alarms unnecessarily as they reduce the OS's ability to minimize battery use. * * @param type type of alarm. * @param triggerAtMillis time in milliseconds that the alarm should go * off, using the appropriate clock (depending on the alarm type). * @param operation Action to perform when the alarm goes off; * typically comes from {@link PendingIntent#getBroadcast * IntentSender.getBroadcast()}. * * @see #set * @see #setRepeating * @see #setWindow * @see #cancel * @see android.content.Context#sendBroadcast * @see android.content.Context#registerReceiver * @see android.content.Intent#filterEquals * @see #ELAPSED_REALTIME * @see #ELAPSED_REALTIME_WAKEUP * @see #RTC * @see #RTC_WAKEUP */ public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null, null, null); }

Android设备CPU休眠问题分析_第9张图片

Alarm任务执行开始的时间是:

03-22 14:54:16.288  5219  5897 D TESTAPP : Thread task in

第一个Alarm任务开始执行的时间间隔5分钟,符合程序预期。
但是结束时间却是:

03-22 15:03:04.279  5219  5897 D TESTAPP : Thread task finish

为什么呢?
而且在这个时间点是我按电源键点亮了屏幕:

03-22 15:03:04.278  2249  2416 D WindowManager: interceptKeyTq keycode=285 interactive=false keyguardActive=false policyFlags=2000000

这么看来,在线程执行的某个时间点CPU休眠了,导致线程挂起了,待下次CPU恢复时,线程继续处理任务。
15:03:04这个时间点,线程处理完任务后设置下一个Alarm任务

03-22 15:04:04.382  5219  5219 D TESTAPP : Test task in
03-22 15:04:04.383  5219  9610 D TESTAPP : Thread task in

间隔1分钟。符合程序预期。

任务在15:07:04正常结束,这个时间点配合Battery Historian图表分析,CPU刚好是正常running状态:

03-22 15:07:04.385  5219  9610 D TESTAPP : Thread task finish
Android设备CPU休眠问题分析_第10张图片

从前面两个阶段看来,Alarm设置的type:ELAPSED_REALTIME_WAKEUP也并没有唤醒CPU来完成Alarm任务。有什么意义呢?

在Android6.0及以上的平台引入了低电耗模式:
https://developer.android.com/training/monitoring-device-state/doze-standby.html#restrictions
根据文档说明,在低电耗模式下AlarmManager闹铃(包括setExact()setWindow())推迟到下一维护时段,这倒是符合之前的现象了。
那么在低电耗模式下触发的Alarm,需要使用使用 setAndAllowWhileIdle()setExactAndAllowWhileIdle()

setAndAllowWhileIdle测试效果:

Android设备CPU休眠问题分析_第11张图片

根据程序配置,第一次Alarm在触发后5分钟开始进行,测试结果符合预期,但是第二次的Alarm事件在第一次Alarm任务执行完毕后的15分钟后,在测试结果表格中的 07:46:13时刻并没有唤醒动作(从 07:28:13开始,包括任务执行需要的3分钟和Alarm配置的任务时间15分钟),不符合预期结果。

Android设备CPU休眠问题分析_第12张图片

setExactAndAllowWhileIdle测试效果也是如此,并没有唤醒CPU。

根据google的说明,setAlarmClock可以唤醒设备。
setAlarmClock接口测试:

Android设备CPU休眠问题分析_第13张图片

这张是使用另外一个厂商的设备测试的结果。

按这么推理,Alarm任务并不可靠,ROM经过定制后可能导致在休眠状态下无法响应Alarm任务。

你可能感兴趣的:(Android设备CPU休眠问题分析)