Android 进阶——性能优化之电量优化全攻略及实战小结(二)

news2024/11/24 18:41:02

文章大纲

  • 引言
  • 一、在低电耗模式和应用待机模式下进行测试
    • 1、在低电耗模式下测试您的应用
    • 2、在应用待机模式下测试您的应用
    • 3、列入白名单的可接受用例
    • 4、确定当前充电状态
    • 5、监控充电状态变化
    • 6、确定当前电池电量
    • 7、监控显著的电池电量变化
  • 二、Wakelock 机制
    • 1、WakeLock分类
    • 2、申请WakeLock锁
    • 3、获取WakeLock对象
    • 4、Wake Lock 申请流程
        • 4.1、setWakeLockDisabledStateLocked()
        • 4.2、applyWakeLockFlagsOnAcquireLocked()处理亮屏标记
        • 4.3、updatePowerStateLocked()更新全局状态
        • 4.4、updateSuspendBlockerLocked()更新SuspendBlocker
        • 4.5、SuspendBlocker#acquire()申请SuspendBlocker
    • 5、WakeLock的释放
    • 6、小结
  • 三、AlarmManager
  • 四、采集电量相关日志报告
    • 1、通过`adb dumpsys batterystats --reset`命令重置
    • 2、再执行`adb dumpsys batterystats --enable full-wake-history` 打开完整唤醒日志
    • 3、断开设备与电脑的连接,以便只从设备的电池中消耗电流,模拟场景并采集数据
    • 4、不充电状态下获取完整的bugreport信息
  • 五、Battery Historian
    • 1、Battery Historian 常见条目解析
    • 2、Battery Historian 主要功能介绍
      • 2.1、Add Metrics
      • 2.2、System Stats
      • 2.3、App Stats

引言

电池续航时间是移动用户体验中最重要的一个方面。没电的设备完全无法使用。因此,对于应用来说,尽可能地考虑电池续航时间是至关重要的。为使应用保持节能,前一篇Android 进阶——性能优化之电量优化全攻略及实战小结(一)对于一些概念进行了总结。

一、在低电耗模式和应用待机模式下进行测试

为确保用户获得良好的体验,您应在低电耗模式和应用待机模式下全面测试您的应用。

1、在低电耗模式下测试您的应用

您可以按以下步骤在低电耗模式下测试您的应用:

  1. 使用 Android 6.0(API 级别 23)或更高版本的系统映像配置硬件设备或虚拟设备。

  2. 将设备连接到开发计算机并安装您的应用。

  3. 运行您的应用并使其保持活动状态。

  4. 运行以下命令,强制系统进入闲置模式:

        $ adb shell dumpsys deviceidle force-idle
    
  5. 准备就绪后,运行以下命令,使系统退出闲置模式:

        $ adb shell dumpsys deviceidle unforce
    
  6. 执行以下命令,重新激活设备:

        $ adb shell dumpsys battery reset
    
  7. 在重新激活设备后观察应用的行为。确保应用在设备退出低电耗模式时正常恢复。

2、在应用待机模式下测试您的应用

如需在应用待机模式下测试您的应用,请执行以下操作:

  1. 使用 Android 6.0(API 级别 23)或更高版本的系统映像配置硬件设备或虚拟设备。

  2. 将设备连接到开发计算机并安装您的应用。

  3. 运行您的应用并使其保持活动状态。

  4. 运行以下命令,强制应用进入应用待机模式:

    $ adb shell dumpsys battery unplug
        $ adb shell am set-inactive <packageName> true
    
  5. 使用以下命令模拟唤醒您的应用:

    $ adb shell am set-inactive <packageName> false
        $ adb shell am get-inactive <packageName>
    
  6. 在唤醒应用后观察它的行为。确保应用从待机模式正常恢复。您应特别检查应用的通知和后台作业是否继续按预期运行。

3、列入白名单的可接受用例

在这里插入图片描述

下表重点介绍了请求将应用列入电池优化豁免白名单或应用目前在该白名单中的可接受用例。一般来说,除非低电耗模式或应用待机模式破坏了应用的核心功能,或者由于技术方面的原因而导致您的应用无法使用高优先级 FCM 消息,否则您的应用不应在白名单中。
在这里插入图片描述

4、确定当前充电状态

首先,确定当前充电状态。BatteryManager 会在一个包含充电状态的粘性 Intent 中广播所有电池和充电详情。

由于它是一个粘性 Intent,因此您并不需要如下一代码段中所示的那样通过简单地调用 registerReceiver 传入 null 作为接收器来注册 BroadcastReceiver,便可返回当前电池状态 Intent。您可以在此处传入实际 BroadcastReceiver 对象,但由于稍后我们将会处理更新,因此并不需要这样做。

KotlinJava

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

您可以提取当前充电状态,并且如果设备正在充电,则还可以提取设备是通过 USB 还是交流充电器进行充电。

KotlinJava

    // 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;

通常,如果设备连接到交流充电器,您应最大限度地提高后台更新的频率;如果设备是通过 USB 充电,则应降低更新频率;如果电池正在放电,则应进一步降低更新频率。

5、监控充电状态变化

就像设备可以轻松地插入电源,充电状态也很容易发生变化,因此必须监控充电状态的变化并相应地改变刷新频率。

每当设备连接或断开电源时,BatteryManager 都会广播一项操作。请务必接收这些事件,即便您的应用并未运行 - 尤其要考虑到这些事件可能会影响您启动应用以便发起后台更新的频率 - 因此您应在清单中注册一个 BroadcastReceiver,通过在一个 Intent 过滤器内定义 ACTION_POWER_CONNECTEDACTION_POWER_DISCONNECTED 来同时监听这两种事件。

<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>

6、确定当前电池电量

在某些情况下,确定当前电池电量也很有用处。您可以选择在电池电量低于某一水平时降低后台更新的频率。

您可以通过从电池状态 intent 提取当前电池电量和刻度来了解当前电池电量,如下所示:

KotlinJava

    int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
    int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

    float batteryPct = level * 100 / (float)scale;

7、监控显著的电池电量变化

您无法轻松地持续监控电池状态,您也不必如此。

