在分析PowerUsageSummary的时候,其实可以发现主要获取应用和服务电量使用情况的实现是在BatteryStatsHelper.java

还是在线网站http://androidxref.com/上对Android版本6.0.1_r10源码进行分析

具体位置在 /frameworks/base/core/java/com/android/internal/os/BatteryStatsHelper.java

create方法

查看构造方法

public BatteryStatsHelper(Context context) {
this(context, true);
} public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) {
this(context, collectBatteryBroadcast, checkWifiOnly(context));
} public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) {
mContext = context;
mCollectBatteryBroadcast = collectBatteryBroadcast;
mWifiOnly = wifiOnly;
}

设置是否需要注册BATTERY_CHANGED驻留广播,该广播监听系统电池电量和充电状态

mCollectBatteryBroadcast = collectBatteryBroadcast;

设备是否只有wifi,无移动网络,比如说平板或者车机,有的就是不能插SIM卡的

mWifiOnly = wifiOnly;

查看create方法

public void create(BatteryStats stats) {
mPowerProfile = new PowerProfile(mContext);
mStats = stats;
} public void create(Bundle icicle) {
if (icicle != null) {
mStats = sStatsXfer;
mBatteryBroadcast = sBatteryBroadcastXfer;
}
mBatteryInfo = IBatteryStats.Stub.asInterface(
ServiceManager.getService(BatteryStats.SERVICE_NAME));
mPowerProfile = new PowerProfile(mContext);
}

其中都获取了PowerProfile对象

mPowerProfile = new PowerProfile(mContext);

PowerProfile创建

持续跟进

public PowerProfile(Context context) {
// Read the XML file for the given profile (normally only one per
// device)
if (sPowerMap.size() == 0) {
readPowerValuesFromXml(context);
}
initCpuClusters();
}

