应用电量报告中添加应用名称搜索功能
This commit is contained in:
		| @@ -1,8 +1,8 @@ | ||||
| #Created by .winboll/winboll_app_build.gradle | ||||
| #Wed Oct 22 16:57:21 HKT 2025 | ||||
| #Wed Oct 22 09:39:33 GMT 2025 | ||||
| stageCount=15 | ||||
| libraryProject= | ||||
| baseVersion=15.4 | ||||
| publishVersion=15.4.14 | ||||
| buildCount=0 | ||||
| buildCount=1 | ||||
| baseBetaVersion=15.4.15 | ||||
|   | ||||
| @@ -51,6 +51,8 @@ public class BatteryReportActivity extends Activity { | ||||
|     private EditText etSearch; | ||||
|     // 应用运行时长缓存(key:包名,value:时长ms) | ||||
|     private Map<String, Long> appRunTimeCache = new HashMap<String, Long>(); | ||||
|     // 新增:包名-应用名称映射缓存(用于搜索匹配名称,避免重复查询) | ||||
|     private Map<String, String> packageToAppNameCache = new HashMap<String, String>(); | ||||
|     // 包管理工具(全局持有,避免重复获取) | ||||
|     private PackageManager mPackageManager; | ||||
|  | ||||
| @@ -74,25 +76,27 @@ public class BatteryReportActivity extends Activity { | ||||
|  | ||||
|         // 1. 加载所有应用(仅存包名,不查名称) | ||||
|         loadAllAppPackage(); | ||||
|         // 2. 强制添加目标应用(包名兜底) | ||||
|         // 2. 预缓存所有包名对应的应用名称(为搜索名称做准备) | ||||
|         preCacheAllAppNames(); | ||||
|         // 3. 强制添加目标应用(包名兜底) | ||||
|         //forceAddTargetPackage(); | ||||
|         // 3. 首次获取运行时长(key:包名) | ||||
|         // 4. 首次获取运行时长(key:包名) | ||||
|         appRunTimeCache = getAppRunTime(); | ||||
|         // 4. 更新时长到数据模型(用包名匹配) | ||||
|         // 5. 更新时长到数据模型(用包名匹配) | ||||
|         updateAppRunTimeToModel(); | ||||
|         // 5. 初始化过滤列表和适配器(传入包管理工具供显示时查名称) | ||||
|         // 6. 初始化过滤列表和适配器(传入包管理工具+名称缓存) | ||||
|         filteredList.addAll(dataList); | ||||
|         adapter = new BatteryReportAdapter(this, filteredList, mPackageManager); | ||||
|         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) { | ||||
| 					filterAppsByPackage(s.toString()); | ||||
| 					filterAppsByPackageAndName(s.toString()); // 替换为新的双维度过滤方法 | ||||
| 				} | ||||
|  | ||||
| 				@Override | ||||
| @@ -145,27 +149,51 @@ public class BatteryReportActivity extends Activity { | ||||
|         dataList.clear(); // 清空列表避免重复 | ||||
|  | ||||
|         LogUtils.d(TAG, "开始加载应用包名列表,共找到" + appList.size() + "个应用"); | ||||
|          | ||||
|  | ||||
|         // 仅遍历包名,不处理名称(避免名称获取异常影响) | ||||
|         for (ApplicationInfo appInfo : appList) { | ||||
|             String packageName = appInfo.packageName; | ||||
| 			/*if (packageName.equals("cc.winboll.studio.powerbell.beta")) { | ||||
| 			//if (packageName.equals("aidepro.top")) { | ||||
| 				LogUtils.d(TAG, "aidepro.top OK5"); | ||||
| 			}*/ | ||||
|             // 模型仅存包名、初始耗电0、初始时长0(名称显示时再查) | ||||
|             // 模型仅存包名、初始耗电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 preCacheAllAppNames() { | ||||
|         packageToAppNameCache.clear(); // 清空旧缓存 | ||||
|         LogUtils.d(TAG, "开始预缓存包名-应用名称映射"); | ||||
|  | ||||
|         for (AppBatteryModel model : dataList) { | ||||
|             String packageName = model.getPackageName(); | ||||
|             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; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 强制添加目标包名(兜底逻辑,仅操作包名) | ||||
|      */ | ||||
| @@ -186,6 +214,8 @@ public class BatteryReportActivity extends Activity { | ||||
|  | ||||
|         // 2. 仅用包名创建模型,强制添加(不依赖名称查询) | ||||
|         dataList.add(0, new AppBatteryModel(TARGET_APP_PACKAGE, "0.0", 0)); | ||||
|         // 同步更新名称缓存 | ||||
|         packageToAppNameCache.put(TARGET_APP_PACKAGE, getAppNameByPackage(TARGET_APP_PACKAGE)); | ||||
|         LogUtils.d(TAG, "强制添加目标包名成功:" + TARGET_APP_PACKAGE); | ||||
|     } | ||||
|  | ||||
| @@ -193,57 +223,51 @@ public class BatteryReportActivity extends Activity { | ||||
|      * 更新运行时长到模型(用包名匹配,不涉及名称) | ||||
|      */ | ||||
|     private void updateAppRunTimeToModel() { | ||||
| 		/*if(appRunTimeCache.containsKey("aidepro.top")) | ||||
| 		 { | ||||
| 		 LogUtils.d(TAG, "aidepro.top OK2"); | ||||
| 		 }*/ | ||||
|  | ||||
| 		int nCount = 0; | ||||
|         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++; | ||||
|                 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)); | ||||
|         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) { | ||||
|     private void filterAppsByPackageAndName(String keyword) { | ||||
|         filteredList.clear(); | ||||
|         if (keyword == null || keyword.isEmpty()) { | ||||
|             filteredList.addAll(dataList); | ||||
|         } else { | ||||
|             String lowerKeyword = keyword.toLowerCase(); | ||||
|             String lowerKeyword = keyword.toLowerCase(); // 统一转为小写,实现不区分大小写搜索 | ||||
|             boolean isTargetMatched = false; | ||||
|  | ||||
|             for (AppBatteryModel model : dataList) { | ||||
|                 String packageNameLower = model.getPackageName().toLowerCase(); | ||||
|                 // 仅按包名包含关键词过滤 | ||||
|                 boolean isMatched = packageNameLower.contains(lowerKeyword); | ||||
|                 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); | ||||
|  | ||||
|                 if (isMatched) { | ||||
|                     filteredList.add(model); | ||||
|                     // 检查是否是目标包名 | ||||
|                     if (TARGET_APP_PACKAGE.equals(model.getPackageName())) { | ||||
|                     if (TARGET_APP_PACKAGE.equals(packageName)) { | ||||
|                         isTargetMatched = true; | ||||
|                     } | ||||
|                 } | ||||
| @@ -271,11 +295,11 @@ public class BatteryReportActivity extends Activity { | ||||
|         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) getSystemService(Context.USAGE_STATS_SERVICE); | ||||
|                 long endTime = System.currentTimeMillis(); | ||||
|                 long startTime = endTime - 24 * 3600 * 1000; // 近24小时 | ||||
|                 List<android.app.usage.UsageStats> statsList = manager.queryUsageStats( | ||||
| 					android.app.usage.UsageStatsManager.INTERVAL_DAILY, startTime, endTime); | ||||
|                     android.app.usage.UsageStatsManager.INTERVAL_DAILY, startTime, endTime); | ||||
|  | ||||
|                 // 仅存储包名和时长(不查名称) | ||||
|                 for (android.app.usage.UsageStats stats : statsList) { | ||||
| @@ -283,21 +307,16 @@ public class BatteryReportActivity extends Activity { | ||||
|                     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)); | ||||
| 					} | ||||
|                     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())); | ||||
|         LogUtils.d(TAG, String.format("应用运行时长列表数量%d。", runTimeMap.size())); | ||||
|         return runTimeMap; | ||||
|     } | ||||
|  | ||||
| @@ -359,8 +378,8 @@ public class BatteryReportActivity extends Activity { | ||||
| 				} | ||||
| 			}); | ||||
|  | ||||
|         // 重新应用包名过滤 | ||||
|         filterAppsByPackage(etSearch.getText().toString()); | ||||
|         // 重新应用“包名+名称”双维度过滤 | ||||
|         filterAppsByPackageAndName(etSearch.getText().toString()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -372,7 +391,7 @@ public class BatteryReportActivity extends Activity { | ||||
|         } | ||||
|  | ||||
|         android.app.usage.UsageStatsManager manager = | ||||
| 			(android.app.usage.UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); | ||||
|             (android.app.usage.UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); | ||||
|         if (manager == null) { | ||||
|             return false; | ||||
|         } | ||||
| @@ -380,7 +399,7 @@ public class BatteryReportActivity extends Activity { | ||||
|         long endTime = System.currentTimeMillis(); | ||||
|         long startTime = endTime - 1000 * 60; | ||||
|         List<android.app.usage.UsageStats> statsList = manager.queryUsageStats( | ||||
| 			android.app.usage.UsageStatsManager.INTERVAL_DAILY, startTime, endTime); | ||||
|             android.app.usage.UsageStatsManager.INTERVAL_DAILY, startTime, endTime); | ||||
|  | ||||
|         return statsList != null && !statsList.isEmpty(); | ||||
|     } | ||||
| @@ -423,28 +442,31 @@ public class BatteryReportActivity extends Activity { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * RecyclerView 适配器(仅在显示时通过包名查询应用名称) | ||||
|      * RecyclerView 适配器(支持从缓存获取名称,避免重复查询) | ||||
|      */ | ||||
|     public static class BatteryReportAdapter extends RecyclerView.Adapter<BatteryReportAdapter.ViewHolder> { | ||||
|         private Context mContext; | ||||
|         private List<AppBatteryModel> mDataList; | ||||
|         private PackageManager mPm; // 用于显示时查询应用名称 | ||||
|         private PackageManager mPm; // 兜底用:缓存无名称时查询 | ||||
|         private Map<String, String> mPackageToNameCache; // 新增:名称缓存映射 | ||||
|  | ||||
|         // Java7 显式构造(传入包管理工具) | ||||
|         public BatteryReportAdapter(Context context, List<AppBatteryModel> dataList, PackageManager pm) { | ||||
|         // Java7 显式构造(新增名称缓存参数) | ||||
|         public BatteryReportAdapter(Context context, List<AppBatteryModel> dataList,  | ||||
|                                     PackageManager pm, Map<String, String> packageToNameCache) { | ||||
|             this.mContext = context; | ||||
|             this.mDataList = dataList; | ||||
|             this.mPm = pm; // 持有包管理工具,供显示时查名称 | ||||
|             this.mPm = pm; | ||||
|             this.mPackageToNameCache = packageToNameCache; // 接收名称缓存 | ||||
|         } | ||||
|  | ||||
|         @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 显式非空判断(避免空指针) | ||||
| @@ -458,28 +480,35 @@ public class BatteryReportActivity extends Activity { | ||||
|             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()); | ||||
|             // 优先从缓存获取名称(避免重复调用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; | ||||
|                     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); | ||||
|                                                    model.getConsumption(), runTimeStr); | ||||
|             holder.tvConsumption.setText(consumptionText); | ||||
|  | ||||
|             // 优化显示:目标应用文字标蓝(通过包名匹配) | ||||
| @@ -504,7 +533,7 @@ public class BatteryReportActivity extends Activity { | ||||
|  | ||||
|         // ViewHolder(绑定系统布局的两个 TextView) | ||||
|         public static class ViewHolder extends RecyclerView.ViewHolder { | ||||
|             TextView tvAppName;     // 显示应用名称(通过包名查询) | ||||
|             TextView tvAppName;     // 显示应用名称(缓存/兜底获取) | ||||
|             TextView tvConsumption; // 显示耗电+运行时长(包名计算结果) | ||||
|  | ||||
|             // Java7 显式构造 | ||||
|   | ||||
| @@ -6,19 +6,19 @@ | ||||
|     android:orientation="vertical" | ||||
|     android:background="@android:color/white"> | ||||
|  | ||||
|     <!-- 搜索框:使用系统兼容背景 --> | ||||
|     <!-- 搜索框:提示文本改为“搜索应用名称或包名” --> | ||||
|     <EditText | ||||
|         android:id="@+id/et_search" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_margin="8dp" | ||||
|         android:padding="12dp" | ||||
|         android:hint="搜索应用包名" | ||||
|         android:hint="搜索应用名称或包名" | ||||
|         android:background="@android:drawable/btn_default_small" | ||||
|         android:inputType="text" | ||||
|         android:textSize="16sp"/> | ||||
|  | ||||
|     <!-- 应用列表:不依赖系统分割线,避免资源缺失 --> | ||||
|     <!-- 应用列表 --> | ||||
|     <androidx.recyclerview.widget.RecyclerView | ||||
|         android:id="@+id/rv_battery_report" | ||||
|         android:layout_width="match_parent" | ||||
| @@ -28,4 +28,3 @@ | ||||
|         android:layout_marginRight="8dp"/> | ||||
|  | ||||
| </LinearLayout> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 ZhanGSKen
					ZhanGSKen