一般而言,持续监控电池电量对电池的影响大于应用正常行为造成的影响,因此最好只监控显著的电池电量变化 - 特别是在设备进入或退出电量不足状态时。

以下清单代码段摘自某个广播接收器内的 Intent 过滤器元素。通过监听 ACTION_BATTERY_LOWACTION_BATTERY_OKAY,每当设备电池电量不足或退出不足状态时,便会触发该接收器。

    <receiver android:name=".BatteryLevelReceiver">
      <intent-filter>
        <action android:name="android.intent.action.BATTERY_LOW"/>
        <action android:name="android.intent.action.BATTERY_OKAY"/>
      </intent-filter>
    </receiver>

一般而言,建议您在电量极低时停用所有后台更新。如果手机自行关机,您就无法利用相关数据,数据的新鲜度也就无关紧要。在许多情况下,为设备充电与将设备插入基座是同一操作。下一课将为您介绍如何确定当前基座状态以及如何监控设备插接状态的变化。

二、Wakelock 机制

Android为了确保应用程序中关键代码的正确执行,提供了Wake Lock(一种锁的机制, 只要有人拿着这个锁,系统就无法进入休眠)可以被用户态程序和内核获得。使得应用程序有权限通过代码阻止AP进入休眠状态。WakeLock是Android中为应用层及框架层提供的用来保证CPU处于唤醒状态的一种锁机制。PMS中为应用及框架层其他组件提供了接口,进行WakeLock的申请和释放。应用在申请WakeLock时,需要在清单文件中配置android.Manifest.permission.WAKE_LOCK权限。如果没有锁了或者超时了, 内核就会启动休眠的那套机制来进入休眠。WakeLock阻止应用处理器(ApplicationProcessor)挂起,确保关键代码的运行,通过中断唤起应用处理器(ApplicationProcessor),可以阻止屏幕变暗。所有的WakeLock被释放后,系统会挂起。

1、WakeLock分类

根据作用时间,WakeLock可以分为永久锁和超时锁:

  • 永久锁:只要获取了WakeLock锁,必须显式进行释放,否则系统会一直持有该锁;
  • 超时锁:在到达给定时间后,自动释放WakeLock锁,其实现原理为方法内部维护了一个Handler进行。

根据释放原则,WakeLock可以分为计数锁和非计数锁:

  • 计数锁:一次申请必须对应一次释放;
  • 非计数锁:不管申请多少次,只需要一次就可以释放该WakeLock。

默认为计数锁。WakeLock机制从上到下架构如下:
在这里插入图片描述

WakeLock有三种表现形式:

  • PowerManger.WakeLock:PMS暴露给应用层和其他组件用来申请WakeLock的接口;
  • PowerManagerService.WakeLock: PowerManager.WakeLock在PMS中的表现形式;
  • SuspendBlocker: PowerManagerService.WakeLock在向底层节点操作时的表现形式。

2、申请WakeLock锁

// 获取PowerManager对象
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
// 创建WakeLock锁实例
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
// 申请WakeLock
wl.acquire(); 
// 释放WakeLock
wl.release();

在PowerManager中,共定义了以下七种WakeLock:

WakeLock说明
PARTIAL_WAKE_LOCK0x00000001,保证CPU处于唤醒状态,但屏幕和键盘灯有可能是关闭的。
PROXIMITY_SCREEN_OFF_WAKE_LOCK0x00000020,通过PSensor进行亮灭屏工作,PSensor检测到有物体靠近时关闭屏幕,远离时又亮屏
DOZE_WAKE_LOCK0x00000040,仅用于PMS唤醒状态为Doze时,进入Doze状态后,DreamMangerService会申请该锁,允许CPU挂起
DRAW_WAKE_LOCK0x00000080,仅用于PMS唤醒状态为Doze时,保证CPU处于运行状态,以进行Doze状态下屏幕的绘制,如AOD、防烧屏显示
FULL_WAKE_LOCK0x0000001a,保证屏幕、键盘灯都保持常亮状态,按Power键灭屏后,会忽略该锁
SCREEN_BRIGHT_WAKE_LOCK0x0000000a,保证屏幕一直保持常亮状态,按Power键灭屏后,会忽略该锁(不推荐)
SCREEN_DIM_WAKE_LOCK0x00000006,保持CPU运转,保证屏幕一直保持Dim状态但有可能是灰的,允许关闭键盘灯,按Power键灭屏后,会忽略该锁(不推荐)

还定义了三个Flag,可以在创建、申请及释放WakeLock时和以上几类搭配使用:

Flag说明
ACQUIRE_CAUSES_WAKEUP不会唤醒设备,强制屏幕马上高亮显示,键盘灯开启。有一个例外,如果有notification弹出的话,会唤醒设备。,不能和PARTIAL_WAKE_LOCK一起使用
ON_AFTER_RELEASE在释放有该Flag的WakeLock时,会稍微延长自动休眠时间一小会儿,但不能和PARTIAL_WAKE_LOCK一起使用,WakeLock被释放后,维持屏幕亮度一小段时间,减少WakeLock循环时的闪烁情况。
PROXIMITY_SCREEN_OFF_WAKE_LOCK在释放PROXIMITY_SCREEN_OFF_WAKE_LOCK锁时,不会立即解除PSensor监听,而是在PSensor上报远离后,才会亮屏并解除Psensor监听,仅用于释放PROXIMITY_SCREEN_OFF_WAKE_LOCK锁

3、获取WakeLock对象

PowerManager中提供了接口newWakeLock()来创建WakeLock对象:

// frameworks/base/core/java/android/os/PowerManager.java

public WakeLock newWakeLock(int levelAndFlags, String tag) {
    validateWakeLockParameters(levelAndFlags, tag);  // 校验Flag
    // 创建WakeLock对象
    return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName());
}

首先进行了参数的校验,然后调用WakeLock构造方法创建WakeLock对象:

// frameworks/base/core/java/android/os/PowerManager.java
WakeLock(int flags, String tag, String packageName) {
    mFlags = flags;                  //表示wakelock类型
    mTag = tag;                      //一个tag,一般为当前类名
    mPackageName = packageName;      //申请wakelock的应用包名
    mToken = new Binder();           //一个Binder标记
    mTraceName = "WakeLock (" + mTag + ")";
}

