精华内容
下载资源
问答
  • 对于功耗优化时长可以看到alarm唤醒频繁,或者alarm timer持锁时间过长的问题,对于这样的情况Android的各个版本也都有持续性的优化,对于alarm来说,简而言之都是加强管控,尽可能减少唤醒,集中批量处理。...

    1 Overview

    对于系统功耗优化,时常可以看到alarm唤醒频繁,或者alarm timer持锁时间过长的问题,对于这样的情况Android的各个版本也都有持续性的优化,对于alarm来说,简而言之都是加强管控,尽可能减少唤醒,集中批量处理。

    2 AlarmManager

    AlarmManager提供接口供应用根据自己的需求,来设置alarm以及对应的处理方法

    frameworks/base/core/java/android/app/AlarmManager.java

    alarm目前有几种类型

    • RTC_WAKEUP
        /**
         * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
         * (wall clock time in UTC), which will wake up the device when
         * it goes off.
         */
        public static final int RTC_WAKEUP = 0;
    
    • RTC
        /**
         * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
         * (wall clock time in UTC).  This alarm does not wake the
         * device up; if it goes off while the device is asleep, it will not be
         * delivered until the next time the device wakes up.
         */
        public static final int RTC = 1;
    
    • ELAPSED_REALTIME_WAKEUP
        /**
         * Alarm time in {@link android.os.SystemClock#elapsedRealtime
         * SystemClock.elapsedRealtime()} (time since boot, including sleep),
         * which will wake up the device when it goes off.
         */
        public static final int ELAPSED_REALTIME_WAKEUP = 2;
    
    • ELAPSED_REALTIME
        /**
         * Alarm time in {@link android.os.SystemClock#elapsedRealtime
         * SystemClock.elapsedRealtime()} (time since boot, including sleep).
         * This alarm does not wake the device up; if it goes off while the device
         * is asleep, it will not be delivered until the next time the device
         * wakes up.
         */
        public static final int ELAPSED_REALTIME = 3;
    
    • RTC_POWEROFF_WAKEUP
        /**
         * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
         * (wall clock time in UTC), which will wake up the device when
         * it goes off. And it will power on the devices when it shuts down.
         * Set as 5 to make it be compatible with android_alarm_type.
         * @hide
         */
        public static final int RTC_POWEROFF_WAKEUP = 5;
    

    其特点用途如下

    • RTC和ELAPSED_REALTIME 的差异在于前者使用绝对时间,后者使用相对时间来设置

    • WAKEUP类型的alarm在超时时如果系统处于待机休眠状态,则会唤醒系统,非WAKEUP类型的只能等到下一次系统唤醒的时候再被处理

    • RTC_POWEROFF_WAKEUP 基本就是用来给关机闹钟,或者定时开机用

    提供的设置接口有

    • set(int type, long triggerAtMillis, PendingIntent operation)
      设置一次性的
    • setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)
      设置可重复执行的
    • setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)
      设置可重复执行的,并且没有精确的时间要求

    第三个参数就是Alarm对应的超时处理方法,

    除了PendingIntent还有OnAlarmListener类型

    public void set(@AlarmType int type, long triggerAtMillis, String tag, OnAlarmListener listener,

            Handler targetHandler)
    

    以上的API在小于19的情况下,可以正常工作,因为从Android 4.4开始,set 或者 setRepeating创建的alarm将会变得不准确,原因是做了唤醒对齐, 时间不敏感的放一起触发。

    这时候可以用setWindow(), 这个api设置的是一个时间范围,可以确保alarm在这个时间范围内触发
    如果需要非常精确的时间,可以使用setExact()

    在Android 6.0 API 23 开始,DOZE 又会导致上述方法设置的alarm无法生效

    标准 AlarmManager 闹铃(包括 setExact() 和 setWindow())推迟到下一维护时段。
        如果您需要设置在低电耗模式下触发的闹铃,请使用 setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle()。
        一般情况下,使用 setAlarmClock() 设置的闹铃将继续触发 — 但系统会在这些闹铃触发之前不久退出低电耗模式。
    

    这时候可以使用setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation)

    取消设置的接口

    对应到设置的方法,cancel两种类型的超时处理方法

        /**
         * Remove any alarms with a matching {@link Intent}.
         * Any alarm, of any type, whose Intent matches this one (as defined by
         * {@link Intent#filterEquals}), will be canceled.
         *
         * @param operation IntentSender which matches a previously added
         * IntentSender. This parameter must not be {@code null}.
         *
         * @see #set
         */
        public void cancel(PendingIntent operation) {
            if (operation == null) {
                final String msg = "cancel() called with a null PendingIntent";
                if (mTargetSdkVersion >= Build.VERSION_CODES.N) {
                    throw new NullPointerException(msg);
                } else {
                    Log.e(TAG, msg);
                    return;
                }
            }
    
            try {
                mService.remove(operation, null);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    
        /**
         * Remove any alarm scheduled to be delivered to the given {@link OnAlarmListener}.
         *
         * @param listener OnAlarmListener instance that is the target of a currently-set alarm.
         */
        public void cancel(OnAlarmListener listener) {
            if (listener == null) {
                throw new NullPointerException("cancel() called with a null OnAlarmListener");
            }
    
            ListenerWrapper wrapper = null;
            synchronized (AlarmManager.class) {
                if (sWrappers != null) {
                    wrapper = sWrappers.get(listener);
                }
            }
    
            if (wrapper == null) {
                Log.w(TAG, "Unrecognized alarm listener " + listener);
                return;
            }
    
            wrapper.cancel();
        }
    

    3 AlarmManagerService

    AlarmManagerService提供接口给AlarmManager,实现上述接口,另外也实现了系统对于Alarm的处理逻辑,包括一些优化

    从Android 4.4开始 Alarm默认是非精确的,也可以指定采用精确模式。在非精确模式下,Alarm是批量提醒的,每个Alarm会被根据其触发时间以及最大触发时间,加入到不同的batch中去,同一个batch中的alarm会同时触发,这也是low power的优化。对于

    frameworks/base/services/core/java/com/android/server/AlarmManagerService.java

    SET

    setImpl 是最常用的方法
    比如setAndAllowWhileIdle的实现

        public void setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
                PendingIntent operation) {
            setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
                    operation, null, null, null, null, null);
        }
    

    setImpl() 会把RTC类型的转换成ELAPSED_REALTIME类型,也就是说最终到底层的只有 ELAPSED_REALTIME_WAKEUP和ELAPSED_REALTIME, 也就是id 2和3

    final long nominalTrigger = convertToElapsed(triggerAtTime, type);
    

    其调用流程如下

    setImpl()
        setImplLocked()
            setImplLocked()
                rescheduleKernelAlarmsLocked()
                    setlocked()
                        set()
    

    Service处理逻辑

    在添加alarm的时候,比如先添加了一个15s的wakeup alarm(假设其就是下一个wakeup类型的alarm),再添加一个10s的wakeup alarm

    在调用进行到 setImplLocked的时候, 15s的alarm所在的batch已在mAlarmBatches 数组中,这个时候来了10s的,那么10s的batch就会位于mAlarmBatches中15s的前面,之后调用rescheduleKernelAlarmsLocked(), 进而把位于第一个的10s设置下去

    如果是先设15s的后面来了一个20s的,实际上20s的在这一次是不会被设置下去的,需要等到其前面的超时之后,alarm thread遍历20s的这个已经排在最前,才会被设置下去

    
    private void setImplLocked(int type, long when, long whenElapsed, long maxWhen, long interval,  
            PendingIntent operation, boolean isStandalone, boolean doValidate,  
            WorkSource workSource) {  
        /**创建一个alarm,其中各参数的含义如下: 
         * type 闹钟类型 ELAPSED_REALTIME、RTC、RTC_WAKEUP等 
         * when 触发时间 UTC类型,绝对时间,通过System.currentTimeMillis()得到 
         * whenElapsed 相对触发时间,自开机算起,含休眠,通过SystemClock.elapsedRealtime()得到 
         * maxWhen 最大触发时间 
         * interval 触发间隔,针对循环闹钟有效 
         * operation 闹钟触发时的行为,PendingIntent类型 
         */  
        Alarm a = new Alarm(type, when, whenElapsed, maxWhen, interval, operation, workSource);  
        //根据PendingIntent删除之前已有的同一个闹钟  
        removeLocked(operation);  
      
        boolean reschedule;  
        //尝试将alarm加入到合适的batch中,如果alarm是独立的或者无法找到合适的batch去容纳此alarm,返回-1  
        int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);  
        if (whichBatch < 0) {  
            //没有合适的batch去容纳alarm,则新建一个batch  
            Batch batch = new Batch(a);  
            batch.standalone = isStandalone;  
            //将batch加入mAlarmBatches中,并对mAlarmBatches进行排序:按开始时间升序排列  
            reschedule = addBatchLocked(mAlarmBatches, batch);  
        } else {  
            //如果找到合适了batch去容纳此alarm,则将其加入到batch中  
            Batch batch = mAlarmBatches.get(whichBatch);  
            //如果当前alarm的加入引起了batch开始时间和结束时间的改变,则reschedule为true  
            reschedule = batch.add(a);  
            if (reschedule) {  
                //由于batch的起始时间发生了改变,所以需要从列表中删除此batch并重新加入、重新对batch列表进行排序  
                mAlarmBatches.remove(whichBatch);  
                addBatchLocked(mAlarmBatches, batch);  
            }  
        }  
      
        if (DEBUG_VALIDATE) {  
            if (doValidate && !validateConsistencyLocked()) {  
                Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when  
                        + " when(hex)=" + Long.toHexString(when)  
                        + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen  
                        + " interval=" + interval + " op=" + operation  
                        + " standalone=" + isStandalone);  
                rebatchAllAlarmsLocked(false);  
                reschedule = true;  
            }  
        }  
      
        if (reschedule) {  
            rescheduleKernelAlarmsLocked();  
        }  
    }  
    
        void rescheduleKernelAlarmsLocked() {
            // Schedule the next upcoming wakeup alarm.  If there is a deliverable batch
            // prior to that which contains no wakeups, we schedule that as well.
            long nextNonWakeup = 0;
            if (mAlarmBatches.size() > 0) {
                final Batch firstWakeup = findFirstWakeupBatchLocked();
                final Batch firstBatch = mAlarmBatches.get(0);
                final Batch firstRtcWakeup = findFirstRtcWakeupBatchLocked();
                if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
                    mNextWakeup = firstWakeup.start;
                    mLastWakeupSet = SystemClock.elapsedRealtime();
                    setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
                }
    

    CANCEL

    cancel方法都会调用到AlarmManagerService中的 remove

    boolean remove(final PendingIntent operation, final IAlarmListener listener)

    相比与set,cancel并没有直接调用到底层的所谓cancel的api,kernel也没有提供cancel的接口

    假设设置了一个10s的wakeup alarm, 在超时之前cancel,实际上是从超时的执行列表中移除10s alarm对应的处理方法,那这个alarm是怎么处理的呢?

    • 首先会从batch中移除这个alarm

    • alarm thread 会调用到rescheduleKernelAlarmsLocked()把移除的这个alarm的下一个作为next,重新写到底层

    4 JNI 层

    frameworks/base/services/core/jni/com_android_server_AlarmManagerService.cpp

    在JNI层,可以看到id 2和3对应的分别是 CLOCK_BOOTTIME_ALARM 和 CLOCK_BOOTTIME

    static const clockid_t android_alarm_to_clockid[N_ANDROID_TIMERFDS] = {
        CLOCK_REALTIME_ALARM,
        CLOCK_REALTIME,
        CLOCK_BOOTTIME_ALARM,
        CLOCK_BOOTTIME,
        CLOCK_MONOTONIC,
        CLOCK_REALTIME,
    };
    

    调用timerfd的接口设置下去

    int AlarmImpl::set(int type, struct timespec *ts)
    {
        if (static_cast<size_t>(type) > ANDROID_ALARM_TYPE_COUNT) {
            errno = EINVAL;
            return -1;
        }
    
        if (!ts->tv_nsec && !ts->tv_sec) {
            ts->tv_nsec = 1;
        }
        /* timerfd interprets 0 = disarm, so replace with a practically
           equivalent deadline of 1 ns */
    
        struct itimerspec spec;
        memset(&spec, 0, sizeof(spec));
        memcpy(&spec.it_value, ts, sizeof(spec.it_value));
    
        return timerfd_settime(fds[type], TFD_TIMER_ABSTIME, &spec, NULL);
    }
    

    5 Linux Kernel 层

    很核心的概念就是对于alarm timer 或者是hrtimer,用户空间只能设置设置一个下来,新的会把旧的覆盖掉, 比如你通过AlarmManager设置了一个15s以后的wakeup 类型的alrm事件,再设置一个10s以后的,那么timerfd会把先前设置的15s的cancel掉,再设置10s的

    timerfd

    ~/fs/timerfd.c

    timerfd_settime 这个系统调用内核的调用路径如下, 根据类型的不同,分为alarm和hrtimer

    do_timerfd_settime()
        timerfd_setup()
            alarm_init()        ~/kernel/time/alarmtimer.c
            alarm_start()
            hrtimer_init()        ~/kernel/time/hrtimer.c
            hrtimer_start()
    
    static int do_timerfd_settime(int ufd, int flags, 
    		const struct itimerspec *new,
    		struct itimerspec *old)
    {
    	struct fd f;
    	struct timerfd_ctx *ctx;
    	int ret;
    
    	if ((flags & ~TFD_SETTIME_FLAGS) ||
    	    !timespec_valid(&new->it_value) ||
    	    !timespec_valid(&new->it_interval))
    		return -EINVAL;
    
    	ret = timerfd_fget(ufd, &f);
    	if (ret)
    		return ret;
    	ctx = f.file->private_data;
    
    	if (!capable(CAP_WAKE_ALARM) && isalarm(ctx)) {
    		fdput(f);
    		return -EPERM;
    	}
    
    	timerfd_setup_cancel(ctx, flags);
    
    	/*
    	 * We need to stop the existing timer before reprogramming
    	 * it to the new values.
    	 */
    	for (;;) {
    		spin_lock_irq(&ctx->wqh.lock);
    
    		if (isalarm(ctx)) {
    			if (alarm_try_to_cancel(&ctx->t.alarm) >= 0)
    				break;
    		} else {
    			if (hrtimer_try_to_cancel(&ctx->t.tmr) >= 0)
    				break;
    		}
    		spin_unlock_irq(&ctx->wqh.lock);
    		cpu_relax();
    	}
    
    	/*
    	 * If the timer is expired and it's periodic, we need to advance it
    	 * because the caller may want to know the previous expiration time.
    	 * We do not update "ticks" and "expired" since the timer will be
    	 * re-programmed again in the following timerfd_setup() call.
    	 */
    	if (ctx->expired && ctx->tintv.tv64) {
    		if (isalarm(ctx))
    			alarm_forward_now(&ctx->t.alarm, ctx->tintv); //如果是Alarm timer类型,先cancel前一个alarm类型的
    		else
    			hrtimer_forward_now(&ctx->t.tmr, ctx->tintv);
    	}
    
    	old->it_value = ktime_to_timespec(timerfd_get_remaining(ctx));
    	old->it_interval = ktime_to_timespec(ctx->tintv);
    
    	/*
    	 * Re-program the timer to the new value ...
    	 */
    	ret = timerfd_setup(ctx, flags, new); //开始设置新的timer
    
    	spin_unlock_irq(&ctx->wqh.lock);
    	fdput(f);
    	return ret;
    }
    

    alarm timer

    AlarmTimer 参考https://www.cnblogs.com/arnoldlu/p/7145879.html 这一篇就好

    值得注意的是 alarmtimer_suspend方法,在进入睡眠之前,遍历alarm_bases->timerqueue,取最近一次timer的expires;将此expires写入RTC定时器,RTC超时后会唤醒系统。

    
    
    static int alarmtimer_suspend(struct device *dev)
    {
    ...
        rtc = alarmtimer_get_rtcdev();-------------------------------------获取RTC设备
        /* If we have no rtcdev, just return */
        if (!rtc)
            return 0;
    
        /* Find the soonest timer to expire*/
        for (i = 0; i < ALARM_NUMTYPE; i++) {------------------------------遍历ALARM_REALTIME和ALARM_BOOTTIME两个alarm_base,取各自最近expires
            struct alarm_base *base = &alarm_bases[i];
            struct timerqueue_node *next;
            ktime_t delta;
    
            spin_lock_irqsave(&base->lock, flags);
            next = timerqueue_getnext(&base->timerqueue);
            spin_unlock_irqrestore(&base->lock, flags);
            if (!next)
                continue;
            delta = ktime_sub(next->expires, base->gettime());
            if (!min.tv64 || (delta.tv64 < min.tv64))---------------------比较两次expires,取最小者
                min = delta;
        }
        if (min.tv64 == 0)
            return 0;
    
        if (ktime_to_ns(min) < 2 * NSEC_PER_SEC) {-----------------------如果expires小于2秒,保持系统唤醒2秒,并中断suspend流程。
            __pm_wakeup_event(ws, 2 * MSEC_PER_SEC);
            return -EBUSY;
        }
    
        /* Setup an rtc timer to fire that far in the future */
        rtc_timer_cancel(rtc, &rtctimer);-------------------------------取消当前rtctimer
        rtc_read_time(rtc, &tm);
        now = rtc_tm_to_ktime(tm);
        now = ktime_add(now, min);--------------------------------------获取RTC时间,将rtc_timer转换成ktimer_t,将RTC时间加上AlarmTimer超时。
    
        /* Set alarm, if in the past reject suspend briefly to handle */
        ret = rtc_timer_start(rtc, &rtctimer, now, ktime_set(0, 0));---设置rtctimer
        if (ret < 0)
            __pm_wakeup_event(ws, 1 * MSEC_PER_SEC);
        return ret;
    }
    
    
    
    
    

    参考文章

    https://www.cnblogs.com/leipDao/p/8203684.html
    https://www.cnblogs.com/arnoldlu/p/7145879.html
    https://blog.csdn.net/wh_19910525/article/details/44303039

    展开全文
  • 现在的处理器都支持DVFS - 动态...在某Android平台,在单线程运行dhrystone的情况下, 可以看到随着DMIPS的增加(其实也就是频率的增加),Power数据前期相对平缓,后面就变陡了,越高频率功耗增加的越多 关于CPU功...

    现在的处理器都支持DVFS - 动态频率电压调整,我们都知道DVFS的设计是为了low power,那么CPU频率和功耗之间的关系如何,运行在相同频率处理A任务和B任务功耗就一样吗?

    同任务不同频率

    在某Android平台,在单线程运行dhrystone的情况下, 可以看到随着DMIPS的增加(其实也就是频率的增加),Power数据前期相对平缓,后面就变陡了,越高频率功耗增加的越多(7000 DMIPS 功耗是400mW,频率加倍 14000 DMIPS 功耗1380mW,虽然性能是2倍,但是功耗变成了3.45倍)

    在这里插入图片描述

    关于CPU功耗和电压频率的关系有一个公式,可以看到电压是以 V2V^2 的级别来影响功耗的

    P=CV2fP = C * V^2 * f

    • P是功耗
    • C可以简单看作一个常数,它由制程和设计等因素决定
    • V是电压
    • f是频率

    从这里也可以看到一个程序功耗优化的方向 - 多线程

    假设A程序原来是单线程,需要2GHz的频率来运行,现在把A程序改造成2个线程的,那理论上两个cpu 1GHz就够(参考某平台A53 2GHz-0.8V 1GHz-0.6V),而且处理时间相同
    代入公式

    • 单线程 2GHz
      Psingle=C0.8V22GHzP_{single} = C * 0.8V^2 * 2GHz

    • 2线程 1GHz
      Pmulti=2C0.6V21GHzP_{multi} = 2 * C * 0.6V^2 * 1GHz

    • 结果
      Psingle=1.78PmultiP_{single} = 1.78 * P_{multi}

    也就是说这种情况下, 维持效率不变, A程序的单线程会比双线程功耗高出78%

    同频率不同任务

    不同任务的场景相对复杂,比如有些任务是间断性性的,期间CPU可能处于空闲状态,当然功耗会低,为了比较方便,我们统一选取持续性负载的任务,测试期间一直占用CPU

    在某Android平台上使用elephant stress的 分别使用单线程md5算法和sha-256算法,CPU都是处在最高频率,而且从trace信息也可以看到任务一直处于占用CPU的状态

    • 任务1 sha-256算法

    systrace CPU占用
    在这里插入图片描述

    功耗430mA 4V
    在这里插入图片描述

    • 任务2 md5算法

    systrace CPU占用
    在这里插入图片描述

    功耗300mA 4V
    在这里插入图片描述

    从上面的实测数据可以看到,sha-256算法的功耗会比md5算法要高出45%, 虽然CPU都是在最高频率运行,中间也没有出现idle或者是空闲的情况。

    关于这个问题有一篇知乎的文章知乎的文章讲的比较好,套用到上面的两个算法来看,可以简单理解为不同算法,CPU运行时候的指令是有很大差别的,不同的指令又对应不同数量和类型的晶体管,每条指令所要调动的晶体管数目不同(fetch,decode,excute,memory),而功耗是被调动晶体管功耗的总和。

    所以虽然我们看到同样的频率,CPU的占用率也是满的,但是功耗是可以差别很大。

    展开全文
  • 可以采用功耗问题时间追踪表来精确追踪功耗异常。 可以使用如下命令来打开指定文件的 kernel log (以 qpnp-adc-tm.c 和 qpnp-adc-common.c 为例): adb shell mount -t debugfs none /sys/kernel/debug adb...


    1、底电流调试(Rock Bottom Current Optimization

    底电流在手机飞行模式下调试。每个平台的底电流数据可能不一样,具体可以参考release出来的Current Consumption Data文档或者release note。一般情况下的底电流参考数据上限是:

    512M RAM < 1.5mA; 1G RAM < 2mA; 2G RAM < 2.6mA

     

    1.1校准RF

    保证RFPAAntenna switchTunerAPTGPIO工作在正常状态

     

    1.2飞行模式

    开启飞行模式、关闭GPS、关闭自动旋转屏幕、关闭自动亮度调节、关闭其他特效效果设置

    开启飞行模式,可以基本避免蓝牙、wifiNFC、网络、FM等的一般影响;

    关闭GPS,可以基本排除开启GPS对底电流的影响;

    关闭自动旋转屏幕,可以基本排除sensor的影响;

    关闭自动亮度调节,可以基本排除距离感应到的影响;

    关闭其他特效效果设置,如指纹识别、黑屏手势、智能体感、手势隔空操作。。。。。。

     

    1.3使用perf_defconfig

    修改device/qcom/<TARGET>/AndroidBoard.mk。如果KERNEL_DEFCONFIG := <TARGET>_defconfig,那么改成KERNEL_DEFCONFIG := <TARGET>-perf_defconfig

    同时,kernel代码改用/kernel/arch/arm/configs/<TARGET>-perf_defconfig

    <TARGET>是平台名称或者项目名称

     

    1.4移除debugging APKs

    /system/app/Logkit.apk

    /system/app/com.qualcomm.qlogcat.apk

    /system/xbin/qlogd

     

    1.5把应用尽量删除

    在设置-->应用,禁用正在运行的应用

     

    1.6去掉CPU占用高的进程

    adb shell

    top

    查看CPU占用,去掉在休眠模式下CPU占用大于0的进程。kill掉该进程,若kill不掉则rm掉相关应用。对于占用CPU高的kwork,需要查找驱动原因。

     

    1.7手动移除所有可以移除的外设

    手机连上安捷伦电源,手机开机,然后让手机进入待机状态。手动移除TPLCM、前camera、后camerasensorSD卡、SIM卡等可以手动移除的外围器件,同时观察并记录底电流变化。

    直接移除WLAN芯片可能会导致开不了机,所以在移除WLAN之前,先对软件做如下处理:

    # mount -o rw,remount -t vfat /dev/block/bootdevice/by-name/modem 

    # cd /firmware/image 

    # rm wcnss.* 

    # reboot 

    或者

    #lsmod

    #rmmod WLAN

    移除其他可以移除的芯片(sensorNFC。。。)

     

    1.8移除驱动模块

    /kernel/arch/arm/configs/<TARGET>-perf_defconfig中把sensorTPLCMcamera等的驱动模块移除;

    或者在对应驱动的Makefile里面,移除驱动代码

    然后编译bootimage,烧入手机观察底电流变化

     

    1.9配置不用的GPIO

    将不用的GPIO置为输入、拉低;配置成SPII2CGPIO,若不用,置为悬空

    boot_images/core/systemdrivers/tlmm/config/platform/TLMMChipset.xml,修改GPIO配置。该处配置GPIO的初始状态,驱动有可能会修改GPIO

    对比项目原理图与平台参考原理图,项目原理图中多出的NC GPIO要处理掉。

     

    1.10检查power相关的NV items

    需要跟CE确认。一般如下:

    1027 = 0

    1895 = 0

    1892 = 0

    1962 = 0

    4679 = 16

    4201 = 0

    3851 = 0

    3852 = 6

    7157 = 1

    69745 rxd_enable = 0

    WCDMA NV:

    NV3581 = 0

    NV3852 = 6

     

    1.11排查GPIOLDO、总线

    对比项目原理图与平台参考原理图,排查硬件不一样的GPIOLDO、总线配置。

    量测各GPIOLDOI2C在休眠时候的电压,需用万用表准确测量。

    休眠时各路I2C GPIO的电压是多少v,用万用表准确测量。

    如果条件允许,测量所有LDO在休眠前和休眠后的准确电压。

    对于LDO,调试方法如下:

    1adb shell关闭LDO

    如关闭L3

    cd /sys/kernel/debug/regulator/8916_l3/

    echo 0 > enable

    2LDO太多设备用到,不适合用adb shell来关。可以这样调试:

    cat /sys/kernel/debug/regulator/8916_l6/consumers 

    shell@msm8916_32:/sys/kernel/debug/regulator/8916_l6 $ cat consumers 

    Device-Supply EN Min_uV Max_uV load_uA 

    0-000c-vio Y 1800000 1800000 0 

    0-0068-vi2c N 1800000 1800000 0 

    5-0038-vcc_i2c Y 1800000 1800000 0 

    1a98000.qcom,mdss_dsi-vddio N 1800000 1800000 100 

    1a98300.qcom,mdss_dsi_pll-vddio N 1800000 1800000 100 

    8916_l6 N 0 0 0 

    这样就可以看到是哪些设备请求了LDO6。然后 找到对应的代码,在休眠时关掉LDO,唤醒时再打开。 

    0-000c: 挂在I2C0上地址为0xc 

    5-0038: 挂在I2C0上地址为0x38 

    查看这两个设备的驱动代码是否有执行regulator_enable

    3)通过寄存器地址关闭LDO

    LDO6的地址是0x14546,则关闭方法是:

    # cd /sys/kernel/debug/spmi/spmi-0 

    # echo 0x14546 > address 

    # echo 1 > count 

    # cat data 可以读寄存器 

    # echo 0x00 > data LDO6

    4)关闭MPP

    在休眠前关闭MPP1MPP2MPP3MPP4 

    PM8916的寄存器地址分别是0xA0460xA1460xA2460xA346 

    在关闭前先cat data以查看原来的值。 

     

    GPIO状态读取的方法如下:

    1GPIO dump

    为了得到休眠时的GPIO状态,增加下面的打印

    rpm_proc/core/power/sleep/src/lpr_definition_uber.c 

    #include "tlmm_hwio.h" 

    void deep_sleep_enter(void) 

    uint64 sleep_duration; 

    ... 

     

    SWEVENT(SLEEP_DEEP_SLEEP_ENTER_COMPLETE, sleep_mode.deep_sleep_mode, sleep_duration); 

    // For test 

    int num; 

    int i=11;/*LCM_I2C_SCL, GPIO_11*/ 

    volatile uint32 cfg ,inout, val; 

     

    num = 122; //8916 only. Need modify for 8974/8x10/8x26 etc. 

     

    cfg = *(volatile uint32*)HWIO_TLMM_GPIO_CFGn_ADDR(i); //(0x61000000 + i * 0x1000) 

    inout = *(volatile uint32*)HWIO_TLMM_GPIO_IN_OUTn_ADDR(i);//(0x61000004 + i * 0x1000) 

    val = ((cfg << 16)&0xffff0000) | (inout&0xffff); 

    SWEVENT(SLEEP_GPIO_DUMP, i, val); 

    mpm_sw_done(sleep_mode.deep_sleep_mode, sleep_duration); 

    } while(FALSE); 

     

    增加for test下面这一段代码。

    然后再修改

    rpm_proc\core\power\sleep\build\SConscript 

    if 'USES_QDSS_SWE' in env: 

    QDSS_IMG = ['QDSS_EN_IMG'] 

    events = [['SLEEP_DEEP_SLEEP_ENTER=320','deep sleep enter. (sleep mode: %d) (count: %d)'], 

    ['SLEEP_DEEP_SLEEP_EXIT','deep sleep exit (sleep mode: %d)'], 

    ['SLEEP_NO_DEEP_SLEEP','bail early from deep sleep. (sleep mode: %d) (reason: %d)'], 

    ['SLEEP_RPM_HALT_ENTER','rpm halt enter'], 

    ['SLEEP_RPM_HALT_EXIT','rpm halt exit'], 

    ['SLEEP_MPM_INTS','pending mpm interrupts at wakeup: (interrupt_status_1 %d), (interrupt_status_2 %d)'], 

    ['SLEEP_DEEP_SLEEP_ENTER_COMPLETE','deep sleep exit complete (sleep mode: %d)'], 

    ['SLEEP_DEEP_SLEEP_EXIT_COMPLETE','deep sleep exit (sleep mode: %d)'], 

    ['SLEEP_MPM_WAKEUP_TIME','mpm wake up time (wakeup time: 0x%0.8x%0.8x)'], 

    ['SLEEP_GPIO_DUMP','gpio [%d] configuration is %d'],

    ['SLEEP_EVENT_LAST=383','sleep last event placeholder'] 

    增加SLEEP_GPIO_DUMP这一项。 

    编译烧写rpm.mbn

    让机器休眠,进入download,抓dump,然后将如下日志发给平台技术支持分析。 

    CODERAM.bin 

    MSGRAM.bin 

    DATARAM.bin 

    以及新编译出来的RPM_AAAAANAZR.elf。  

    2GPIO寄存器读取

    RPM可能不是很方便,也可以用busybox来读取寄存器,例如读GPIO11: 

    Physical Address for GPIO_CFG11 = 0x100B000 

    root@Android:/data/busybox # ./busybox devmem 0x100B000 32 

    ./busybox devmem 0x100B000 32 

    0x00000203 

    GPIO_PULL = "11" PULL_UP 

    FUNC_SEL = "0000" FUNCTION GPIO 

    DRV_STRENGTH = "000" DRV_2_MA 

    GPIO_OE = "1" Output Enable 

     

    1.12 rpm dump

    rpm dump,然后把log提供给平台技术支持。

    方法如下: 

    1ps_hold接地

    在休眠状态下,接ps_hold到地少于200mS,机器会进入紧急下载状态,插入USBQPST会自动得到memory dump,然后上传以下几个文件: 

    CODERAM.bin 

    MSGRAM.bin

    DATARAM.bin 

    以及RPM_AAAAANAZR.elf(必须与机器的编译时间一致匹配的elf) 

    2)改resetdownload key

    发这些命令改resetdownload key: 

    # cd /sys/kernel/debug/spmi/spmi-0 

    # echo 0x844 > address 

    # echo 4 > count 

    # cat data 

    00840 -- -- -- -- 0F 07 04 00 

    # echo 0x00 0x00 0x01 0x00 > data 

    # cat data 

    00840 -- -- -- -- 00 00 01 00 

    # echo 0x00 0x00 0x01 0x80 > data 

    # cat data 

    00840 -- -- -- -- 00 00 01 80 

    然后长按下键,会进入download。之后抓取log方法同上。

    如果进不了download,需要确认: 

    CONFIG_MSM_DLOAD_MODE=y 

    另外也有可能与nv 4399905有关系。

     

    1.13检查rpm_stats

    检查rpm_stats是否进入vdd min或者xo/no shutdown。使用下面的命令检查rpm lower power mode count:

    cat /sys/kernel/debug/rpm_stats

    如果vmincount0,则表明设备从来没有进入vdd minnon-zero则说明设备进入过vdd_min

    RPM Mode: xosd

    count:0

    time in last mode(msec):0

    time since last mode(sec):794

    actual last sleep(msec):0

    RPM Mode:vmin

    count:11

    time in last mode(msec):0

    time since last mode(sec):359

    actual last sleep(msec):110000

     

    1.14使用Trace32

    可以dump出来完整详细的gpio/clk/pmic信息,排除休眠时候的状态异常。

     

     

    2、待机电流优化(Standby Current Optimization

    2.1通过adb log排查

    adb logcat -v time > YearMounthDayHourMinute_logcat.txt   //main log

    adb logcat -v time -b events > YearMounthDayHourMinute_logcat_event.txt   //event log

    adb logcat -v time -b radio > YearMounthDayHourMinute_logcat_radio.txt    //radio log

    adb shell dmesg > YearMounthDayHourMinute_dmesg.txt                 //kernel log

    可以采用功耗问题时间追踪表来精确追踪功耗异常。

    可以使用如下命令来打开指定文件的kernel log(以qpnp-adc-tm.cqpnp-adc-common.c为例):

    adb shell mount -t debugfs none /sys/kernel/debug

    adb shell "echo 8 > /proc/sys/kernel/printk" 

    adb shell "echo 'file qpnp-adc-tm.c +p' > /sys/kernel/debug/dynamic_debug/control"

    adb shell "echo 'file qpnp-adc-common.c +p' > /sys/kernel/debug/dynamic_debug/control"

    adb shell "echo 8 > /proc/sys/kernel/printk"

     

    为指定的函数开启log,以qpnpint_handle_irq为例:

    adb shell "echo 'func qpnpint_handle_irq +p' > /sys/kernel/debug/dynamic_debug/control" 

     

    *#logkit#*调出logkit apk,可以保存logcatdmesgcrashQXDMGPU log等日志信息到手机里面。

     

    2.2 top

    通过top命令,可以查询到cpu占用较高的应用。如果一个应用一直在占用cpu,而此时并没有打开该应用,那么该应用很可能会导致待机异常。

    adb shell

    top

    “该场景下CPU使用率”是User+System+IOW+IRQ

    “模块相关的CPU占用率”是模块相关进程占用CPU使用率的总和

     

    2.3正在运行

    设置-->应用-->正在运行,可以看到正在运行的应用或者服务。禁止掉应用或者服务,观察待机电流变化。

     

    2.4 wakeup debug mask

    调试wakeup问题,可以使能debug功能,然后抓取logLog中会增加一些debug信息。

    mount -t debugfs none /sys/kernel/debug  

    echo 1 > /sys/kernel/debug/clk/debug_suspend  

    echo 1 > /sys/module/msm_show_resume_irq/parameters/debug_mask  

    echo 4 > /sys/module/wakelock/parameters/debug_mask  

    echo 1 > /sys/module/lpm_levels/parameters/debug_mask  

    echo 0x16 > /sys/module/smd/parameters/debug_mask  

     

    2.5 wakelock  

    1wakeup_sources  

    kernel wakelockuserspace wakelock都有可能阻止系统睡眠。所有的wakeup_sources均保存在sys节点/sys/kernel/debug/wakeup_sources里面。 

    该文件包含了如下信息:

    1the total amount of time a wakeup source has prevented suspend  

    2the amount of time a wakelock has been active since the last activation etc. The unit of time is milliseconds. 

     

    2active_since

    active_since值可以用来确认wakelock是否正在阻止休眠。如果该值不是零,那么这个wakelock正在工作并且阻止休眠。

     

    3、获取wakeup_sources的命令

    adb root 67754400

    adb remount

    adb shell 

    cat /sys/kernel/debug/wakeup_sources > /data/wakeup_sources.txt

    adb pull /data/wakeup_sources.txt

    获得wakeup_sources.txt以后,通过Excel打开,active_since不为0的项为wakeup source。以表2为例,msm_dwc3对应的active-since481756>0,这意味着msm_dwc3驱动在阻止系统睡眠,下一步需要检查msm_dwc3驱动代码及相关log

     

    表格 2 Wakeup source opened in Excel

     

    4power:wakeup_source_activate and power:wakeup_source_deactivate events

    当一个wakeup sourceacquire或者release时候,power:wakeup_source_activatepower:wakeup_source_deactivate event将随即被写到trace buffer里面,这样可以记录wakeup sourcedriver使用的频率。

    开启该功能的方法:

    echo "power:wakeup_source_activate power:wakeup_source_deactivate" > /sys/kernel/debug/tracing/set_event

     

    The power:wakeup_source_activate and power:wakeup_source_deactivate events are written to the trace buffer any time a wakeup source is acquired or released and it can provide information on how often a wakeup source is being used by a driver.  

    To enable these events, you can enable following:  

    echo "power:wakeup_source_activate power:wakeup_source_deactivate" > /sys/kernel/debug/tracing/set_event 

    Once the above done, the traces will be present in /sys/kernel/debug/tracing/trace. 

     

    2.6 powertop

    powertop用来看CPU的运行统计以协助调试power问题。powertop的用法如下:

    powertop --h

    Usage: powertop [OPTION...]

    n -d, --dump read wakeups once and print list of top offenders

    n -t, --time=DOUBLE default time to gather data in seconds

    n -r, --reset Reset PM stats data

    n -h, --help Show this help message

    n -v, --version Show version information and exit

    获取powertop log的方法:

    1. 通过USB连接手机到电脑

    2. adb shell,然后执行如下命令:

    sleep 10 && /data/powertop [-r] -d -t 30 > /data/powertop.log & 

    3. 拔掉USB线,等待10秒后开始功耗测试

    4. 插上USB  

    5. adb pull /data/powertop.log  

     

    2.7 CPU freq log 

    打开CPU freq change log

    mount -t debugfs none /sys/kernel/debug  

    cd /sys/kernel/debug 

    echo -n 'file acpuclock-8x60.c +p' > dynamic_debug/control 

    echo -n 'file acpuclock-krait.c +p' > dynamic_debug/control

     

    查看cpu freq stats: 

    cat /sys/devices/system/cpu/cpu0/cpufreq/stats 

    cat /sys/devices/system/cpu/cpu1/cpufreq/stats 

    cat /sys/devices/system/cpu/cpu2/cpufreq/stats 

    cat /sys/devices/system/cpu/cpu3/cpufreq/stats 

     

    To lock cpu freg: 

    echo the same freq to following sys mode will lock cpu freq to the setting freq. 

    /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq 

    /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq

     

    To enable/disable specific freq for ACPU 

    ACPU freq table is defined in acpu_freq_tbl_* structure of specific platform. 

    arch/arm/mach-msm/acpuclock-<platform name>.c 

    For 8974, it is defined in arch/arm/mach-msm/acpuclock-8974.c. the first column of following table used to enable/disable freq in the row: 1:enable, 0:disable  

    static struct acpu_level acpu_freq_tbl_2p3g_pvs0[] __initdata = { 

    { 1, { 300000, PLL_0, 0, 0 }, L2(0), 800000, 72 }, 

    { 0, { 345600, HFPLL, 2, 36 }, L2(1), 800000, 83 }, 

    { 1, { 422400, HFPLL, 2, 44 }, L2(2), 800000, 101 }, 

    { 0, { 499200, HFPLL, 2, 52 }, L2(2), 805000, 120 }, 

    { 0, { 576000, HFPLL, 1, 30 }, L2(3), 815000, 139 }, 

    { 1, { 652800, HFPLL, 1, 34 }, L2(3), 825000, 159 }, 

    { 1, { 729600, HFPLL, 1, 38 }, L2(4), 835000, 180 }, 

    { 0, { 806400, HFPLL, 1, 42 }, L2(4), 845000, 200 }, 

    { 1, { 883200, HFPLL, 1, 46 }, L2(4), 855000, 221 }, 

    { 1, { 960000, HFPLL, 1, 50 }, L2(9), 865000, 242 }, 

    { 1, { 1036800, HFPLL, 1, 54 }, L2(10), 875000, 264 }, 

    { 0, { 1113600, HFPLL, 1, 58 }, L2(10), 890000, 287 }, 

    { 1, { 1190400, HFPLL, 1, 62 }, L2(10), 900000, 308 }, 

    … 

    { 1, { 1958400, HFPLL, 1, 102 }, L2(19), 1040000, 565 }, 

    { 0, { 2035200, HFPLL, 1, 106 }, L2(19), 1055000, 596 }, 

    { 0, { 2112000, HFPLL, 1, 110 }, L2(19), 1070000, 627 }, 

    { 0, { 2188800, HFPLL, 1, 114 }, L2(19), 1085000, 659 }, 

    { 1, { 2265600, HFPLL, 1, 118 }, L2(19), 1100000, 691 }, 

    { 0, { 0 } } 

    }; 

     

    2.8 Hoplug cores 

    Core 0 cant be hotplugged, Core 1/2/3 can be hotplugged, 

    To remove core : 

    echo 0 > /sys/devices/system/cpu/cpu1/online 

    echo 0 > /sys/devices/system/cpu/cpu2/online 

    echo 0 > /sys/devices/system/cpu/cpu3/online 

     

    To add back core: 

    echo 1 > /sys/devices/system/cpu/cpu1/online

    echo 1 > /sys/devices/system/cpu/cpu2/online

    echo 1 > /sys/devices/system/cpu/cpu3/online

     

    2.9 Scaling governor

    To check scaling governor

    cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

     

    To set new governor

    echo <new_governor> > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

    比如:

    echo ondemand > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

     

    2.10 Mpdecision

    Use Mpdecison daemon to start/stop/enable debug with commands below: 

    Start mpdecison: 

    start mpdecision 

    Stop mpdecison

    stop mpdecision

     

    Enable mpdecision debug 

    start mpdecision --debug 

     

    2.11 Power feature enable/disable 

    Following sys node can be used to enable the lower resource, 

     

    echo 2 > /sys/module/lpm_resources/enable_low_power/l2 

    echo 1 > /sys/module/lpm_resources/enable_low_power/pxo 

    echo 1 > /sys/module/lpm_resources/enable_low_power/vdd_dig 

    echo 1 > /sys/module/lpm_resources/enable_low_power/vdd_mem 

    echo 1 > /sys/module/pm_8x60/modes/cpu0/power_collapse/suspend_enabled 

    echo 1 > /sys/module/pm_8x60/modes/cpu1/power_collapse/suspend_enabled 

    echo 1 > /sys/module/pm_8x60/modes/cpu2/power_collapse/suspend_enabled 

    echo 1 > /sys/module/pm_8x60/modes/cpu3/power_collapse/suspend_enabled 

    echo 1 > /sys/module/pm_8x60/modes/cpu0/power_collapse/idle_enabled 

    echo 1 > /sys/module/pm_8x60/modes/cpu0/standalone_power_collapse/suspend_enabled

    echo 1 > /sys/module/pm_8x60/modes/cpu1/standalone_power_collapse/suspend_enabled

    echo 1 > /sys/module/pm_8x60/modes/cpu2/standalone_power_collapse/suspend_enabled 

    echo 1 > /sys/module/pm_8x60/modes/cpu3/standalone_power_collapse/suspend_enabled

    echo 1 > /sys/module/pm_8x60/modes/cpu0/standalone_power_collapse/idle_enabled 

    echo 1 > /sys/module/pm_8x60/modes/cpu1/standalone_power_collapse/idle_enabled 

    echo 1 > /sys/module/pm_8x60/modes/cpu2/standalone_power_collapse/idle_enabled 

    echo 1 > /sys/module/pm_8x60/modes/cpu3/standalone_power_collapse/idle_enabled 

    echo 0 to above sys node will disable related low power mode. 

     

    2.12 Check system alarm  

    get Android alarms and statistics: 

    adb dumpsys alarm > alarms.txt

     

    enable android debug message in logcat: 

    setprop persist.alarm.debug 1

     

    2.13 Kernel timer check

    Sys node /proc/timer_stats can be used to check kernel timer stastics, customer can use following command to get timer statics in specific scenario: 

    echo 0 > /proc/timer_stats && sleep 10 && echo 1 > /proc/timer_stats && sleep 30 && cat /proc/timer_stats > /data/timer_stats & 

     

    OEMs need to provide file /data/timer_stats to salesforce case for check. 

     

     

    3、其他功耗项的优化

    3.1屏幕对功耗的影响

    屏幕亮度等级不同,功耗不同。亮度越低,功耗越低。调低屏幕默认背光亮度等级和屏幕最高亮度设置时候的背光亮度等级,可以优化手机整体功耗表现。

    LCD背光等级的设备节点:

    /sys/class/leds/lcd-backlight/brightness

    默认背光等级和最高亮度背光等级需要同时考虑到用户体验和功耗表现,需要一起评估。

    另外,调试LCDfps帧率,也可以优化功耗。

     

    3.2 CPU/GPU DVFS

    CPU/GPU的动态调频调压可以优化手机的功耗表现。该影响是整体性的,系统性的。

    CPU降频主要通过两种方式实现,都可以达到降频的目标。

    1、设置CPU工作在powersave模式。设置该模式后,CPU将一直工作在最低频率(300000hz)。此时手机最省电,但是有可能会出现手机运行变卡顿。

    例如:将CPU0置为powersave模式,命令为:

    echo "powersave" > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

    例如:将CPU1置为powersave模式,命令为:

    echo "powersave" > /sys/devices/system/cpu/cpu1/cpufreq/scaling_governor

    ex780共有4CPUCPU0~CPU3),都可以这样处理

    2、限制CPU最高频率,以限制CPU的运行频率上限

    CPUCPU0~CPU3)可以选择的频率值如下所列,即这些数值都可以用作CPU的频率上限。选择的频率上限可以根据实际场景需要来设置。在超级省电模式下,CPU工作的宗旨是:CPU工作频率低+运行不卡,两项都要保障。

    CPU可以选择的频率:

    300000 422400 652800 729600 883200 960000 1036800 1190400 1267200 1497600 1574400 1728000 1958400 2265600 2457600

    例如:将CPU0的频率上限设置为960000

    echo 960000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq

    例如:将CPU0的频率上限设置为422400

    echo 422400 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq

     

    GPU相关调试与CPU类似,设备节点路径/sys/devices/fdb00000.qcom,kgsl-3d0/kgsl/kgsl-3d0

     

    3.3 CPU占用率

    应用对cpu的占有率,如果占有率过高,则该应用一般会导致功耗较大。

    adb shell

    top -m 6 

     

    3.4游戏功耗

    可以从下面几个方面优化:

    降低屏幕背光亮度等级;

    采用CPUGPU动态调频调压,并调低CPUGPU频率下限;

    采用thermal-engine.conf 

     

    3.5 Camera功耗偏大

    降低camera帧率;

    降低屏幕背光亮度等级;

    采用CPUGPU动态调频调压,并调低CPUGPU频率下限;

    采用thermal-engine.conf 

    展开全文
  • Android最佳实践之性能 - 电池续航时间优化Doze和App Standby的优化(API23)参考地址:http://developer.android.com/training/monitoring-device-state/doze-standby.html 从Android 6.0 (API level 23)开始,...

    Android最佳实践之性能 - 电池续航时间优化

    Doze和App Standby的优化(API23)

    参考地址:http://developer.android.com/training/monitoring-device-state/doze-standby.html 
    从Android 6.0 (API level 23)开始,Android提供了两个节电功能用来增加电池的续航时间。Doze 可以在设备长时间不使用时,通过延迟后台CPU和网络的活动来减少电池的消耗;App Standby将延迟没有交互的app网络活动。 
    Doze和App Standby在Android6.0以上的版本上管理所有App的行为。为保证最好的用户体验,需要在Doze 和App Standby 模式下测试你的app并适当调整你的代码。

    理解Doze

    如果用户离开设备一段时间,没有插上电源,屏幕会关闭,设备会进入Doze模式。在这种模式下,系统会限制app的网络和CPU的服务来保存电量。系统也会组织app访问网络,延迟它要做的任务、同步工作以及标准闹钟。 
    系统会定期的退出Doze模式一小会儿,让app完成他们的延迟活动。在这个窗口期(Maintenance Window),系统运行所有的同步工作、任务以及闹钟,允许app访问网络。 
    doze.png
    在每个Maintenance Window结束后,系统会再次进入Doze模式,挂起网络操作等。随着时间的推移,这个窗口期会出现的越来越不频繁,这样帮助设备省电。

    Doze的限制

    • 网络访问挂起
    • 系统忽略唤醒锁(wake locks.)
    • 标准的AlarmManager闹钟被推迟到下一次Maintenance Window运行
    • 系统不会进行WIFI扫描
    • 系统不允许sync adapters运行
    • 系统不允许JobScheduler工作

    适配你的app到Doze模式

    Doze特别会影响闹钟和定时器,因为在5.1(APi22)及以下版本中,闹钟在Doze模式下不会启动。 
    在 Android 6.0 (API level 23) 中,提供了2个新的AlarmManager方法,setAndAllowWhileIdle()setExactAndAllowWhileIdle()。这样在Doze模式下,你也可以设置闹钟执行(每个App每次可以执行15分钟)。 
    为确保你的应用在Doze模式下如何表现,你可以使用adb命令迫使系统进入和退出Doze模式,观察你的应用程序的行为。

    理解App Standby

    App Standby 允许系统决定app在用户没使用它时的空闲状态。当用户不触摸app一段时间且下面条件都没有时系统来做这个决定:

    • 用户显示的启动app
    • app在前台有一个任务(一个Activity或一个前台的service,或被调用的Activity或前台service)
    • app在锁屏界面或通知栏生成一个notification 
      当设备充电时,系统将app从Standby状态中释放掉,允许它自由访问网络,执行任意的任务和同步操作。如果设备长时间空闲,系统将允许空闲的app一天访问一次网络。

    当app空闲时允许GCM来交互

    Google Cloud Messaging (GCM),是一个推送服务。GCM通过high-priority GCM messages在Doze和App Standby模式下进行了优化。你的app可以使用它们高效的最低耗电的在系统和设备间进行交互。 
    作为最佳实践,如果你需要使用即时消息,你应该使用GCM。如果你的服务器和客户端已经使用GCM,确保你的服务使用high-priority messages,因为这个可以可靠的唤醒app即使设备在Doze模式下。

    对其它使用场景的支持

    几乎所有的app都可以支持Doze,但也有一些用户场景还不能满足。在这些场景内,系统提供一个可配置的白名单来让某些app免除 Doze和App Standby优化。 
    在白名单中的app可以在 Doze和App Standby模式下使用网络和唤醒锁。然而,白名单的app也有受限,和其它app一样。例如,白名单的app的job和同步任务被延迟了,和闹钟一样被限制了。app可以通过调用isIgnoringBatteryOptimizations()来检查自己是否在白名单中。 
    用户可以在 Settings > Battery > Battery Optimization中手动配置白名单,系统也提供了方法让app去要求用户允许加入白名单。

    • 启动ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS Intent,进入Battery Optimization界面,那里可以添加白名单
    • 加了REQUEST_IGNORE_BATTERY_OPTIMIZATIONS权限的app可以直接弹出一个系统对话框让用户直接添加app到白名单中而不用进入设置界面。使用**ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS**Intent来触发这个Dialog。
    • 用户可以手动从白名单中移除app

    注意:Google Play政策禁止app直接免除Android 6.0+(Doze和App Standby模式)电量管理特性的,除非app的核心功能受到不利影响。

    测试Doze和App Standby模式

    为确保好的用户体验,上线app前需进行Doze和App Standby模式的完整测试:

    Doze测试

    1、配置一个Android6.0+的硬件设备或虚拟机 
    2、连接设备到开发机,安装app 
    3、运行app并保持活跃 
    4、关闭设备屏幕(app时活动的) 
    5、强制使系统循环进入Doze模式:

    $ adb shell dumpsys battery unplug
    $ adb shell dumpsys deviceidle step
    • 第二行命令可能不止执行一次,直至设备状态到空闲。 

    6、观察你的app重新激活后的行为,确保app在退出Doze模式后优雅的激活。

    测试App Standby模式

    1、配置一个Android6.0+的硬件设备或虚拟机 
    2、连接设备到开发机,安装app 
    3、运行app并保持活跃 
    4、强制使系统循环进入App Standby模式:

    $ adb shell dumpsys battery unplug
    $ adb shell am set-inactive <packageName> true
    • 5、模拟唤醒app:
    $ adb shell am set-inactive <packageName> false
    $ adb shell am get-inactive <packageName>
    • 6、唤醒app后观察app的行为。确保app从standby模式优雅的恢复过来。 

    特别的,你需要检查你的app的通知和后台工作是否继续按预期运行。

    可接受的白名单用户场景

    下面列表中高亮的是可接受的白名单场景:

    Type用户场景是否可以用GCM白名单是否接受备注
    即时消息、聊天或调用应用程序在Doze或App Standby模式下,需要传递即时消息是的,可用GCM不接受需要high-priority messages 唤醒app和访问网络
    即时消息、聊天或调用应用程序企业VOIP apps同上否,不能用GCM接受 
    任务自动化的appapp核心的功能就是即时消息、语音呼叫、照片管理或地图定位如果适用的话接受 

    监测电池电量和充电状态

    原文地址:http://developer.android.com/training/monitoring-device-state/battery-monitoring.html 
    执行应用程序更新对电量的影响取决于电池和充电状态的电量值。设备充电时执行更新的影响可以忽略不计,所以在大多数情况下当设备被连接一个充电器时你可以最大化你的刷新频率。相反,如果设备没有充电,减少你的更新频率有助于延长电池续航时间。 
    同样,你可以检测电池的电量,在电量几乎用光时减少甚至停止程序的运行。

    获取当前的充电状态

    BatteryManager通过一个sticky的Intent广播了所有的有关电池和充电状态的信息。 
    因为是sticky的Intent,所以不需要注册BroadcastReceiver,简单的传null到registerReceiver中即可,然后电池状态就返回了。这里也可以传一个实际的BroadcastReceiver对象,但这里不是必须的,后面会有讲解。

    IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    Intent batteryStatus = context.registerReceiver(null, ifilter);
    • 1
    • 2

    你可以提取当前的充电状态,如果设备在充电,还可以知道是通过USB或AC充电器来充电。

    // Are we charging / charged?
    int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
    boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                         status == BatteryManager.BATTERY_STATUS_FULL;
    
    // How are we charging?
    int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
    boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
    boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
    • 通常,在交流电(AC)充电情况下,你应该马力全开的执行程序,而在USB充电时,减少频率,在不充电的 情况下降低更低的频率来执行程序更新。

    监控充电状态的变化

    插拔设备的数据线很容易的引起充电状态的变化,监视充电状态的变化时很重要的,它决定了程序的刷新频率。 
    BatteryManager广播了一个action,告知我们设备何时连上或断开了电源。我们注册一个BroadcastReceiver,通过定义 ACTION_POWER_CONNECTED 和 ACTION_POWER_DISCONNECTED的IntentFilter来监听:

    <receiver android:name=".PowerConnectionReceiver">
      <intent-filter>
        <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
        <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
      </intent-filter>
    </receiver>
    • 在广播中提取充电状态:
    public class PowerConnectionReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
            boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                                status == BatteryManager.BATTERY_STATUS_FULL;
    
            int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
            boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
            boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
        }
    }

    • 获取当前电量
    int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
    int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
    
    float batteryPct = level / (float)scale;
    • 监测电量的显著变化

    你不能很荣易地持续监控电池状态,而且你也不需要。 
    一般来说,持续监控电量对电池的影响大于app本身行为对电量的影响,所以最好的实践是仅仅监控电量显著的变化-如设备进入或离开低电量时。 
    下面这段manifest片段,当设备电量很低或离开低电量时广播被触发,它监听ACTION_BATTERY_LOWACTION_BATTERY_OKAY

    <receiver android:name=".BatteryLevelReceiver">
    <intent-filter>
      <action android:name="android.intent.action.ACTION_BATTERY_LOW"/>
      <action android:name="android.intent.action.ACTION_BATTERY_OKAY"/>
      </intent-filter>
    </receiver>

    一般在电池电量非常低时最好禁用所有你的后台更新操作,因为不管你的数据多新,在你使用它之前手机就关闭屏幕了。 
    在很多情况下,给设备充电和将它放到dock模式是一致的。

    确定和监控Docking(底座)状态和类型

    原文地址:http://developer.android.com/training/monitoring-device-state/docking-monitoring.html#CurrentDockState 
    Android设备可以放到到几种不同类型的dock(底座)中。包括car dock、home dock以及Digital和Analog的dock。dock状态和充电状态紧密相连因为很多的底座为dock状态下的设备提供电量。 
    手机的dock状态如何影响你的更新频率取决于你的app。你可以选择在desktop dock增加体育中心app的更新频率,或者如果设备在car dock模式下完全禁用你的更新。相反,在car dock模式下如果你的后台服务需要更新交通状况你可以选择来最大化你的更新频率。 
    dock状态也是由一个sticky的intent来广播的,它允许你查询设备是否在dock模式,如果是,是哪一种dock。

    确定当前的dock状态

    dock状态在sticky广播的ACTION_DOCK_EVENT的action中。因为是sticky,不需要注册一个广播:

    IntentFilter ifilter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
    Intent dockStatus = context.registerReceiver(null, ifilter);

    可以从EXTRA_DOCK_STATE 中提取当前的dock状态:

    int dockState = battery.getIntExtra(EXTRA_DOCK_STATE, -1);
    boolean isDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;

    确定当前Dock类型

    有四种Dock类型:

    • Car:车载底座
    • Desk:桌面底座
    • Low-End (Analog) Desk:模拟底座
    • High-End (Digital) Desk:数字底座 
      后两个类型在Android in API level 11中才有。所以最好的实践就是检查是否在3种desk的类型之中,而不用关心具体是哪一种:
    boolean isCar = dockState == EXTRA_DOCK_STATE_CAR;
    boolean isDesk = dockState == EXTRA_DOCK_STATE_DESK ||
                     dockState == EXTRA_DOCK_STATE_LE_DESK ||
                     dockState == EXTRA_DOCK_STATE_HE_DESK;
    • 监控Dock的状态和类型变化

    设备不管何时dock或undock,都会触发ACTION_DOCK_EVENT的广播。需要监听这个广播只需要在manifest中注册,然后加对应的action即可:

    <action android:name="android.intent.action.ACTION_DOCK_EVENT"/>

    确定和监控连接状态

    原文地址:http://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html 
    闹钟和后台服务最常见的用途是定期从服务器更新数据到App,并缓存数据,或执行长时间的下载。但是如果你没有连接到互联网,或你的下载连接太慢了,你都要唤醒设备来进行更新吗? 
    使用ConnectivityManager来检测是否连上网络,如果连上可以知道连接的是哪种网络。

    检测是否连上网络

    如果没有连上网络,就不需要进行任何的更新操作了。下面的代码判断设备是否连上网络:

    ConnectivityManager cm =
            (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
    
    NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
    boolean isConnected = activeNetwork != null &&
                          activeNetwork.isConnectedOrConnecting();
    • 判断连接的网络类型
    boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;
    • 移动网络数据的开销比WI-FI要大,所以大多数情况下,你的设备需要在WI-FI情况下进行数据更新。 

    没有网络时需要禁用网络更新,一旦建立网络连接,可以恢复更新操作。

    监控网络连接的变化

    当网络连接发生变化时ConnectivityManager广播了一个 CONNECTIVITY_ACTION (“android.net.conn.CONNECTIVITY_CHANGE”) 的action,你可以在manifest注册一个broadcast receiver来监听这个action。

    <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
    • 设备的网络连接状态可能会变化的很频繁,当每次设备网络从移动蜂窝数据切换到WI-FI时就会被触发。因此,最好的实践就是仅当你要将之前暂停的更新或下载恢复时才监听这个广播。这个一般就够了,在进行一个更新之前,先检查是否有网络连接;然后没有网络连接,挂起更新操作直到网络恢复。

    按需操作Broadcast Receivers

    原文地址:http://developer.android.com/training/monitoring-device-state/manifest-receivers.html#ToggleReceivers 
    监控设备状态变化最简单的方法是在Manifest创建一个BroadcastReceiver。这个方式有一个影响就是app每次都需要唤醒设备来触发这些广播,可能会很频繁。 
    一个更好的方式是在运行时禁用和使用BroadcastReceiver,这样只在需要的时候被动接受系统触发的广播。

    切换Receivers可用状态来提高效率

    我们可以使用PackageManager来切换在Manifest注册的包括broadcast receiver的任意组件。

    ComponentName receiver = new ComponentName(context, myReceiver.class);
    
    PackageManager pm = context.getPackageManager();
    
    pm.setComponentEnabledSetting(receiver,
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP)
    • 使用这个技术,如果你确定网络连接已经丢失,你可以禁用除连接状态改变receiver之外的所有的receiver。相反,一旦网络连接上,你可以停止监听连接状态的receiver,然后执行更新操作前简单检查下是否连上网络。

    展开全文
  • 摘要:在手机终端领域,由于...本文研究基于android系统手机终端的功耗优化技术,通过分析研究组件功耗模型、组件状态模型、系统功耗优化、组件功耗优化等技术,提升手机终端的续航表现。首先,分析国内外的研究现状...
  • 摘要:在信息技术快速发展的今天,随着集成电路技术和移动互联网技术的快速发展,基于Android...因此,优化基于Android嵌入式系统多核处理器智能手机的功耗,提高待机时间成为一个研究热点问题.本文主要通过优化Andr...
  • https://blog.csdn.net/zhangbijun1230/article/details/80164118 https://blog.csdn.net/zhangbijun1230/article/details/80741113 https://blog.csdn.net/DKBDKBDKB/article/details/84616521
  • 功耗和温升通常是Android系统的硬伤,尤其是结构空间有限的Android系统设备,比如用Android系统开发的手表设备。结构有限意味着能放的电池容量不会很大,导致待机时间变得特别短。 而且通常这种手表设备的在原始...
  • Android 功耗优化

    2019-11-07 19:45:03
    功耗优化- ...android 功耗优化- https://blog.csdn.net/zhangbijun1230/article/category/7618452 MTK 驱动- https://blog.csdn.net/zhangbijun1230/article/cat...
  • 可以采用功耗问题时间追踪表来精确追踪功耗异常。 可以使用如下命令来打开指定文件的kernel log(以qpnp-adc-tm.c和qpnp-adc-common.c为例): adb shell mount -t debugfs none /sys/kernel/debug adb shell ...
  • 高通android平台功耗优化方法

    千次阅读 2017-08-18 14:25:37
    Android :/data/busybox # ./busybox devmem 0x100B000 32  ./busybox devmem 0x100B000 32  0x00000203  GPIO_PULL = "11" PULL_UP  FUNC_SEL = "0000" FUNCTION GPIO  ...
  • battery-historian V2.0的数据获取及参数分析单来说,Battery historian是一款... 参照https://www.2cto.com/kf/201607/528696.html的介绍:Android系统想要成为一个功能完备,生态繁荣的操作系统,那就必须提供...
  • 摘要:Android操作系统提供了唤醒锁机制用于避免系统进入休眠状态.但若存在唤醒锁的误用,将导致...结合具体实机测试数据,本文提出的检测机制及功耗优化方法,可有效降低功耗,提高用户体验.;The Android PowerManager...
  • Android操作系统提供了唤醒锁机制用于避免系统进入休眠状态.但若存在唤醒锁的误用,将导致设备能耗的加剧并严重影响用户...结合具体实机测试数据,本文提出的检测机制及功耗优化方法,可有效降低功耗,提高用户体验.
  • 功耗,指的是终端各个应用在使用过程的功率消耗情况,检测各个功能模块的平均耗电情况,是否存在漏电,提高平台的待机时间,提高用户体验,帮助开发改善相应的应用耗电情况。51Testing软件测试网A5X1R0u!C}f;S*S...
  • 功耗优化:关于一些对功耗的检测及优化 内存优化:关于一些对内存的常见优化手段及检测工具 功耗优化 耗电原因 CPU:wakelocks 网络、无线、蓝牙等 屏幕 CPU 与网络等是属于 Background Process,若要优化,需要...
  • SPMSPM以及时钟请求信号控制流程因为整个系统不只是AP(MCU),还包括modem、connectivity等子系统;CPU进入WFI后,整个系统就依靠一颗SCP:SPM来控制睡眠/唤醒的流程,它会去关注各个子系统的状态 SPM =System ...
  • android 功耗 高通平台   分类:功耗 1、底电流调试(Rock Bottom Current Optimization) 底电流在手机飞行模式下调试。每个平台的底电流数据可能不一样,具体可
  • 可以采用功耗问题时间追踪表来精确追踪功耗异常。 可以使用如下命令来打开指定文件的kernel log(以qpnp-adc-tm.c和qpnp-adc-common.c为例): adb shell mount -t debugfs none /sys/kernel/debug ...
  • Android后台调度与省电I.... AlarmManager:利用系统层级的闹钟服务(持有Wake Lock)。1. 使用场景在大概的时间间隔(重复)运行指定任务。在精确的时间间隔(重复)运行指定任务。需要精确的定时(重复)任务,如闹钟...
  • android功耗优化(2)--对齐唤醒

    千次阅读 2018-03-27 11:48:20
    概况Android手机上安装的很多应用都会频繁唤醒手机(唤醒系统、唤醒屏幕),造成手机耗电等现象。良好的对齐唤醒管理方案,就是对后台应用待机时不频繁唤醒,智能节省电量。实现原理:APK作为该功能的入口,勾选应用...
  • Android 系统性能优化,最近几年 ,Google 之外,绝对是中国的手机厂商参与得最深最广。不光深挖系统、场景,还要和 Top 应用合作优化,和各种流氓应用勾心斗角。同时 Google 也在不断吸收一些 common 的优化经验,...
  • android 7.0低电耗Doze模式从 Android 6.0(API 级别 23)开始,Android 引入了两个省电功能,可通过管理应用在设备未连接至电源时的行为方式为用户延长电池寿命。 低电耗(Doze)模式通过在设备长时间处于闲置状态时...
  • Android O 的Doze模式白名单路径Doze 模式列表上述备注规则如下if(powerWhitelist.isSysWhitelisted(pkg)) { // Summary of app which doesn't have a battery optimization setting show:Battery optimization ...
  • Android功耗分析

    2019-09-04 14:25:39
    Andorid功耗分析(2)高通平台功耗优化基础 Android功耗分析(3)高通功耗问题分析 Android功耗分析(4)MTK平台待机功耗分析流程 Android功耗分析(5)功耗调试方法 Android功耗分析(6)整机功耗测试 Android功耗...
  • Android 性能优化1、结合以下四个部分讲解:性能问题分类性能优化原则和方法借助性能优化工具分析解决问题性能优化指标 2性能问题分类1、渲染问题:过度绘制、布局冗杂2、内存问题:内存浪费(内存管理)、内存泄漏...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,929
精华内容 1,971
热门标签
关键字:

android系统功耗优化