可以看到这里有一段注释: Read the XML file for the given profile (normally only one perdevice

跟进readPowerValuesFromXml方法,其实这个方法就是用来解析power_profile.xml文件的,该文件在源码中的位置为 /frameworks/base/core/res/res/xml/power_profile.xmlpower_profile.xml是一个可配置的功耗数据文件

private void readPowerValuesFromXml(Context context) {
int id = com.android.internal.R.xml.power_profile;
final Resources resources = context.getResources();
XmlResourceParser parser = resources.getXml(id);
boolean parsingArray = false;
ArrayList<Double> array = new ArrayList<Double>();
String arrayName = null; try {
// ....

在这里需要提一下Android中对于应用和硬件的耗电量计算方式:

有一张“价格表”,记录每种硬件1秒钟耗多少电。有一张“购物清单”,记录apk使用了哪几种硬件,每种硬件用了多长时间。假设某个应用累计使用了60秒的cpu,cpu1秒钟耗1mAh,那这个应用就消耗了60mAh的电

这里的价格表就是我们找到的power_profile.xml文件,手机的硬件是各不相同的,所以每一款手机都会有一张自己的"价格表",这张表的准确性由手机厂商负责。

这也是为什么我们碰到读取xml文件的时候注释里面会有normally only one perdevice

如果我们想要看自己手机的power_profile.xml文件咋办,它会存储在手机的/system/framework/framework-res.apk路径中,我们可以将它pull出来,通过反编译的手法获得power_profile.xml文件

refreshStats方法

接着可以看到重载的refreshStats

/**
* Refreshes the power usage list.
*/
public void refreshStats(int statsType, int asUser) {
SparseArray<UserHandle> users = new SparseArray<>(1);
users.put(asUser, new UserHandle(asUser));
refreshStats(statsType, users);
} /**
* Refreshes the power usage list.
*/
public void refreshStats(int statsType, List<UserHandle> asUsers) {
final int n = asUsers.size();
SparseArray<UserHandle> users = new SparseArray<>(n);
for (int i = 0; i < n; ++i) {
UserHandle userHandle = asUsers.get(i);
users.put(userHandle.getIdentifier(), userHandle);
}
refreshStats(statsType, users);
} /**
* Refreshes the power usage list.
*/
public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) {
refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000,
SystemClock.uptimeMillis() * 1000);
}

refreshStats是刷新电池使用数据的接口,向上提供数据,其中的具体实现在

public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
long rawUptimeUs) {
// Initialize mStats if necessary.
getStats(); mMaxPower = 0;
mMaxRealPower = 0;
mComputedPower = 0;
mTotalPower = 0; mUsageList.clear();
mWifiSippers.clear();
mBluetoothSippers.clear();
mUserSippers.clear();
mMobilemsppList.clear(); if (mStats == null) {
return;
} if (mCpuPowerCalculator == null) {
mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
}
mCpuPowerCalculator.reset(); if (mWakelockPowerCalculator == null) {
mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile);
}
mWakelockPowerCalculator.reset(); if (mMobileRadioPowerCalculator == null) {
mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats);
}
mMobileRadioPowerCalculator.reset(mStats); // checkHasWifiPowerReporting can change if we get energy data at a later point, so
// always check this field.
final boolean hasWifiPowerReporting = checkHasWifiPowerReporting(mStats, mPowerProfile);
if (mWifiPowerCalculator == null || hasWifiPowerReporting != mHasWifiPowerReporting) {
mWifiPowerCalculator = hasWifiPowerReporting ?
new WifiPowerCalculator(mPowerProfile) :
new WifiPowerEstimator(mPowerProfile);
mHasWifiPowerReporting = hasWifiPowerReporting;
}
mWifiPowerCalculator.reset(); final boolean hasBluetoothPowerReporting = checkHasBluetoothPowerReporting(mStats,
mPowerProfile);
if (mBluetoothPowerCalculator == null ||
hasBluetoothPowerReporting != mHasBluetoothPowerReporting) {
mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
mHasBluetoothPowerReporting = hasBluetoothPowerReporting;
}
mBluetoothPowerCalculator.reset(); if (mSensorPowerCalculator == null) {
mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile,
(SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE));
}
mSensorPowerCalculator.reset(); if (mCameraPowerCalculator == null) {
mCameraPowerCalculator = new CameraPowerCalculator(mPowerProfile);
}
mCameraPowerCalculator.reset(); if (mFlashlightPowerCalculator == null) {
mFlashlightPowerCalculator = new FlashlightPowerCalculator(mPowerProfile);
}
mFlashlightPowerCalculator.reset(); mStatsType = statsType;
mRawUptime = rawUptimeUs;
mRawRealtime = rawRealtimeUs;
mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs);
mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeUs);
mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeUs, mStatsType);
mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
mBatteryTimeRemaining = mStats.computeBatteryTimeRemaining(rawRealtimeUs);
mChargeTimeRemaining = mStats.computeChargeTimeRemaining(rawRealtimeUs); if (DEBUG) {
Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime="
+ (rawUptimeUs/1000));
Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime="
+ (mBatteryUptime/1000));
Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime="
+ (mTypeBatteryUptime/1000));
}
mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
* mPowerProfile.getBatteryCapacity()) / 100;
mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
* mPowerProfile.getBatteryCapacity()) / 100; processAppUsage(asUsers); // Before aggregating apps in to users, collect all apps to sort by their ms per packet.
for (int i=0; i<mUsageList.size(); i++) {
BatterySipper bs = mUsageList.get(i);
bs.computeMobilemspp();
if (bs.mobilemspp != 0) {
mMobilemsppList.add(bs);
}
} for (int i=0; i<mUserSippers.size(); i++) {
List<BatterySipper> user = mUserSippers.valueAt(i);
for (int j=0; j<user.size(); j++) {
BatterySipper bs = user.get(j);
bs.computeMobilemspp();
if (bs.mobilemspp != 0) {
mMobilemsppList.add(bs);
}
}
}
Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
@Override
public int compare(BatterySipper lhs, BatterySipper rhs) {
return Double.compare(rhs.mobilemspp, lhs.mobilemspp);
}
}); processMiscUsage(); Collections.sort(mUsageList); // At this point, we've sorted the list so we are guaranteed the max values are at the top.
// We have only added real powers so far.
if (!mUsageList.isEmpty()) {
mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
final int usageListCount = mUsageList.size();
for (int i = 0; i < usageListCount; i++) {
mComputedPower += mUsageList.get(i).totalPowerMah;
}
} if (DEBUG) {
Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge="
+ makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower));
} mTotalPower = mComputedPower;
if (mStats.getLowDischargeAmountSinceCharge() > 1) {
if (mMinDrainedPower > mComputedPower) {
double amount = mMinDrainedPower - mComputedPower;
mTotalPower = mMinDrainedPower;
BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount); // Insert the BatterySipper in its sorted position.
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
} else if (mMaxDrainedPower < mComputedPower) {
double amount = mComputedPower - mMaxDrainedPower; // Insert the BatterySipper in its sorted position.
BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
}
}
}

我们依次分析

  • SparseArray<UserHandle> asUsers UserHanler代表设备上的一个用户
  • long rawRealtimeUs 系统开机后的运行时间
  • long rawUptimeUs 系统不包括休眠的运行时间