除以上几个属性之外,WakeLock中还有如下几个属性:

// frameworks/base/core/java/android/os/PowerManager.java

//表示内部计数
private int mInternalCount;
//表示外部计数
private int mExternalCount;
//表示是否是计数锁,默认true
private boolean mRefCounted = true;
//表示是否已经持有该锁
private boolean mHeld;
//表示和该wakelock相关联的工作源,这在一些服务获取wakelock时很有用,以便计算工作成本
private WorkSource mWorkSource;
//表示一个历史标签
private String mHistoryTag;

4、Wake Lock 申请流程

在这里插入图片描述

当创建好WakeLock对象以后,就可以申请WakeLock锁了。不管是永久锁还是超时锁,都是通过acquire()方法来申请:

mWakeLock.acquire();             //申请一个永久锁
mWakeLock.acquire(int timeout);  //申请一个超时锁,指定作用时间
复制代码

PowerManager#acquire()方法如下:

// frameworks/base/core/java/android/os/PowerManager.java

// 申请永久锁
public void acquire() {
    synchronized (mToken) {
        acquireLocked();
    }
}

// 申请超时锁
public void acquire(long timeout) {
    synchronized (mToken) {
        acquireLocked();
        //通过Handler设置一个延迟消息自动释放锁
        mHandler.postDelayed(mReleaser, timeout);
    }
}
复制代码

这两种申请方式完全一样,只不过如果是申请一个超时锁,会通过Handler发送一个延时消息,到达时间后去自动释放锁。继续看acquireLocked()方法:

// frameworks/base/core/java/android/os/PowerManager.java

private void acquireLocked() {
    // 计数器+1
    mInternalCount++;
    mExternalCount++;
    //如果是非计数锁或者内部计数值为1,即第一次申请该锁,才会真正去申请
    if (!mRefCounted || mInternalCount == 1) {
        // 移除释放超时锁的Msg
        mHandler.removeCallbacks(mReleaser);
        try {
            // 通过Binder进入PMS中
            mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
                    mHistoryTag);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        mHeld = true;  // 表示已持有该锁,申请成功
    }
}
复制代码

对同一个WakeLock每申请一次,属性值mInternalCount和mExternalCount都会+1,这两个值都用来表示引用计数,前者相对于PowerManager内部,后者则相对于用户操作,之所以有两个引用计数器,主要是为了针对超时锁的释放,如果一个超时锁在已自动释放的情况下,用户手动再释放一次,相当于释放两次。这种情况下由于mExternalCount的存在,就不会导致crash。

mRefCounted用来表示计数锁或非计数锁,默认为true(计数锁),可以通过setReferenceCount()来设置:

public void setReferenceCounted(boolean value) {
    synchronized (mToken) {
        mRefCounted = value;
    }
}
复制代码

mHeld表示是否已经持有锁,可以通过调用isHeld()来判断是已申请WakeLock。

以上逻辑都是在APP进程执行的,接下来通过mService进入到system_server,开始执行了PMS中的流程。直接来看PMS#acquireWakeLockInternal()方法:

// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
        WorkSource ws, String historyTag, int uid, int pid) {
    synchronized (mLock) {
        // 这是PMS中的WakeLock类
        WakeLock wakeLock;
        // 通过IBinder标记确认是否已申请该WakeLock
        int index = findWakeLockIndexLocked(lock);
        boolean notifyAcquire;
        // 说明已申请过该WakeLock,则更新下该WakeLock即可
        if (index >= 0) {
            wakeLock = mWakeLocks.get(index);
            if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
                notifyWakeLockChangingLocked(wakeLock, flags, tag, packageName,
                        uid, pid, ws, historyTag);
                wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid);
            }
            notifyAcquire = false;
        } else { // 说明没有申请过该WakeLock
            ......
            // 创建一个WakeLock
            wakeLock = new WakeLock(lock, flags, tag, packageName, ws, historyTag, uid, pid,
                    state);
            // .......
            // 添加到保存系统所有WakeLock的list中
            mWakeLocks.add(wakeLock);
            // 对于PowerManager.PARTIAL_WAKE_LOCK类型锁,省电机制Doze模式会对其进行disable处理
            setWakeLockDisabledStateLocked(wakeLock);
            notifyAcquire = true;
        }
        // 处理PowerManager.ACQUIRE_CAUSES_WAKEUP标记,带有此标记,进行亮屏处理
        applyWakeLockFlagsOnAcquireLocked(wakeLock, uid);
        mDirty |= DIRTY_WAKE_LOCKS;  // 设置DIRTY_WAKE_LOCKS标记位
        updatePowerStateLocked();  // 更新状态
        if (notifyAcquire) {
            // 将申请WakeLock动作通知其他组件
            notifyWakeLockAcquiredLocked(wakeLock);
        }
    }
}
复制代码

首先,通过传入的第一个参数IBinder进行查找WakeLock是否已经存在,若存在,则在原有的WakeLock上更新其属性值;若不存在,则创建一个WakeLock对象,同时将该WakeLock保存到List中,并将相关数据保存到UidState中。

4.1、setWakeLockDisabledStateLocked()

创建WakeLock实例后,接下来调用setWakeLockDisabledStateLocked()方法,这个方法会对PARTIAL_WAKE_LOCK类型的WakeLock进行disable。系统如果持有该类型锁,会导致CPU一直保持唤醒状态而无法休眠,因此在省电策略DeviceIdle模块中,会在某些特定状态下将该类型的锁进行diable:

// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

