diff --git a/powerbell/build.properties b/powerbell/build.properties index 5941290..dd1821a 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Oct 22 18:58:26 HKT 2025 +#Wed Oct 22 12:10:38 GMT 2025 stageCount=17 libraryProject= baseVersion=15.4 publishVersion=15.4.16 -buildCount=0 +buildCount=3 baseBetaVersion=15.4.17 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BatteryReportActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BatteryReportActivity.java index 7a6bf84..647bf64 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BatteryReportActivity.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BatteryReportActivity.java @@ -39,7 +39,6 @@ public class BatteryReportActivity extends Activity { private RecyclerView rvBatteryReport; private BatteryReportAdapter adapter; - // 数据列表:仅存储包名、耗电、时长(不存储名称,计算全程用包名) private List dataList = new ArrayList(); private List filteredList = new ArrayList(); private BroadcastReceiver batteryReceiver; @@ -47,18 +46,14 @@ public class BatteryReportActivity extends Activity { private float lastBatteryPercent = 100.0f; private long lastCheckTime = System.currentTimeMillis(); private EditText etSearch; - // 应用运行时长缓存(key:包名,value:时长ms) private Map appRunTimeCache = new HashMap(); - // 新增:包名-应用名称映射缓存(用于搜索匹配名称,避免重复查询) private Map packageToAppNameCache = new HashMap(); - // 包管理工具(全局持有,避免重复获取) private PackageManager mPackageManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_battery_report); - // 初始化包管理工具(仅一次,供后续查询名称使用) mPackageManager = getPackageManager(); // 权限检查(Java7 传统条件判断) @@ -72,34 +67,31 @@ public class BatteryReportActivity extends Activity { rvBatteryReport = (RecyclerView) findViewById(R.id.rv_battery_report); rvBatteryReport.setLayoutManager(new LinearLayoutManager(this)); - // 1. 加载所有应用(仅存包名,不查名称) + // 初始化流程:新增“加载24小时累计耗电”步骤 loadAllAppPackage(); - // 2. 预缓存所有包名对应的应用名称(为搜索名称做准备) preCacheAllAppNames(); - // 4. 首次获取运行时长(key:包名) appRunTimeCache = getAppRunTime(); - // 5. 更新时长到数据模型(用包名匹配) updateAppRunTimeToModel(); - // 6. 初始化过滤列表和适配器(传入包管理工具+名称缓存) + calculateInitial24hTotalConsumption(); // 初始化时计算24小时累计耗电 filteredList.addAll(dataList); adapter = new BatteryReportAdapter(this, filteredList, mPackageManager, packageToAppNameCache); rvBatteryReport.setAdapter(adapter); - // 搜索监听:修改为“包名+应用名称”双匹配 + // 搜索监听(不变) etSearch.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { - filterAppsByPackageAndName(s.toString()); // 替换为新的双维度过滤方法 + filterAppsByPackageAndName(s.toString()); } @Override public void afterTextChanged(Editable s) {} }); - // 电池广播(耗电计算全程用包名) + // 电池广播:调用修改后的“单次耗电计算+累计累加”方法 batteryReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -108,17 +100,13 @@ public class BatteryReportActivity extends Activity { float currentPercent = (float) level / scale * 100; LogUtils.d(TAG, "电池百分比变化:" + lastBatteryPercent + " -> " + currentPercent); - // 计算电池消耗并更新数据(全程用包名) if (currentPercent < lastBatteryPercent) { float dropPercent = lastBatteryPercent - currentPercent; long duration = System.currentTimeMillis() - lastCheckTime; LogUtils.d(TAG, "电池消耗:" + dropPercent + "%,时长:" + duration + "ms"); - // 重新获取最新运行时长(key:包名) appRunTimeCache = getAppRunTime(); - // 更新模型中的运行时长(包名匹配) updateAppRunTimeToModel(); - // 分配耗电(包名计算) - distributeBatteryConsumption(dropPercent, appRunTimeCache); + calculateSingleConsumptionAndAccumulate(dropPercent, appRunTimeCache); // 单次+累计逻辑 } lastBatteryPercent = currentPercent; @@ -138,67 +126,63 @@ public class BatteryReportActivity extends Activity { } /** - * 加载所有应用(仅获取包名,不查询应用名称,数据计算核心步骤) + * 加载所有应用(仅获取包名,初始化模型时单次耗电、累计耗电均设为0) */ private void loadAllAppPackage() { List appList = mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA); - dataList.clear(); // 清空列表避免重复 + dataList.clear(); LogUtils.d(TAG, "开始加载应用包名列表,共找到" + appList.size() + "个应用"); - // 仅遍历包名,不处理名称(避免名称获取异常影响) for (ApplicationInfo appInfo : appList) { String packageName = appInfo.packageName; - // 模型仅存包名、初始耗电0、初始时长0(名称显示/搜索时用缓存) - dataList.add(new AppBatteryModel(packageName, "0.0", 0)); + // 初始化:单次耗电(consumption)=0,累计耗电(totalConsumption)=0,运行时长=0 + dataList.add(new AppBatteryModel(packageName, 0.0f, 0.0f, 0)); } LogUtils.d(TAG, "应用包名列表加载完成,共添加" + dataList.size() + "个包名。"); } /** - * 新增:预缓存所有包名对应的应用名称(为搜索提供支持,避免重复查询) + * 预缓存应用名称(逻辑不变) */ private void preCacheAllAppNames() { - packageToAppNameCache.clear(); // 清空旧缓存 + packageToAppNameCache.clear(); LogUtils.d(TAG, "开始预缓存包名-应用名称映射"); for (AppBatteryModel model : dataList) { String packageName = model.getPackageName(); - String appName = getAppNameByPackage(packageName); // 复用工具方法获取名称 - packageToAppNameCache.put(packageName, appName); // 缓存映射关系 + String appName = getAppNameByPackage(packageName); + packageToAppNameCache.put(packageName, appName); } LogUtils.d(TAG, "预缓存完成,共缓存" + packageToAppNameCache.size() + "个应用名称"); } /** - * 工具方法:通过包名获取应用名称(带异常处理,避免崩溃) + * 通过包名获取应用名称(逻辑不变) */ private String getAppNameByPackage(String packageName) { try { ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, 0); return mPackageManager.getApplicationLabel(appInfo).toString(); } catch (PackageManager.NameNotFoundException e) { - // 包名不存在(如应用已卸载),用包名兜底 LogUtils.e(TAG, "包名" + packageName + "对应的应用未找到:" + e.getMessage()); return packageName; } catch (Exception e) { - // 其他异常(如权限问题),同样用包名兜底 LogUtils.e(TAG, "查询应用名称失败(包名:" + packageName + "):" + e.getMessage()); return packageName; } } /** - * 更新运行时长到模型(用包名匹配,不涉及名称) + * 更新运行时长到模型(逻辑不变) */ private void updateAppRunTimeToModel() { int nCount = 0; for (AppBatteryModel model : dataList) { String packageName = model.getPackageName(); Long runTime; - // Java7 显式判断包名是否在缓存中 if (appRunTimeCache.containsKey(packageName)) { runTime = appRunTimeCache.get(packageName); LogUtils.d(TAG, String.format("应用包 %s 运行时长已更新。", packageName)); @@ -213,30 +197,94 @@ public class BatteryReportActivity extends Activity { } /** - * 新增:双维度过滤(包名+应用名称) - * 搜索关键词同时匹配“包名(不区分大小写)”或“应用名称(不区分大小写)” + * 【新增】初始化时计算24小时累计耗电(赋值给totalConsumption) + * 逻辑:基于24小时运行时长占比,分配当前电池容量的理论24小时消耗 + */ + private void calculateInitial24hTotalConsumption() { + long total24hRunTime = 0; + // 1. 计算24小时内所有应用总运行时长 + for (Map.Entry entry : appRunTimeCache.entrySet()) { + total24hRunTime += entry.getValue(); + } + LogUtils.d(TAG, "24小时内所有应用总运行时长:" + formatRunTime(total24hRunTime)); + + // 2. 按运行时长占比分配24小时累计耗电(假设电池满电循环,用总容量近似24小时总消耗) + for (AppBatteryModel model : dataList) { + String packageName = model.getPackageName(); + Long app24hRunTime = appRunTimeCache.getOrDefault(packageName, 0L); + + // 计算占比与累计耗电 + float ratio = (total24hRunTime > 0) ? (float) app24hRunTime / total24hRunTime : 0; + float initialTotalConsumption = batteryCapacity * ratio; // 用电池容量近似24小时总消耗 + model.setTotalConsumption(initialTotalConsumption); // 初始化累计耗电 + LogUtils.d(TAG, String.format("应用包 %s 24小时累计耗电初始化:%.1f mAh", packageName, initialTotalConsumption)); + } + } + + /** + * 【核心修改】计算单次耗电(赋值给consumption)+ 累加至累计耗电(totalConsumption = totalConsumption + consumption) + */ + private void calculateSingleConsumptionAndAccumulate(float dropPercent, Map runTimeMap) { + long totalSingleRunTime = 0; + // 1. 计算本次电池下降期间的总运行时长 + for (Map.Entry entry : runTimeMap.entrySet()) { + totalSingleRunTime += entry.getValue(); + } + + // 2. 遍历计算每个应用的“单次耗电”并“累加至累计” + for (AppBatteryModel model : dataList) { + String packageName = model.getPackageName(); + Long appSingleRunTime = runTimeMap.getOrDefault(packageName, 0L); + + // 步骤1:计算本次单次耗电(赋值给consumption) + float ratio = (totalSingleRunTime > 0) ? (float) appSingleRunTime / totalSingleRunTime : 0; + float singleConsumption = batteryCapacity * dropPercent / 100 * ratio; // 单次消耗 + model.setConsumption(singleConsumption); // 存储单次耗电 + + // 步骤2:累加单次耗电到累计耗电(totalConsumption = 原有累计 + 本次单次) + float newTotalConsumption = model.getTotalConsumption() + singleConsumption; + model.setTotalConsumption(newTotalConsumption); // 更新累计耗电 + + // 同步运行时长 + model.setRunTime(appSingleRunTime); + + LogUtils.d(TAG, String.format("应用包 %s:单次耗电%.1f mAh,累计耗电%.1f mAh", + packageName, singleConsumption, newTotalConsumption)); + } + + // 3. 按累计耗电排序(从高到低) + Collections.sort(dataList, new Comparator() { + @Override + public int compare(AppBatteryModel m1, AppBatteryModel m2) { + return Float.compare(m2.getTotalConsumption(), m1.getTotalConsumption()); + } + }); + + // 4. 重新应用过滤并刷新列表 + filterAppsByPackageAndName(etSearch.getText().toString()); + } + + /** + * 双维度过滤(逻辑不变) */ private void filterAppsByPackageAndName(String keyword) { filteredList.clear(); if (keyword == null || keyword.isEmpty()) { filteredList.addAll(dataList); } else { - String lowerKeyword = keyword.toLowerCase(); // 统一转为小写,实现不区分大小写搜索 + String lowerKeyword = keyword.toLowerCase(); for (AppBatteryModel model : dataList) { String packageName = model.getPackageName(); String packageNameLower = packageName.toLowerCase(); - // 从缓存中获取应用名称(避免重复查询PackageManager) String appName = packageToAppNameCache.get(packageName); String appNameLower = appName.toLowerCase(); - // 匹配规则:包名包含关键词 OR 应用名称包含关键词 boolean isMatched = packageNameLower.contains(lowerKeyword) - || appNameLower.contains(lowerKeyword); + || appNameLower.contains(lowerKeyword); if (isMatched) { filteredList.add(model); - } } } @@ -244,7 +292,7 @@ public class BatteryReportActivity extends Activity { } /** - * 获取应用运行时长(返回包名-时长映射,不处理名称) + * 获取应用运行时长(逻辑不变,返回24小时运行时长) */ private Map getAppRunTime() { Map runTimeMap = new HashMap(); @@ -257,11 +305,10 @@ public class BatteryReportActivity extends Activity { List statsList = manager.queryUsageStats( android.app.usage.UsageStatsManager.INTERVAL_DAILY, startTime, endTime); - // 仅存储包名和时长(不查名称) for (android.app.usage.UsageStats stats : statsList) { long runTimeMs = stats.getTotalTimeInForeground(); String packageName = stats.getPackageName(); - LogUtils.d(TAG, "包名" + packageName + "运行时长:" + formatRunTime(runTimeMs)); + LogUtils.d(TAG, "包名" + packageName + "24小时运行时长:" + formatRunTime(runTimeMs)); runTimeMap.put(packageName, runTimeMs); if (packageName.equals("aidepro.top")) { LogUtils.d(TAG, String.format("runTimeMap.put(packageName, runTimeMs) 特殊查询 %s 查询有结果。", packageName)); @@ -277,7 +324,7 @@ public class BatteryReportActivity extends Activity { } /** - * 格式化运行时长(工具方法,与包名无关) + * 格式化运行时长(逻辑不变) */ private String formatRunTime(long runTimeMs) { if (runTimeMs <= 0) { @@ -298,48 +345,7 @@ public class BatteryReportActivity extends Activity { } /** - * 分配电池消耗(全程用包名计算,不涉及名称) - */ - private void distributeBatteryConsumption(float totalDropPercent, Map runTimeMap) { - long totalRunTime = 0; - // 遍历包名计算总时长 - for (Map.Entry entry : runTimeMap.entrySet()) { - totalRunTime += entry.getValue(); - } - - // 按包名匹配计算各应用耗电 - for (AppBatteryModel model : dataList) { - String packageName = model.getPackageName(); - Long appRunTime; - // Java7 显式判断包名是否存在 - if (runTimeMap.containsKey(packageName)) { - appRunTime = runTimeMap.get(packageName); - } else { - appRunTime = 0L; - } - - float ratio = (totalRunTime > 0) ? (float) appRunTime / totalRunTime : 0; - float consumption = batteryCapacity * totalDropPercent / 100 * ratio; - model.setConsumption(String.format("%.1f", consumption)); - model.setRunTime(appRunTime); // 同步更新时长 - } - - // 按耗电排序(用包名对应的耗电值排序) - Collections.sort(dataList, new Comparator() { - @Override - public int compare(AppBatteryModel m1, AppBatteryModel m2) { - float c1 = Float.parseFloat(m1.getConsumption()); - float c2 = Float.parseFloat(m2.getConsumption()); - return Float.compare(c2, c1); // 逆序(耗电从高到低) - } - }); - - // 重新应用“包名+名称”双维度过滤 - filterAppsByPackageAndName(etSearch.getText().toString()); - } - - /** - * 权限检查(仅操作系统服务,与包名无关) + * 权限检查(逻辑不变) */ private boolean hasUsageStatsPermission(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { @@ -361,31 +367,43 @@ public class BatteryReportActivity extends Activity { } /** - * 数据模型(仅存储包名、耗电、时长,不存名称) + * 【核心修改】数据模型:明确字段含义 + * - consumption:单次耗电(两次电池广播间的消耗,float类型便于计算) + * - totalConsumption:累计耗电(24小时初始化值+后续单次累加,显示用) */ public static class AppBatteryModel { - private String packageName; // 核心标识:应用包名 - private String consumption; // 电量消耗(mAh) - private long runTime; // 运行时长(ms) + private String packageName; // 应用包名(核心标识) + private float consumption; // 单次耗电(mAh,float类型) + private float totalConsumption;// 累计耗电(mAh,显示+排序用) + private long runTime; // 运行时长(ms) - // Java7 显式构造(仅传入包名、初始耗电、初始时长) - public AppBatteryModel(String packageName, String consumption, long runTime) { + // Java7 显式构造:初始化单次耗电、累计耗电为0 + public AppBatteryModel(String packageName, float consumption, float totalConsumption, long runTime) { this.packageName = packageName; - this.consumption = consumption; + this.consumption = consumption; // 单次耗电初始为0 + this.totalConsumption = totalConsumption; // 累计耗电初始为0(后续初始化时赋值) this.runTime = runTime; } - // Getter/Setter(仅操作包名、耗电、时长) + // Getter/Setter:覆盖所有字段,确保数据操作正常 public String getPackageName() { return packageName; } - public String getConsumption() { - return consumption; + public float getConsumption() { + return consumption; // 获取单次耗电 } - public void setConsumption(String consumption) { - this.consumption = consumption; + public void setConsumption(float consumption) { + this.consumption = consumption; // 设置单次耗电 + } + + public float getTotalConsumption() { + return totalConsumption; // 获取累计耗电(显示用) + } + + public void setTotalConsumption(float totalConsumption) { + this.totalConsumption = totalConsumption; // 设置累计耗电(初始化/累加用) } public long getRunTime() { @@ -398,37 +416,37 @@ public class BatteryReportActivity extends Activity { } /** - * RecyclerView 适配器(支持从缓存获取名称,避免重复查询) + * RecyclerView 适配器:仅显示累计耗电(totalConsumption),逻辑适配模型修改 */ public static class BatteryReportAdapter extends RecyclerView.Adapter { private Context mContext; private List mDataList; - private PackageManager mPm; // 兜底用:缓存无名称时查询 - private Map mPackageToNameCache; // 新增:名称缓存映射 + private PackageManager mPm; + private Map mPackageToNameCache; - // Java7 显式构造(新增名称缓存参数) + // Java7 显式构造:接收名称缓存,确保显示时高效获取应用名 public BatteryReportAdapter(Context context, List dataList, PackageManager pm, Map packageToNameCache) { this.mContext = context; this.mDataList = dataList; this.mPm = pm; - this.mPackageToNameCache = packageToNameCache; // 接收名称缓存 + this.mPackageToNameCache = packageToNameCache; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + // 加载系统列表项布局(text1显示应用名,text2显示累计耗电+时长) View itemView = LayoutInflater.from(mContext) .inflate(android.R.layout.simple_list_item_2, parent, false); return new ViewHolder(itemView); } - // 绑定数据:优先从缓存取名称,无缓存时兜底查询 @Override public void onBindViewHolder(ViewHolder holder, int position) { - // Java7 显式非空判断(避免空指针) + // Java7 显式非空判断:避免空指针异常 if (mDataList == null || mDataList.isEmpty() || position >= mDataList.size()) { holder.tvAppName.setText("未知应用"); - holder.tvConsumption.setText("耗电:0.0 mAh | 运行时长:0秒"); + holder.tvConsumption.setText("累计耗电:0.0 mAh | 运行时长:0秒"); return; } @@ -436,66 +454,59 @@ public class BatteryReportActivity extends Activity { String packageName = model.getPackageName(); String appName = ""; - // 优先从缓存获取名称(避免重复调用PackageManager,提升性能) + // 优先从缓存获取应用名:减少PackageManager调用,提升性能 if (mPackageToNameCache != null && mPackageToNameCache.containsKey(packageName)) { appName = mPackageToNameCache.get(packageName); } else { - // 缓存无数据时,兜底查询名称(兼容异常场景) + // 缓存无数据时兜底查询,并同步更新缓存 try { ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0); appName = mPm.getApplicationLabel(appInfo).toString(); - // 同步更新到缓存,后续复用 if (mPackageToNameCache != null) { mPackageToNameCache.put(packageName, appName); } } catch (PackageManager.NameNotFoundException e) { - appName = packageName; + appName = packageName; // 包名不存在时用包名兜底 LogUtils.e("Adapter", "包名" + packageName + "对应的应用未找到:" + e.getMessage()); } catch (Exception e) { - appName = packageName; + appName = packageName; // 其他异常时用包名兜底 LogUtils.e("Adapter", "查询应用名称失败(包名:" + packageName + "):" + e.getMessage()); } } - // 显示应用名称(缓存/兜底结果) + // 显示逻辑:仅展示累计耗电(totalConsumption),隐藏单次耗电 holder.tvAppName.setText(appName); - - // 格式化运行时长(调用 Activity 的工具方法) + // 格式化运行时长 + 累计耗电(保留1位小数,提升可读性) String runTimeStr = ((BatteryReportActivity) mContext).formatRunTime(model.getRunTime()); - // 显示耗电+运行时长(基于包名计算的结果) - String consumptionText = String.format("耗电:%s mAh | 运行时长:%s", - model.getConsumption(), runTimeStr); - holder.tvConsumption.setText(consumptionText); + String totalConsumptionText = String.format("累计耗电:%.1f mAh | 运行时长:%s", + model.getTotalConsumption(), runTimeStr); + holder.tvConsumption.setText(totalConsumptionText); - // 优化显示:目标应用文字标蓝(通过包名匹配) + // 显示优化:文字颜色区分(避免所有应用均标蓝,仅示例可按需修改) + holder.tvAppName.setTextColor(mContext.getResources().getColor(android.R.color.black)); + holder.tvConsumption.setTextColor(mContext.getResources().getColor(android.R.color.darker_gray)); - holder.tvAppName.setTextColor(mContext.getResources().getColor(android.R.color.holo_blue_dark)); - holder.tvConsumption.setTextColor(mContext.getResources().getColor(android.R.color.holo_blue_dark)); - - //holder.tvAppName.setTextColor(mContext.getResources().getColor(android.R.color.black)); - //holder.tvConsumption.setTextColor(mContext.getResources().getColor(android.R.color.darker_gray)); - - - // 调整文字大小(提升可读性) + // 调整文字大小:适配手机屏幕,提升可读性 holder.tvAppName.setTextSize(16); holder.tvConsumption.setTextSize(14); } - // 获取列表长度(Java7 三元运算符) + // 获取列表长度:Java7 三元运算符判断空值,避免空指针 @Override public int getItemCount() { return mDataList == null ? 0 : mDataList.size(); } - // ViewHolder(绑定系统布局的两个 TextView) + /** + * ViewHolder:绑定系统布局控件,与显示逻辑对应 + */ public static class ViewHolder extends RecyclerView.ViewHolder { - TextView tvAppName; // 显示应用名称(缓存/兜底获取) - TextView tvConsumption; // 显示耗电+运行时长(包名计算结果) + TextView tvAppName; // 显示应用名称 + TextView tvConsumption; // 显示累计耗电 + 运行时长 - // Java7 显式构造 + // Java7 显式构造:绑定控件ID(系统布局固定ID:text1、text2) public ViewHolder(View itemView) { super(itemView); - // 强制类型转换(系统布局控件 ID:text1 和 text2) tvAppName = (TextView) itemView.findViewById(android.R.id.text1); tvConsumption = (TextView) itemView.findViewById(android.R.id.text2); }