From f7b2c0d4c0110f872b63795b879052022932cae0 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Wed, 22 Oct 2025 16:56:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BA=94=E7=94=A8=E7=94=B5?= =?UTF-8?q?=E9=87=8F=E4=BD=BF=E7=94=A8=E6=83=85=E5=86=B5=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- powerbell/build.properties | 4 +- powerbell/src/main/AndroidManifest.xml | 12 + .../activities/BatteryReportActivity.java | 371 ++++++++++++++---- .../res/layout/activity_batteryreport.xml | 23 +- 4 files changed, 334 insertions(+), 76 deletions(-) diff --git a/powerbell/build.properties b/powerbell/build.properties index 84f7a1e..5c55b08 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Oct 22 06:41:10 GMT 2025 +#Wed Oct 22 08:55:04 GMT 2025 stageCount=14 libraryProject= baseVersion=15.4 publishVersion=15.4.13 -buildCount=13 +buildCount=43 baseBetaVersion=15.4.14 diff --git a/powerbell/src/main/AndroidManifest.xml b/powerbell/src/main/AndroidManifest.xml index 3a6684c..e594040 100644 --- a/powerbell/src/main/AndroidManifest.xml +++ b/powerbell/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ @@ -35,7 +36,18 @@ + + + + + + + + + dataList = new ArrayList(); - private List filteredList = new ArrayList(); // 搜索过滤后的数据 + // 数据列表:仅存储包名、耗电、时长(不存储名称,计算全程用包名) + private List dataList = new ArrayList(); + private List filteredList = new ArrayList(); private BroadcastReceiver batteryReceiver; - private int batteryCapacity = 5400; // 手动设置电池容量(mAh,可根据设备实际容量调整) - private float lastBatteryPercent = 100.0f; // 上次电池百分比 - private long lastCheckTime = System.currentTimeMillis(); // 上次检查时间 + private int batteryCapacity = 5400; // 电池容量(mAh) + private float lastBatteryPercent = 100.0f; + private long lastCheckTime = System.currentTimeMillis(); private EditText etSearch; + // 应用运行时长缓存(key:包名,value:时长ms) + private Map appRunTimeCache = new HashMap(); + // 包管理工具(全局持有,避免重复获取) + private PackageManager mPackageManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_batteryreport); + // 初始化包管理工具(仅一次,供后续查询名称使用) + mPackageManager = getPackageManager(); + // 权限检查(Java7 传统条件判断) if (!hasUsageStatsPermission(this)) { Toast.makeText(this, "请进入设置-应用-权限-特殊访问权限-使用情况访问权限,开启本应用的权限", Toast.LENGTH_LONG).show(); startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)); return; } - etSearch = (EditText) findViewById(R.id.et_search); - rvBatteryReport = (RecyclerView) findViewById(R.id.rv_battery_report); + etSearch = (EditText) findViewById(R.id.et_search); + rvBatteryReport = (RecyclerView) findViewById(R.id.rv_battery_report); rvBatteryReport.setLayoutManager(new LinearLayoutManager(this)); - loadAllAppList(); // 加载所有应用(包含系统应用) + // 1. 加载所有应用(仅存包名,不查名称) + loadAllAppPackage(); + // 2. 强制添加目标应用(包名兜底) + //forceAddTargetPackage(); + // 3. 首次获取运行时长(key:包名) + appRunTimeCache = getAppRunTime(); + // 4. 更新时长到数据模型(用包名匹配) + updateAppRunTimeToModel(); + // 5. 初始化过滤列表和适配器(传入包管理工具供显示时查名称) filteredList.addAll(dataList); - adapter = new BatteryReportAdapter(this, filteredList); + adapter = new BatteryReportAdapter(this, filteredList, mPackageManager); 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) { - filterApps(s.toString()); + filterAppsByPackage(s.toString()); } @Override public void afterTextChanged(Editable s) {} }); - // 注册电池广播监听 + // 电池广播(耗电计算全程用包名) batteryReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -92,13 +108,17 @@ 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"); - // 分配耗电到各应用 - distributeBatteryConsumption(dropPercent, getAppRunTime()); + // 重新获取最新运行时长(key:包名) + appRunTimeCache = getAppRunTime(); + // 更新模型中的运行时长(包名匹配) + updateAppRunTimeToModel(); + // 分配耗电(包名计算) + distributeBatteryConsumption(dropPercent, appRunTimeCache); } lastBatteryPercent = currentPercent; @@ -111,95 +131,248 @@ public class BatteryReportActivity extends Activity { @Override protected void onDestroy() { super.onDestroy(); + // Java7 显式非空判断 if (batteryReceiver != null) { unregisterReceiver(batteryReceiver); } } - // 加载所有应用(包含系统应用) - private void loadAllAppList() { - PackageManager pm = getPackageManager(); - List appList = pm.getInstalledApplications(PackageManager.GET_META_DATA); + /** + * 加载所有应用(仅获取包名,不查询应用名称,数据计算核心步骤) + */ + private void loadAllAppPackage() { + List appList = mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA); + dataList.clear(); // 清空列表避免重复 + LogUtils.d(TAG, "开始加载应用包名列表,共找到" + appList.size() + "个应用"); + + // 仅遍历包名,不处理名称(避免名称获取异常影响) for (ApplicationInfo appInfo : appList) { String packageName = appInfo.packageName; - String appName = pm.getApplicationLabel(appInfo).toString(); - dataList.add(new AppBatteryModel(appName, packageName, "0.0")); + /*if (packageName.equals("cc.winboll.studio.powerbell.beta")) { + //if (packageName.equals("aidepro.top")) { + LogUtils.d(TAG, "aidepro.top OK5"); + }*/ + // 模型仅存包名、初始耗电0、初始时长0(名称显示时再查) + dataList.add(new AppBatteryModel(packageName, "0.0", 0)); } + + /*for (AppBatteryModel appBatteryModel : dataList) { + if (appBatteryModel.getPackageName().equals("aidepro.top")) { + LogUtils.d(TAG, "aidepro.top OK4"); + } + }*/ + + LogUtils.d(TAG, "应用包名列表加载完成,共添加" + dataList.size() + "个包名。" ); } - // 搜索过滤应用 - private void filterApps(String keyword) { + /** + * 强制添加目标包名(兜底逻辑,仅操作包名) + */ + private void forceAddTargetPackage() { + // 1. 先检查列表中是否已存在目标包名 + boolean isTargetExists = false; + for (AppBatteryModel model : dataList) { + if (TARGET_APP_PACKAGE.equals(model.getPackageName())) { + isTargetExists = true; + break; + } + } + + if (isTargetExists) { + LogUtils.d(TAG, "目标包名已在列表中,无需重复添加"); + return; + } + + // 2. 仅用包名创建模型,强制添加(不依赖名称查询) + dataList.add(0, new AppBatteryModel(TARGET_APP_PACKAGE, "0.0", 0)); + LogUtils.d(TAG, "强制添加目标包名成功:" + TARGET_APP_PACKAGE); + } + + /** + * 更新运行时长到模型(用包名匹配,不涉及名称) + */ + private void updateAppRunTimeToModel() { + /*if(appRunTimeCache.containsKey("aidepro.top")) + { + LogUtils.d(TAG, "aidepro.top OK2"); + }*/ + + int nCount = 0; + for (AppBatteryModel model : dataList) { + String packageName = model.getPackageName(); + /*if(packageName.equals("aidepro.top")) + { + LogUtils.d(TAG, "aidepro.top OK3"); + }*/ + + Long runTime; + // Java7 显式判断包名是否在缓存中 + if (appRunTimeCache.containsKey(packageName)) { + /*if(packageName.equals("cc.winboll.studio.powerbell.beta")) { + LogUtils.d(TAG, String.format("特殊查询 %s 查询有结果。", packageName)); + }*/ + runTime = appRunTimeCache.get(packageName); + LogUtils.d(TAG, String.format("应用包 %s 运行时长已更新。", packageName)); + nCount++; + } else { + runTime = 0L; + } + model.setRunTime(runTime); + } + LogUtils.d(TAG, String.format("dataList.size() %d, appRunTimeCache.size() %d。", dataList.size(), appRunTimeCache.size())); + LogUtils.d(TAG, String.format("updateAppRunTimeToModel() 更新的数据量为:%d", nCount)); + } + + /** + * 按包名过滤应用(搜索逻辑仅操作包名) + */ + private void filterAppsByPackage(String keyword) { filteredList.clear(); - if (keyword.isEmpty()) { + if (keyword == null || keyword.isEmpty()) { filteredList.addAll(dataList); } else { + String lowerKeyword = keyword.toLowerCase(); + boolean isTargetMatched = false; + for (AppBatteryModel model : dataList) { - if (model.getAppName().toLowerCase().contains(keyword.toLowerCase()) - || model.getPackageName().toLowerCase().contains(keyword.toLowerCase())) { + String packageNameLower = model.getPackageName().toLowerCase(); + // 仅按包名包含关键词过滤 + boolean isMatched = packageNameLower.contains(lowerKeyword); + + if (isMatched) { filteredList.add(model); + // 检查是否是目标包名 + if (TARGET_APP_PACKAGE.equals(model.getPackageName())) { + isTargetMatched = true; + } + } + } + + // 兜底:目标包名未匹配时强制添加 + if (!isTargetMatched) { + for (AppBatteryModel model : dataList) { + if (TARGET_APP_PACKAGE.equals(model.getPackageName())) { + filteredList.add(0, model); + LogUtils.d(TAG, "搜索兜底:强制添加目标包名到结果列表"); + break; + } } } } adapter.notifyDataSetChanged(); } - // 获取应用前台运行时长(统计24小时数据) + /** + * 获取应用运行时长(返回包名-时长映射,不处理名称) + */ private Map getAppRunTime() { Map runTimeMap = new HashMap(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { try { - android.app.usage.UsageStatsManager manager = - (android.app.usage.UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); + android.app.usage.UsageStatsManager manager = + (android.app.usage.UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); long endTime = System.currentTimeMillis(); long startTime = endTime - 24 * 3600 * 1000; // 近24小时 - // 使用数值4替代INTERVAL_DAY,兼容低版本识别问题 List statsList = manager.queryUsageStats( - 4, startTime, endTime); + android.app.usage.UsageStatsManager.INTERVAL_DAILY, startTime, endTime); + + // 仅存储包名和时长(不查名称) for (android.app.usage.UsageStats stats : statsList) { - LogUtils.d(TAG, "应用" + stats.getPackageName() + "运行时长:" + stats.getTotalTimeInForeground()); - runTimeMap.put(stats.getPackageName(), stats.getTotalTimeInForeground()); + long runTimeMs = stats.getTotalTimeInForeground(); + String packageName = stats.getPackageName(); + LogUtils.d(TAG, "包名" + packageName + "运行时长:" + formatRunTime(runTimeMs)); + runTimeMap.put(packageName, runTimeMs); + if (packageName.equals("aidepro.top")) { + LogUtils.d(TAG, String.format("runTimeMap.put(packageName, runTimeMs) 特殊查询 %s 查询有结果。", packageName)); + } } } catch (Exception e) { LogUtils.e(TAG, "获取应用运行时长失败:" + e.getMessage()); } } + + /*if(runTimeMap.containsKey("aidepro.top")) + { + LogUtils.d(TAG, "aidepro.top OK"); + }*/ + + LogUtils.d(TAG, String.format("应用运行时长列表数量%d。", runTimeMap.size())); return runTimeMap; } - // 分配电池消耗到各应用,并按消耗从大到小排序 + /** + * 格式化运行时长(工具方法,与包名无关) + */ + private String formatRunTime(long runTimeMs) { + if (runTimeMs <= 0) { + return "0秒"; + } + long seconds = runTimeMs / 1000; + long hours = seconds / 3600; + long minutes = (seconds % 3600) / 60; + seconds = seconds % 60; + + if (hours > 0) { + return String.format("%d时%d分%d秒", hours, minutes, seconds); + } else if (minutes > 0) { + return String.format("%d分%d秒", minutes, seconds); + } else { + return String.format("%d秒", seconds); + } + } + + /** + * 分配电池消耗(全程用包名计算,不涉及名称) + */ private void distributeBatteryConsumption(float totalDropPercent, Map runTimeMap) { long totalRunTime = 0; - for (Long time : runTimeMap.values()) { - totalRunTime += time; - } - for (AppBatteryModel model : dataList) { - Long appRunTime = runTimeMap.get(model.getPackageName()); - if (appRunTime == null) appRunTime = 0L; - float ratio = totalRunTime > 0 ? (float) appRunTime / totalRunTime : 0; - float consumption = batteryCapacity * totalDropPercent / 100 * ratio; - model.setConsumption(String.format("%.1f", consumption)); + // 遍历包名计算总时长 + 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); // 逆序排列 + return Float.compare(c2, c1); // 逆序(耗电从高到低) } }); - filterApps(etSearch.getText().toString()); // 重新应用搜索过滤 + + // 重新应用包名过滤 + filterAppsByPackage(etSearch.getText().toString()); } + /** + * 权限检查(仅操作系统服务,与包名无关) + */ private boolean hasUsageStatsPermission(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { return false; } - android.app.usage.UsageStatsManager manager = - (android.app.usage.UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); + android.app.usage.UsageStatsManager manager = + (android.app.usage.UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); if (manager == null) { return false; } @@ -207,26 +380,27 @@ public class BatteryReportActivity extends Activity { long endTime = System.currentTimeMillis(); long startTime = endTime - 1000 * 60; List statsList = manager.queryUsageStats( - android.app.usage.UsageStatsManager.INTERVAL_DAILY, startTime, endTime); + android.app.usage.UsageStatsManager.INTERVAL_DAILY, startTime, endTime); return statsList != null && !statsList.isEmpty(); } + /** + * 数据模型(仅存储包名、耗电、时长,不存名称) + */ public static class AppBatteryModel { - private String appName; - private String packageName; - private String consumption; + private String packageName; // 核心标识:应用包名 + private String consumption; // 电量消耗(mAh) + private long runTime; // 运行时长(ms) - public AppBatteryModel(String appName, String packageName, String consumption) { - this.appName = appName; + // Java7 显式构造(仅传入包名、初始耗电、初始时长) + public AppBatteryModel(String packageName, String consumption, long runTime) { this.packageName = packageName; this.consumption = consumption; + this.runTime = runTime; } - public String getAppName() { - return appName; - } - + // Getter/Setter(仅操作包名、耗电、时长) public String getPackageName() { return packageName; } @@ -238,42 +412,105 @@ public class BatteryReportActivity extends Activity { public void setConsumption(String consumption) { this.consumption = consumption; } + + public long getRunTime() { + return runTime; + } + + public void setRunTime(long runTime) { + this.runTime = runTime; + } } + /** + * RecyclerView 适配器(仅在显示时通过包名查询应用名称) + */ public static class BatteryReportAdapter extends RecyclerView.Adapter { private Context mContext; private List mDataList; + private PackageManager mPm; // 用于显示时查询应用名称 - public BatteryReportAdapter(Context context, List dataList) { + // Java7 显式构造(传入包管理工具) + public BatteryReportAdapter(Context context, List dataList, PackageManager pm) { this.mContext = context; this.mDataList = dataList; + this.mPm = pm; // 持有包管理工具,供显示时查名称 } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(mContext) - .inflate(android.R.layout.simple_list_item_2, parent, false); + .inflate(android.R.layout.simple_list_item_2, parent, false); return new ViewHolder(itemView); } + // 绑定数据:核心修改——仅在此处通过包名查询应用名称并显示 @Override public void onBindViewHolder(ViewHolder holder, int position) { + // Java7 显式非空判断(避免空指针) + if (mDataList == null || mDataList.isEmpty() || position >= mDataList.size()) { + holder.tvAppName.setText("未知应用"); + holder.tvConsumption.setText("耗电:0.0 mAh | 运行时长:0秒"); + return; + } + AppBatteryModel model = mDataList.get(position); - holder.tvAppName.setText(model.getAppName()); // 显示应用名称 - holder.tvConsumption.setText("电量消耗:" + model.getConsumption() + " mAh"); + String packageName = model.getPackageName(); + String appName = ""; + + // 关键步骤:通过包名查询应用名称(仅显示时执行,不影响数据计算) + try { + ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, 0); + appName = mPm.getApplicationLabel(appInfo).toString(); + } catch (PackageManager.NameNotFoundException e) { + // 包名不存在(如应用已卸载),用包名作为名称兜底 + appName = packageName; + LogUtils.e("Adapter", "包名" + packageName + "对应的应用未找到:" + e.getMessage()); + } catch (Exception e) { + // 其他异常(如权限问题),同样用包名兜底 + appName = packageName; + LogUtils.e("Adapter", "查询应用名称失败(包名:" + packageName + "):" + e.getMessage()); + } + + // 显示应用名称(查询到则显示名称,否则显示包名) + holder.tvAppName.setText(appName); + + // 格式化运行时长(调用 Activity 的工具方法) + String runTimeStr = ((BatteryReportActivity) mContext).formatRunTime(model.getRunTime()); + // 显示耗电+运行时长(基于包名计算的结果) + String consumptionText = String.format("耗电:%s mAh | 运行时长:%s", + model.getConsumption(), runTimeStr); + holder.tvConsumption.setText(consumptionText); + + // 优化显示:目标应用文字标蓝(通过包名匹配) + if (BatteryReportActivity.TARGET_APP_PACKAGE.equals(packageName)) { + holder.tvAppName.setTextColor(mContext.getResources().getColor(android.R.color.holo_blue_dark)); + holder.tvConsumption.setTextColor(mContext.getResources().getColor(android.R.color.holo_blue_dark)); + } else { + 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 三元运算符) @Override public int getItemCount() { - return mDataList.size(); + return mDataList == null ? 0 : mDataList.size(); } + // ViewHolder(绑定系统布局的两个 TextView) public static class ViewHolder extends RecyclerView.ViewHolder { - TextView tvAppName; - TextView tvConsumption; + TextView tvAppName; // 显示应用名称(通过包名查询) + TextView tvConsumption; // 显示耗电+运行时长(包名计算结果) + // Java7 显式构造 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); } diff --git a/powerbell/src/main/res/layout/activity_batteryreport.xml b/powerbell/src/main/res/layout/activity_batteryreport.xml index 7d1620d..70a6d3e 100644 --- a/powerbell/src/main/res/layout/activity_batteryreport.xml +++ b/powerbell/src/main/res/layout/activity_batteryreport.xml @@ -1,22 +1,31 @@ - + android:orientation="vertical" + android:background="@android:color/white"> + + android:layout_margin="8dp" + android:padding="12dp" + android:hint="搜索应用包名" + android:background="@android:drawable/btn_default_small" + android:inputType="text" + android:textSize="16sp"/> + + android:layout_height="0dp" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp"/>