private boolean setWakeLockDisabledStateLocked(WakeLock wakeLock) {
    if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK)
            == PowerManager.PARTIAL_WAKE_LOCK) {
        boolean disabled = false;
        final int appid = UserHandle.getAppId(wakeLock.mOwnerUid);
        // 非系统进程
        if (appid >= Process.FIRST_APPLICATION_UID) {
            // Cached inactive processes are never allowed to hold wake locks.
            if (mConstants.NO_CACHED_WAKE_LOCKS) {
                // 强制进入suspend、对应uid进程没有处于active且进程adj大于PROCESS_STATE_RECEIVER
                disabled = mForceSuspendActive  // 强制进入suspend
                        || (!wakeLock.mUidState.mActive && wakeLock.mUidState.mProcState
                                != ActivityManager.PROCESS_STATE_NONEXISTENT &&
                        wakeLock.mUidState.mProcState > ActivityManager.PROCESS_STATE_RECEIVER);
            }
            if (mDeviceIdleMode) {  //处于idle状态时,将非白名单应用wakeLock 禁用
                final UidState state = wakeLock.mUidState;
                if (Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 &&
                        Arrays.binarySearch(mDeviceIdleTempWhitelist, appid) < 0 &&
                        state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT &&
                        state.mProcState >
                                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
                    disabled = true;
                }
            }
        }
        // 更新mDisabled属性
        if (wakeLock.mDisabled != disabled) {
            wakeLock.mDisabled = disabled;
            return true;
        }
    }
    return false;
}
复制代码

主要有三种情况下会禁用Partical WakeLock:

  • 强制进入suspend;
  • WakeLock所属进程不处于active状态,且进程adj大于PROCESS_STATE_RECEIVER;
  • DeviceIdle处于IDLE状态,且所属进程不在doze白名单中;

4.2、applyWakeLockFlagsOnAcquireLocked()处理亮屏标记

接下来调用applyWakeLockFlagsOnAcquireLocked()方法,对ACQUIRE_CAUSES_WAKEUP标记进行处理。如果WakeLock带有标志,并且WakeLock类型为FULL_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK、SCREEN_DIM_WAKE_LOCK这三种其中之一,则会点亮屏幕:

// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock, int uid) {
    // 如果持有ACQUIRE_CAUSES_WAKEUP标记,且为亮屏相关三类锁之一
    if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0
            && isScreenLock(wakeLock)) {
        ......
        // 亮屏流程
        wakeUpNoUpdateLocked(SystemClock.uptimeMillis(),
                PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag,
                opUid, opPackageName, opUid);
    }
}
复制代码

wakeUpNoUpdateLocked()方法是点亮屏幕的主要方法,会在后面部分分析。

4.3、updatePowerStateLocked()更新全局状态

这个方法在PowerManagerService模块(一) 启动流程和核心方法中进行了分析,其中涉及到WakeLock流程的有两个方法:updateWakeLockSummaryLocked()和updateSuspendBlockerLocked()方法,前者已经分析过了,用来将所有的WakeLock统计到mWakeLockSummary全局变量中,这里对后一个方法进行分析。

4.4、updateSuspendBlockerLocked()更新SuspendBlocker

在这个方法中,将会根据系统所有WakeLock的状态,获得一个SuspendBlocker锁:

// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

private void updateSuspendBlockerLocked() {
    // 是否因持有WakeLock锁而需要CPU保持唤醒
    final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0);
    // 是否因Display状态而需要CPU保持唤醒
    final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked();
    // 是否开启auto_suspend模式
    final boolean autoSuspend = !needDisplaySuspendBlocker;
    // 是否处于交互状态
    final boolean interactive = mDisplayPowerRequest.isBrightOrDim();

    // 如果持有Display SuspendBlocker,则关闭auto-suspend模式
    if (!autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) {
        setHalAutoSuspendModeLocked(false);
    }
    // 申请mWakeLockSuspendBlocker锁
    if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) {
        mWakeLockSuspendBlocker.acquire();
        mHoldingWakeLockSuspendBlocker = true;
    }
    // 申请mDisplaySuspendBlocker锁
    if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) {
        mDisplaySuspendBlocker.acquire();
        mHoldingDisplaySuspendBlocker = true;
    }
    // 设置交互状态
    if (mDecoupleHalInteractiveModeFromDisplayConfig) {
        if (interactive || mDisplayReady) {
            setHalInteractiveModeLocked(interactive);
        }
    }
    // 释放mWakeLockSuspendBlocker
    if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
        mWakeLockSuspendBlocker.release();
        mHoldingWakeLockSuspendBlocker = false;
    }
    // 释放mDisplaySuspendBlocker
    if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
        mDisplaySuspendBlocker.release();
        mHoldingDisplaySuspendBlocker = false;
    }
    // 开启auto_suspend模式
    if (autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) {
        setHalAutoSuspendModeLocked(true);
    }
}
复制代码

在创建PMS实例时,创建了两个SuspendBlocker对象:mWakeLockSuspendBlocker和mDisplaySuspendBlocker,在这个方法中则体现出了他们的作用。当系统当前持有WakeLock锁,通过mWakeLockSuspendBlocker使CPU保持唤醒,当屏幕亮屏时,则通过mDisplaySuspendBlocker使CPU保持唤醒。

再看看needDisplaySuspendBlockerLocked()方法,如果该方法返回true,表示需要mDisplaySuspendBlocker锁:

private boolean needDisplaySuspendBlockerLocked() {
    if (!mDisplayReady) {
        return true;
    }
    // 当请求policy为bright或dim时,需要对psensor灭屏场景进行区分
    if (mDisplayPowerRequest.isBrightOrDim()) {
        // 如果在使用psensor且psensor上报靠近事件且psensor灭屏后允许CPU休眠,则表示不需要mDisplaySuspendBlocker
        if (!mDisplayPowerRequest.useProximitySensor || !mProximityPositive
                || !mSuspendWhenScreenOffDueToProximityConfig) {
            return true;
        }
    }
    if (mScreenBrightnessBoostInProgress) {
        return true;
    }
    return false;
}
复制代码

4.5、SuspendBlocker#acquire()申请SuspendBlocker

接下来将目光聚焦到SuspendBlocker上,它是一个接口,并且只有acquire()release()两个方法,用来申请和释放SuspendBlocker。PMS.SuspendBlockerImpl实现了该接口,acquire()方法如下:

public void acquire() {
    synchronized (this) {
        // 引用计数+1
        mReferenceCount += 1;
        if (mReferenceCount == 1) {
            // 进入native层
            mNativeWrapper.nativeAcquireSuspendBlocker(mName);
        }
    }
}
复制代码

