@@ -26,8 +26,6 @@ import android.widget.Toast;
import androidx.recyclerview.widget.LinearLayoutManager ;
import androidx.recyclerview.widget.RecyclerView ;
import cc.winboll.studio.powerbell.R ;
import java.lang.reflect.Constructor ;
import java.lang.reflect.Method ;
import java.util.ArrayList ;
import java.util.Collections ;
import java.util.Comparator ;
@@ -38,52 +36,70 @@ import cc.winboll.studio.libappbase.LogUtils;
public class BatteryReportActivity extends Activity {
public static final String TAG = " BatteryReportActivity " ;
// 目标应用包名常量(统一维护,数据计算核心标识)
private static final String TARGET_APP_PACKAGE = " cc.winboll.studio.mymessagemanager " ;
private RecyclerView rvBatteryReport ;
private BatteryReportAdapter adapter ;
private List < AppBatteryModel > dataList = new ArrayList < AppBatteryModel > ( ) ;
private List < AppBatteryModel > filtered List = new ArrayList < AppBatteryModel > ( ) ; // 搜索过滤后的数据
// 数据列表:仅存储包名、耗电、时长(不存储名称,计算全程用包名)
private List < AppBatteryModel > data List = new ArrayList < AppBatteryModel > ( ) ;
private List < AppBatteryModel > filteredList = new ArrayList < AppBatteryModel > ( ) ;
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 < String , Long > appRunTimeCache = new HashMap < String , Long > ( ) ;
// 包管理工具(全局持有,避免重复获取)
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 < ApplicationInfo > appList = pm . getInstalledApplications ( PackageManager . GET_META_DATA ) ;
/**
* 加载所有应用(仅获取包名,不查询应用名称,数据计算核心步骤)
*/
private void loadAllAppPackage ( ) {
List < ApplicationInfo > 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 < String , Long > getAppRunTime ( ) {
Map < String , Long > runTimeMap = new HashMap < String , Long > ( ) ;
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 < android . app . usage . UsageStats > 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 . getP ackageName( ) , stats . getTotalTimeInForeground ( ) ) ;
long runTimeMs = stats . getTotalTimeInForeground ( ) ;
String p ackageName = 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 < String , Long > 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 < String , Long > 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 < AppBatteryModel > ( ) {
@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 < 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 ( ) ;
}
/**
* 数据模型(仅存储包名、耗电、时长,不存名称)
*/
public static class AppBatteryModel {
private String app Name;
private String packageName ;
private Stri ng consumption ;
private String package Name; // 核心标识:应用包名
private String consumption ; // 电量消耗( mAh)
private lo ng 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 < BatteryReportAdapter . ViewHolder > {
private Context mContext ;
private List < AppBatteryModel > mDataList ;
private PackageManager mPm ; // 用于显示时查询应用名称
public BatteryReportAdapter ( Context context , List < AppBatteryModel > dataList ) {
// Java7 显式构造(传入包管理工具)
public BatteryReportAdapter ( Context context , List < AppBatteryModel > 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 . getApp Name ( ) ) ; // 显示应用名称
holder . tvConsumption . setText ( " 电量消耗: " + model . getConsumption ( ) + " mAh " ) ;
String packageName = model . getPackage Name ( ) ;
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 ) ;
}