public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
long rawUptimeUs) {

初始化Stats操作

getStats()

如果mStats为空,则初始化

public BatteryStats getStats() {
if (mStats == null) {
load();
}
return mStats;
}
mMaxPower = 0; // 最大耗电量
mMaxRealPower = 0; // 最大真实耗电量
mComputedPower = 0; // 通过耗电计算器计算的耗电量总和
mTotalPower = 0; // 总的耗电量

刷新耗电量之前需要先清空之前的数据,clear都是清空操作

mUsageList.clear(); // 存储了BatterySipper列表,各类耗电量都存储在BatterySipper中,BatterySipper存储在mUsageList中
mWifiSippers.clear(); // 在统计软件耗电过程中使用到WIFI的应用,其对应的BatterySipper列表
mBluetoothSippers.clear(); // 在统计软件耗电过程中使用到BlueTooth的应用,其对应的BatterySipper列表
mUserSippers.clear(); // 设备上有多个用户时,存储了其他用户的耗电信息的SparseArray数据,键为userId,值为对应的List<BatterySipper>
mMobilemsppList.clear(); // 存储有数据接收和发送的BatterySipper对象的列表

初始化八大模块的耗电计算器,都继承于PowerCalculator抽象类,八大模块在processAppUsage方法中进行分析,这里只需要知道有哪八个以及进行的操作是初始化即可

计算项 Class文件
CPU功耗 mCpuPowerCalculator.java
Wakelock功耗 mWakelockPowerCalculator.java
无线电功耗 mMobileRadioPowerCalculator.java
WIFI功耗 mWifiPowerCalculator.java
蓝牙功耗 mBluetoothPowerCalculator.java
Sensor功耗 mSensorPowerCalculator.java
相机功耗 mCameraPowerCalculator.java
闪光灯功耗 mFlashlightPowerCalculator.java
if (mCpuPowerCalculator == null) {
mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
}
mCpuPowerCalculator.reset(); if (mWakelockPowerCalculator == null) {
mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile);
}
mWakelockPowerCalculator.reset(); if (mMobileRadioPowerCalculator == null) {
mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats);
}
mMobileRadioPowerCalculator.reset(mStats); // checkHasWifiPowerReporting can change if we get energy data at a later point, so
// always check this field.
final boolean hasWifiPowerReporting = checkHasWifiPowerReporting(mStats, mPowerProfile);
if (mWifiPowerCalculator == null || hasWifiPowerReporting != mHasWifiPowerReporting) {
mWifiPowerCalculator = hasWifiPowerReporting ?
new WifiPowerCalculator(mPowerProfile) :
new WifiPowerEstimator(mPowerProfile);
mHasWifiPowerReporting = hasWifiPowerReporting;
}
mWifiPowerCalculator.reset(); final boolean hasBluetoothPowerReporting = checkHasBluetoothPowerReporting(mStats,
mPowerProfile);
if (mBluetoothPowerCalculator == null ||
hasBluetoothPowerReporting != mHasBluetoothPowerReporting) {
mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
mHasBluetoothPowerReporting = hasBluetoothPowerReporting;
}
mBluetoothPowerCalculator.reset(); if (mSensorPowerCalculator == null) {
mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile,
(SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE));
}
mSensorPowerCalculator.reset(); if (mCameraPowerCalculator == null) {
mCameraPowerCalculator = new CameraPowerCalculator(mPowerProfile);
}
mCameraPowerCalculator.reset(); if (mFlashlightPowerCalculator == null) {
mFlashlightPowerCalculator = new FlashlightPowerCalculator(mPowerProfile);
}
mFlashlightPowerCalculator.reset();

电量统计需要先设置统计时间段,通过设置统计类型mStatsType变量来表示

mStatsType = statsType;

有三种可选值

  // 统计从上一次充电以来至现在的耗电量
public static final int STATS_SINCE_CHARGED = 0; // 统计系统启动以来到现在的耗电量
public static final int STATS_CURRENT = 1; // 统计从上一次拔掉USB线以来到现在的耗电量
public static final int STATS_SINCE_UNPLUGGED = 2;

当前系统的运行时间

mRawUptimeUs = rawUptimeUs;

当前系统的真实运行时间,包括休眠时间

mRawRealtimeUs = rawRealtimeUs;

剩下的也是一堆时间

mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs); // 电池放电运行时间
mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeUs); // 电池真实放电运行时间,包含休眠时间
mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeUs, mStatsType); // 对应类型的电池放电运行时间,如上次充满电后的电池运行时间
mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType); // 对应类型的电池放电运行时间,包括休眠时间
mBatteryTimeRemaining = mStats.computeBatteryTimeRemaining(rawRealtimeUs); // 电池预计使用时长
mChargeTimeRemaining = mStats.computeChargeTimeRemaining(rawRealtimeUs); // 电池预计多久充满时长