SuspendBlocker对象的申请操作也使用了引用计数方式,每申请一次,其引用计数+1,如果mReferenceCount > 1,不会进行锁的申请,只有当mReferenceCount为0时,才会真正执行申请锁操作,然后通过JNI进入了native层:

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

static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str());
}
复制代码

最终,在SystemSuspend中,将会在/sys/power/wake_lock节点中,写入SuspendBlocker的name,完成申请过程:

// system/hardware/interfaces/suspend/1.0/default/SystemSuspend.cpp

void SystemSuspend::incSuspendCounter(const string& name) {
    auto l = std::lock_guard(mCounterLock);
    if (mUseSuspendCounter) {
        mSuspendCounter++;
    } else {
        if (!WriteStringToFd(name, mWakeLockFd)) { 
        }
    }
}
复制代码

这个name就是在创建SuspendBlocker对象时传入的String类型参数,可以通过adb shell看看这个节点内容:

$ adb shell cat /sys/power/wake_lock
PowerManager.SuspendLockout PowerManagerService.Display
复制代码

整个申请流程可以看出,WakeLock的申请和释放锁,对应着SuspendBlocker的申请和释放锁。也可以说SuspendBlocker类是WakeLock锁功能的具体实现者。只要持有SuspendBlocker锁,都会使得CPU处于唤醒状态。

5、WakeLock的释放

当申请了WakeLock锁(更准确地说是PARTIAL_WAKELOCK类型的WakeLock锁)执行完相应的任务后,要及时对其进行释放,否则会导致CPU无法进入休眠状态,导致电量消耗过快。在释放WakeLock时,也有两种释放形式:

  • release():正常释放WakeLock;
  • release(int flag):传入一个标志值,以达到修改释放行为的目的。目前只支持一个Flag——RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY,这个值前面已经说过,主要和PSensor灭屏后的动作有关。release()方法如下:
// frameworks/base/core/java/android/os/PowerManager.java

public void release(int flags) {
    synchronized (mToken) {
        if (mInternalCount > 0) {
            // 计数-1
            mInternalCount--;
        }
        // 如果非超时锁,外部计数-1
        if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
            mExternalCount--;
        }
        if (!mRefCounted || mInternalCount == 0) {
            mHandler.removeCallbacks(mReleaser);
            if (mHeld) {
                try {
                    // 进入PMS中
                    mService.releaseWakeLock(mToken, flags);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
                mHeld = false;
            }
        }
        // 在没有申请锁时进行释放操作,直接抛出异常
        if (mRefCounted && mExternalCount < 0) {
            throw new RuntimeException("WakeLock under-locked " + mTag);
        }
    }
}
复制代码

对于计数锁的释放,每次都会对内部计数值mInternalCount减一,且当它减为0时,才会去执行PMS中的释放流程;对于非计数锁的释放,每次都会调用到PMS释放流程中。

进入PMS流程后,PMS#releaseWakeLock()在进行权限验证后,执行PMS#releaseWakeLockInternal()方法:

// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

private void releaseWakeLockInternal(IBinder lock, int flags) {
    synchronized (mLock) {
        //查找WakeLock是否存在,不存在直接返回
        int index = findWakeLockIndexLocked(lock);
        if (index < 0) {
            return;
        }
        WakeLock wakeLock = mWakeLocks.get(index);
        // 如果带有该Flag,它会在距离传感器上报远离事件后才会取消PSensor监听
        if ((flags & PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY) != 0) {
            //表示在点亮屏幕前需要等待PSensor返回远离值
            mRequestWaitForNegativeProximity = true;
        }

        //释放锁
        removeWakeLockLocked(wakeLock, index);
    }
}
复制代码

这里对RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY标记进行了处理。这个标记和PSensor的行为有关。

当应用申请了PROXIMITY_SCREEN_OFF_WAKE_LOCK类型WakeLock锁,PMS模块会请求DMS模块注册一个PSensor监听,根据PSensor的事件状态来进行亮灭屏,通话时贴耳灭屏就是这样实现的。如果在PSensor灭屏的场景下,带这个flag去释放WakeLock,那么即使该WakeLock已经释放,依然会保持PSensor的监听状态,直到PSensor上报远离事件后,才会解除PSensor监听。

然后将执行removeWakeLockLocked()方法进行进一步操作:

// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

private void removeWakeLockLocked(WakeLock wakeLock, int index) {
    mWakeLocks.remove(index);  // 从保存系统WakeLock列表中移除该WakeLock
    // 更新WakeLock所属的UidState中的相关wakeLock数据
    ......
    // 对带有ON_AFTER_RELEASE标志的wakelock进行处理
    applyWakeLockFlagsOnReleaseLocked(wakeLock);
    mDirty |= DIRTY_WAKE_LOCKS;  // 设置标记位
    // 更新PMS全局状态
    updatePowerStateLocked();
}
复制代码

首先,将该WakeLock从mWakeLocks列表中移除;

接下来,执行applyWakeLockFlagsOnReleaseLocked()方法,对带有ON_AFTER_RELEASE标志的WakeLock进行处理。在释放带有该标记的WakeLock时,会更新用户活动状态,从而当没有用户操作情况下,会延迟自动灭屏时间:

// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) {
    if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0
            && isScreenLock(wakeLock)) {
        // 更新用户活动时间,用于延长灭屏时间
        userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
                PowerManager.USER_ACTIVITY_EVENT_OTHER,
                PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
                wakeLock.mOwnerUid);
    }
}
复制代码

userActivityNoUpdateLocked()方法会更新用户活动时间,该方法的详细分析见Android R PowerManagerService模块(4) 灭屏流程。

最后,执行updatePowerStateLocked()方法,该方法中和WakeLock相关的方法已经在申请流程中说过,这里只看看释放相关代码:

// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

private void updateSuspendBlockerLocked() {
    ......
    // 释放mWakeLockSuspendBlocker
    if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
        mWakeLockSuspendBlocker.release();
        mHoldingWakeLockSuspendBlocker = false;
    }
    // 释放mDisplaySuspendBlocker
    if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
        mDisplaySuspendBlocker.release();
        mHoldingDisplaySuspendBlocker = false;
    }
}
复制代码

和申请流程一样,还是对mWakeLockSuspendBlocker和mDisplaySuspendBlocker进行释放:

// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java

public void release() {
    synchronized (this) {
        mReferenceCount -= 1;  // 引用计数-1
        if (mReferenceCount == 0) {
            // 进入native层
            mNativeWrapper.nativeReleaseSuspendBlocker(mName);
        } else if (mReferenceCount < 0) {
            // ......
        }
    }
}
复制代码

在释放时对引用计数-1,当mReferenceCount为0时,会进入native层进行最后的释放:

// hardware/libhardware_legacy/power.cpp

static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
    release_wake_lock(name.c_str());
}
复制代码

最终,也会在SystemSuspend中,在/sys/power/wake_unlock节点中写入SuspendBlocker的name,完成释放:

// system/hardware/interfaces/suspend/1.0/default/SystemSuspend.cpp

void SystemSuspend::decSuspendCounter(const string& name) {
    auto l = std::lock_guard(mCounterLock);
    if (mUseSuspendCounter) {
        if (--mSuspendCounter == 0) {
            mCounterCondVar.notify_one();
        }
    } else {
        if (!WriteStringToFd(name, mWakeUnlockFd)) {
            PLOG(ERROR) << "error writing " << name << " to " << kSysPowerWakeUnlock;
        }
    }
}
复制代码

至此,WakeLock释放流程执行完毕。

6、小结

WakeLock锁的申请和释放流程,实际上就是通过SuspendBlocker操作/sys/power/wake_lock和/sys/power/wake_unlock节点,来控制设备的唤醒和休眠。 SuspendBlocker官方的解释是:SuspendBlocker相当于持有部分唤醒锁,该接口在内部使用,以避免在高级别唤醒锁机制上引入内部依赖关系。

在PMS中,共创建了三个SuspendBlocker锁,其中两个已经见过了:

    1. mWakeLockSuspendBlocker锁:表示由WakeLock而使CPU保持唤醒状态,该锁的申请条件是mWakeLockSummary & WAKE_LOCK_CPU) != 0

mWakeLockSummary汇总了所有WakeLock锁,当申请了PARTIAL_WAKE_LOCK、DRAW_WAKE_LOCK锁,或者屏幕处于唤醒、屏保时,会给该属性设置WAKE_LOCK_CPU标记位,从而申请mWakeLockSuspendBlocker锁,向/sys/power/wake_lock节点中写入"PowerManager.WakeLocks",使CPU保持唤醒状态。

    1. mDisplaySuspendBlocker锁:表示由于屏幕亮屏而使CPU保持唤醒状态。只要屏幕处于亮屏状态时,就会申请mDisplaySuspendBlocker锁,向/sys/power/wake_lock中写入“PowerManagerService.Display”;
    1. PowerManagerService.Broadcasts锁:表示由于需要确保Notifier中广播的成功发送,而使CPU保持唤醒状态;

该锁在分析流程中还暂时没有碰到,它以构造方法的形式传给了Notifier类,PMS中和其他服务的部分交互通过Notifier进行,还有亮灭屏广播都是由PMS交给Notifier来发送。如果CPU在广播发送过程中进入休眠,则广播无法发送完成,因此,需要一个锁来保证Notifier中广播的成功发送,这就是PowerManagerService.Broadcasts锁的作用。当广播发送完毕后,该锁就立即释放。

API17 后不推荐使用WakeLock 保持屏幕常亮,其中SCREEN_DIM_WAKE_LOCK保证屏幕亮起,但是亮度可能比较丢,同时键盘背光也可以不亮,而SCREEN_BRIGHT_WAKE_LOCK保证屏幕全亮,同时键盘背光也亮,FULL_WAKE_LOCK表现和SCREEN_BRIGHT_WAKE_LOCK类似,区别在于这个等级WakeLock 使用的是推荐的WindowFlagWindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,在代码中使用
//在Activity中
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

//在布局中
添加android:keepScreenOn=“true”

三、AlarmManager

AlarmManage有一个AlarmManagerService,该服务程序主要维护app注册下来的各类Alarm,并且一直监听Alarm设备,一旦有Alarm触发,或者是Alarm事件发生,AlarmManagerService就会遍历Alarm列表,找到相应的注册Alarm并发出广播
AlarmManager会维持一个cpu的wakelock。这样能保证电话休眠时,也能处理alarm的广播。一旦alarmreceiver的onReceive()方法执行完,wakelock会迅速被释放。如果在receiver中开启一个service,有可能service还没启动,wakelock已经被释放了。所以此时要实现单独的wakelock策略。有4种Alarm类型:

  • RTC_WAKEUP:在指定的时刻(设置Alarm的时候),唤醒设备来触发Intent。
  • RTC:在一个显式的时间触发Intent,但不唤醒设备。
  • ELAPSED_REALTIME:从设备启动后,如果流逝的时间达到总时间,那么触发Intent,但不唤醒设备。流逝的时间包括设备睡眠的任何时间。注意一点的是,时间流逝的计算点是自从它最后一次启动算起。
  • ELAPSED_REALTIME_WAKEUP:从设备启动后,达到流逝的总时间后,如果需要将唤醒设备并触发Intent。

四、采集电量相关日志报告

通过软件进行电量测试,主要是结合adb dump batterystats 导出电量报告并利用Google的Battery Historian工具自动进行分析,首先是采集Batterystats 数据,连接手机,打开开发者模式,连接adb。

1、通过adb dumpsys batterystats --reset命令重置

因为设备始终在后台收集batterystats和其他调试信息, 重置会清除采集旧数据便于排除干扰。

2、再执行adb dumpsys batterystats --enable full-wake-history 打开完整唤醒日志

3、断开设备与电脑的连接,以便只从设备的电池中消耗电流,模拟场景并采集数据

然后使得设备不在充电状态,模拟执行一段时间后

4、不充电状态下获取完整的bugreport信息

重新链接设备,执行adb bugreport 导出电量日志。

在SDK 25后bugreport工具必须要在7.0 以上设备使用,旧版本需要下载SDK R20的platform-tools工具;Android <=6.0 时adb bugreport >xxx.txt

Android > 6.0 时 直接adb bugreport xxx.zip,导出完整的bugreport日志待用