DEBUG模式下会输出时间日志,这不重要

if (DEBUG) {
Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime="
+ (rawUptimeUs/1000));
Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime="
+ (mBatteryUptime/1000));
Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime="
+ (mTypeBatteryUptime/1000));
}

计算最低和最高的电量近似值

该方法待会详细说明,现在我们只需要知道它主要进行统计APP软件的耗电量操作,统计之后会将每种类型,每个UID的耗电值存储在对应的BatterySipper

processAppUsage(asUsers);

对每个应用程序的每毫秒ms接收和发送的数据包mobilemspp进行排序

for (int i=0; i<mUsageList.size(); i++) {
BatterySipper bs = mUsageList.get(i);
bs.computeMobilemspp();
if (bs.mobilemspp != 0) {
mMobilemsppList.add(bs);
}
}
// 遍历其他用户的耗电情况
for (int i=0; i<mUserSippers.size(); i++) {
List<BatterySipper> user = mUserSippers.valueAt(i);
for (int j=0; j<user.size(); j++) {
BatterySipper bs = user.get(j);
bs.computeMobilemspp();
if (bs.mobilemspp != 0) {
mMobilemsppList.add(bs);
}
}
}

mMobilemsppList进行排序

Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
@Override
public int compare(BatterySipper lhs, BatterySipper rhs) {
return Double.compare(rhs.mobilemspp, lhs.mobilemspp);
}
});

计算硬件的耗电量,跟前面的processAppUsage(asUsers);对应,这两个方法我们都后面再说

processMiscUsage();

对软硬件耗电量结果进行降序排序

Collections.sort(mUsageList);

获取最大耗电量

因为我们刚才进行了排序,所以耗电最多的硬件/软件正位于顶部,赋值mMaxRealPower最大真实耗电量

遍历usageList计算得到mComputedPower耗电量总和

if (!mUsageList.isEmpty()) {
mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
final int usageListCount = mUsageList.size();
for (int i = 0; i < usageListCount; i++) {
mComputedPower += mUsageList.get(i).totalPowerMah;
}
}

如果存在未计算到的耗电量,实例化一个DrainType.UNACCOUNTED类型的BatterySipper进行存储,并添加到mUsageList

mTotalPower = mComputedPower;
if (mStats.getLowDischargeAmountSinceCharge() > 1) {
// 如果最低放电量 > 计算的总耗电量,说明还有未计算的
if (mMinDrainedPower > mComputedPower) {
double amount = mMinDrainedPower - mComputedPower;
mTotalPower = mMinDrainedPower;
// 实例化一个DrainType.UNACCOUNTED类型的BatterySipper,用来存储未计算的耗电量
BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount); // Insert the BatterySipper in its sorted position.
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
}

如果存在计算多了的耗电量,实例化一个DrainType.OVERCOUNTED类型的BatterySipper进行存储,并添加到mUsageList

// 如果最高放电量 < 计算的总耗电量,说明多算了耗电量
else if (mMaxDrainedPower < mComputedPower) {
double amount = mComputedPower - mMaxDrainedPower; // Insert the BatterySipper in its sorted position.
BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
int index = Collections.binarySearch(mUsageList, bs);
if (index < 0) {
index = -(index + 1);
}
mUsageList.add(index, bs);
mMaxPower = Math.max(mMaxPower, amount);
}
}

这篇已经太长了,关于软硬件的耗电量计算就在另外一篇里面写吧

参考链接

END

建了一个微信的安全交流群,欢迎添加我微信备注进群,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注