五、Battery Historian

Batterystats 是包含在 Android 框架中的一种工具,用于收集设备上的电池数据。您可以使用 adb 将收集的电池数据转储到开发机器,并生成可使用 Battery Historian 分析的报告。Battery Historian 会将报告从 Batterystats 转换为可在浏览器中查看的 HTML 直观视图。适合的场景:

  • 显示进程从什么位置以及通过何种方式消耗电池电量。
  • 识别系统为了延长电池续航时间可能会延迟甚至移除应用中的哪些任务。
    在这里插入图片描述

1、Battery Historian 常见条目解析

条目说明
CPU RunningCPU的运行状态是否被唤醒,有条状柱子就说明被唤醒运行中
Kernel only uptime只有内核运行的情况,有柱子就说明符合该条目,下同
Screen屏幕亮屏的情况
Top app当前运行的App
JobScheduler异步作业JobScheduler 运行的情况
SyncManager同步操作SyncManager运行的情况
SensorSensor使用情况
Phone scanning
Phone call是否正在打电话
GPS是否正在使用GPS
Network Connectivity网络连接状态(wifi、mobile是否连接)
Activity Manager Proc活跃的用户进程
Mobile network type网络类型
Mobile radio active移动蜂窝信息,基带BP测耗电情况
Crashes(logcat)某个时间点出现Crash的应用
Doze是否处于Doze模式,从Android 6.0(API级别23)开始,该平台包含Doze功能,这对应用程序实施了某些优化,例如,无论 JobScheduler 的调度方式如何,低电耗模式都会将作业处理成在简短的维护期间批量进行。
Doze active与Doze模式相反
Temp White List电量优化白名单
Mobile signal strength移动信号强度(greate\good\modrate\poor)
Wifi scan是否在扫描Wifi信号
Wifi supplicant是否在处理Wifi请求
Wifi radio是否正在通过Wifi 传输数据
Wifi signal strengthWifi信号强度(greate\good\modrate\poor)
Wifi runningWifi组件是否在工作(但未传输数据)
Wifi onWifi组件是否在工作(但未传输数据)
Audio音频是否开启
CameraCamera是否正在工作
Video是否在播放视频
Package Install是否在安装包
Battery Level当前电量
Plugged是否在充电

2、Battery Historian 主要功能介绍

Battery Historian 在全系统级别以HTML表示形式从系统日志中查看相关的事件。 在特定应用程序级别,该工具提供了各种数据,比如可帮助您确定电池耗尽应用程序的行为。将鼠标悬停在信息图标上可查看有关每个指标的更多信息,包括图表中使用的颜色的关键字,将鼠标悬停在栏上,即可查看有关该指标的详细信息以及时间线上特定点的电池状态,

2.1、Add Metrics

从下拉列表中添加其他指标。

2.2、System Stats

System Stats选项卡包含系统范围的统计信息,如单元信号级别和屏幕亮度。 这些信息提供了设备发生情况的整体情况。 这对确保没有外部事件影响您的测试特别有用。另外Battery Historian图表下面的stats部分可以查看bugreport文件中的其他信息,还可以查询以下信息(不限于):

  • 该应用在设备上的预计用电量。
  • 网络信息。
  • Wakelocks。
  • 服务。
  • 进程信息。

在这里插入图片描述

这些表格提供了有关您的应用的两个维度数据。 首先,您可以查看您的应用的用电量排名与其他应用的相比。 为此,请单击表下的设备功率估算表。
在这里插入图片描述

2.3、App Stats

App Stats选项卡包含有关特定应用程序的信息。 使用左侧的应用程序选择窗格中(图3)排序应用程序下拉列表对应用程序列表进行排序。 您可以选择一个特定的应用程序来查看使用下面(图4)个应用程序下拉列表的统计信息。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/101240.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux系统x86-64架构下,从零实现一个系统调用。Ubuntu22.04LTS

名称版本OSUbuntu 22.04 LTSCurrent Kernel5.15.0-56-genericDestination Kernel5.16.60首先要会编译linux内核的源码,这块在我的另外一片文章里面。 https://blog.csdn.net/jl19861101/article/details/128327069 打开linux内核源码目录/arch/x86/entry/syscalls/syscall_64.t…

前端面试比较好的回答

介绍一下Connection:keep-alive 什么是keep-alive 我们知道HTTP协议采用“请求-应答”模式&#xff0c;当使用普通模式&#xff0c;即非KeepAlive模式时&#xff0c;每个请求/应答客户和服务器都要新建一个连接&#xff0c;完成 之后立即断开连接&#xff08;HTTP协议为无连接…

【笔记】canvas 绘制足球 —— 第一步 画个球体

文章目录一、球体分析二、足球结构分析三、canvas常用API四、画个球体1.初始化2.代码五、加上足球的皮肤一、球体分析 先上两张图 球坐标转直角坐标 xρsin(φ)cos(θ)x \rho \times sin(\varphi) \times cos(\theta) xρsin(φ)cos(θ) yρsin(φ)sin(θ)y \rho \times si…

柴油,光伏模块,风力涡轮机,电池和水力抽水蓄能组成的混合隔离微电网的设计(Matlab实现)

目录 0 引言 1 概述 2 HYMOD 软件操作 2.1 设计的三个阶段 3 HYMOD 软件架构 4 系统和元件的可靠性 5 微电网设计示例 6 Matlab代码与结论 7 操作指南 7.1 概述 7.2 操作 7.3 软件具体操作 0 引言 本文介绍了混合微电网优化设计 (Hymod) 软件。该软件具有最先进…

Redis发布和订阅

Redis发布和订阅1.什么是发布和订阅2.Redis命令演示发布订阅1.什么是发布和订阅 Redis发布订阅(pub/sub)是一种消息通信模式&#xff1a;发布者(pub)发送消息&#xff0c;订阅者(sub)接收消息。 Redis客户端可以订阅任意数量的频道。 2.Redis命令演示发布订阅 打开两个终端 终…

python教程二十 输入和输出