BatteryStatsHelper.java源码分析的更多相关文章

  1. Java源码分析 | CharSequence

    本文基于 OracleJDK 11, HotSpot 虚拟机. CharSequence 定义 CharSequence 是 java.lang 包下的一个接口,是 char 值的可读序列, 即其本身 ...

  2. Java源码分析:关于 HashMap 1.8 的重大更新(转载)

    http://blog.csdn.net/carson_ho/article/details/79373134 前言 HashMap 在 Java 和 Android 开发中非常常见 而HashMap ...

  3. JAVA源码分析-HashMap源码分析(二)

    本文继续分析HashMap的源码.本文的重点是resize()方法和HashMap中其他的一些方法,希望各位提出宝贵的意见. 话不多说,咱们上源码. final Node<K,V>[] r ...

  4. Java源码分析之LinkedList

    LinkedList与ArrayList正好相对,同样是List的实现类,都有增删改查等方法,但是实现方法跟后者有很大的区别. 先归纳一下LinkedList包含的API 1.构造函数: ①Linke ...

  5. Java源码分析:Guava之不可变集合ImmutableMap的源码分析

    一.案例场景 遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样-- public static final Map<String,String& ...

  6. JAVA源码分析-HashMap源码分析(一)

    一直以来,HashMap就是Java面试过程中的常客,不管是刚毕业的,还是工作了好多年的同学,在Java面试过程中,经常会被问到HashMap相关的一些问题,而且每次面试都被问到一些自己平时没有注意的 ...

  7. 【转】【java源码分析】Map中的hash算法分析

    全网把Map中的hash()分析的最透彻的文章,别无二家. 2018年05月09日 09:08:08 阅读数:957 你知道HashMap中hash方法的具体实现吗?你知道HashTable.Conc ...

  8. 【Java源码分析】LinkedList类

    LinkedList<E> 源码解读 继承AbstractSequentialList<E> 实现List<E>, Deque<E>, Cloneabl ...

  9. 一致性哈希Java源码分析

    首次接触一致性哈希是在学习memcached的时候,为了解决分布式服务器的负载均衡或者说选路的问题,一致性哈希算法不仅能够使memcached服务器被选中的概率(数据分布)更加均匀,而且使得服务器的增 ...

  10. JAVA源码分析------锁(1)

    http://870604904.iteye.com/blog/2258604 第一次写博客,也就是记录一些自己对于JAVA的一些理解,不足之处,请大家指出,一起探讨. 这篇博文我打算说一下JAVA中 ...

随机推荐

  1. 闻道Go语言,6月龄必知必会

    大家好,我是马甲哥, 学习新知识, 我的策略是模仿-->归纳--->举一反三, 在同程倒腾Go语言一年有余,本次记录<闻道Go语言,6月龄必知必会>,形式是同我的主力语言C#做 ...

  2. offset新探索:双管齐下,加速大数据量查询

    摘要:随着offset的增加,查询的时长也会越来越长.当offset达到百万级别的时候查询时长通常是业务所不能容忍的. 本文分享自华为云社区<offset新探索:双管齐下,加速大数据量查询> ...

  3. Linux三剑客sed

    注意sed和awk使用单引号,双引号有特殊解释 sed是Stream Editor(字符流编辑器)的缩写,简称流编辑器. sed是操作.过滤和转换文本内容的强大工具. 常用功能包括结合正则表达式对文件 ...

  4. 想开发DAYU200,我教你

    摘要:本文主要介绍OpenHarmony富设备DAYU200开发板的入门指导. 本文分享自华为云社区<DAYU200开发指导>,作者: 星辰27. 1 概述 DAYU200开发板属于Ope ...

  5. java安全之CC1浅学(1)

    前言 由于CC链还是比较复杂的,我们可以先看命令执行的部分payload之后再加上反序列化部分组成一个完整的payload 调试一 项目导入依赖,这里使用3.1版本 <!-- https://m ...

  6. 第2-2-4章 常见组件与中台化-常用组件服务介绍-分布式ID-附Snowflake雪花算法的代码实现

    目录 2.3 分布式ID 2.3.1 功能概述 2.3.2 应用场景 2.3.3 使用说明 2.3.4 项目截图 2.3.5 Snowflake雪花算法的代码实现 2.3 分布式ID 2.3.1 功能 ...

  7. IIS部署WebApi跨域不生效

    在IIS8.5上部署了WebApi程序,但是跨域不生效检查了前端和后端都没有问题. 后面才发现在应用程序池中需要设置为集成模式.经典模式下不能正常使用

  8. 周立功DTU+温度传感器,ZWS物联网平台尝试

    1.前言 了解到周立功有相关的物联网云平台,近期在调研动态环境监控项目,可以进行一个上云的尝试.购置了传感器.周立功的DTU等硬件,将传感器的温度.湿度等数据进行一个云平台的上传. 2.前期准备 传感 ...

  9. laravel框架部署

    每一个 Laravel 程序都会生成一个随机的 32 位长度的字符串存储在 .env 文件中的 APP_KEY 键值中, 当我们新建 Laravel 项目的时候会自动为你创建一个,只有在克隆的时候你才 ...

  10. Linux 使用打印机

    前言 在 deepin 上打印机好使,在我的mint上不好使,简单的查看一下deepin上驱动及软件.安装上就行了. 软件及驱动 ii hpijs-ppds 3.18.12+dfsg0-2 all H ...