输出格式美化 Python两种输出值的方式: 表达式语句和 print() 函数。 第三种方式是使用文件对象的 write() 方法&#xff0c;标准输出文件可以用 sys.stdout 引用。 如果你希望输出的形式更加多样&#xff0c;可以使用 str.format() 函数来格式化输出值。 如果你希望将输出…

记录在苹果mac os系统上使用51单片机仿真软件Proteus

目录 1.安装Wineskin shell 指令 2.安装Wrapper 点击update ​​​​​​​ 1.安装Wineskin 首先我们需要安装一个程序&#xff1a; 可以将在Windows系统上才能运行exe文件打包为mac系统可执行的文件。 shell 指令 brew install --no-quarantine gcenx/wine/unofficial…

气体在线监测仪——排水管井内的有害气体监测

一、产品概述 气体在线监测仪内部采用模块化设计&#xff0c;可对雨污水管井内的有害气体进行在线监测&#xff0c;设备采用高精度、高分辨率的原装进口气体传感器&#xff0c;具有体积小、重量轻、设计简洁、高性价比、多参数高集成、安装方便等特点。 气体在线监测仪广泛应…

Java IO

目录 一、File 类 二、RandomAccessFile 三、流类 四、字节流 4.1 、InputStream 4.2、OutputStream 五、字符流 5.1、Reader 5.2、Writer 六、管道流 七、ByteArrayInputStream 和 ByteArrayOutputStream 八、System.out 和 System.in 九、打印流 十、DataOutp…

【Leetcode】单值二叉树、 相同的树、对称二叉树、另一颗树的子树、二叉树遍历、二叉树的前序遍历

文章目录OJ链接单值二叉树相同的树对称二叉树另一颗树的子树二叉树遍历二叉树的前序遍历OJ链接 1、【单值二叉树】OJ链接 2、【相同的树】OJ链接 3、【对称二叉树】OJ链接 4、【另一棵树的子树】OJ链接 5、【二叉树遍历】OJ链接 6、【二叉树的前序遍历】OJ链接 单值二叉树 >…

R语言用线性模型进行臭氧预测: 加权泊松回归,普通最小二乘,加权负二项式模型,多重插补缺失值

最近我们被客户要求撰写关于线性模型的研究报告&#xff0c;包括一些图形和统计输出。在这篇文章中&#xff0c;我将从一个基本的线性模型开始&#xff0c;然后尝试找到一个更合适的线性模型。 数据预处理 由于空气质量数据集包含一些缺失值&#xff0c;因此我们将在开始拟合…

driftingblues3靶机(auth.log日志、命令执行)

环境准备 靶机链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;yc07 虚拟机网络链接模式&#xff1a;桥接模式 攻击机系统&#xff1a;kali linux 2021.1 信息收集 1.探测目标靶机开放端口和服务情况 2.用dirsearch扫描出目录 dirsearch -u 192.168.1.101 漏洞…

【Python】ValueError: Grouper for ‘Code‘ not 1-dimensional

目录&#xff1a;ValueError: Grouper for Code not 1-dimensional解决一、问题描述二、问题分析2.1 构建的DataFrame两列列名一样2.2 情况2三、问题解决一、问题描述 在我进行pandas的groupby分组的时候&#xff0c;我们的数据集如下&#xff1a; 但是在分组时&#xff0c;出…

Oracle sql性能优化案例

v$sql 表字段说明&#xff1a; sql_id&#xff1a;唯一性标识&#xff1b; sql_fulltext&#xff1a;SQL执行内容&#xff1b; elapsed_time&#xff1a;消逝时间&#xff0c;即自然耗费的时间&#xff0c;单位是微妙&#xff0c;10的-6次方秒&#xff1b; cpu_time&#…

手机也可以轻松码代码!两款手机端代码最佳神器Pydroid和Pythonista!

Pyroid是一款支持Android系统的移动代码编译器。 Python 3可以说是Android上一个易于使用且功能强大的Python 3 IDE&#xff0c;它可以帮助您在Android上使用Python、Jupyter笔记本等。 安装 我们可以从应用程序商店下载并安装。安装完成后&#xff0c;需要在第一次打开Python…

我用python生成了一亿棵不同的圣诞树 | 使用Python代码自动生成圣诞树轮廓

圣诞将至&#xff0c;这次来试试用Python代码过圣诞节把~挑战生成一亿棵圣诞树。 文章目录前言一、为什么能生成一亿棵圣诞树&#xff1f;二、怎么根据圣诞树图片生成对应的圣诞树轮廓1.读取圣诞树图片2.二值化圣诞树图片3.提取圣诞树图片轮廓4.显示圣诞树轮廓总结前言 圣诞将…

数据结构作业——第十六周--排序

1 . 单选题 简单 5分 对整数序列&#xff08;8&#xff0c;9&#xff0c;10&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;20&#xff0c;1&#xff0c;2&#xff09;进行递增排序&#xff0c;采用每趟冒出一个最小元素的冒泡排序算法&#xff0c;需要进行的趟数是____…

重新定义“创新”,戴森以发明家精神引领科技突破

自创立以来&#xff0c;戴森坚持精益工程、寻求颠覆性解决方案&#xff0c;现已成为行业领先的全球科技公司。而在前沿产品背后&#xff0c;其创新理念、发明家精神为戴森一系列不可复制的核心科技和突破性产品奠定了基石。 2022年12月18日&#xff0c;第二届戴森科技节在深圳启…

【pyclipper+增材CAM】轮廓偏置

在增材打印CAM中&#xff0c;我们需要在切片得到的每层轮廓中规划生成打印路径。传统的三轴3D打印的常见填充方式有&#xff1a;轮廓平行填充和方向平行填充。其中轮廓平行填充主要是通过轮廓偏置实现的。 pyclipper安装使用 Python下安装pyclipper库&#xff0c;命令行输入p…

ZigBee环境配置与工程创建 -- IAR for 8051 8.10

IAR8.10版本的安装相对于10.30.1版本的安装要简单的过&#xff0c;同样是做ZigBee的裸机项目开发工具&#xff0c;10版本之前都是旧版的操作界面&#xff0c;如果后期运行协议栈的话可以适配Z-Stask2.5.1a版本 文章目录1. IAR环境安装2.IAR for 8051工程创建3.工程配置4.工程编…