安卓TV或者盒子重启后应用使用时间丢失的问题

安卓TV或者盒子重启后应用使用时间丢失的问题

Loss of app usage time after android TV or box reboots

在搜索引擎我只找到了一篇与这个问题相关的链接,大家可以看一下,他的解决办法是APP自己保存使用数据。

android - AndroidTV: UsageStatsManager not returning details for apps results after Reboot - Stack Overflow

As we had experienced that after a device power cycle , Android Tv box won’t return the usage stats of installed application. Here is a workaround I followed .

  • We fetch the details of installed application then store only last used time in a Hash map.
  • And we used a custom comparator to compare via last time used and sort our Installed application details list (ResolveInfo) , for recently used app list.
  • Now to overcome the situation of device power cycle scenario, We are maintaining a Hash map locally in app . Where we will store the installed App’s Last used time as Long millis (epoch) . lastTimeUsedMap = new HashMap(); And we update the map every time we get a new data from UsageStatsManager.
  • We simplify the map object and store as string in shared preference.
  • And after reboot first we will find in usage stats if installed package is having a time stamp or not . If not we will get the time from stored MAP .
private void SortByRecentlyUsed(final List<ResolveInfo> info, final Context context){

    /*load LastTimeUsedMap from pref*/
    lastTimeUsedMap = loadLastTimeUsedMap(context);

    UsageStatsManager usm = getUsageStatsManager(context);
    Calendar calendar = Calendar.getInstance();
    long endTime = calendar.getTimeInMillis();
    calendar.add(Calendar.MONTH, -1);
    long startTime = calendar.getTimeInMillis();

    final List<UsageStats> usageStatsList = usm.queryUsageStats(UsageStatsManager.INTERVAL_BEST, startTime, endTime);

    /*Update AppRecentTimeUsedMap with latest data from UsageStatsManager*/
    updateAppRecentTimeUsedMap(usageStatsList,info);

    class RecentUseComparator implements Comparator<ResolveInfo> {
        @Override
        public int compare(ResolveInfo lhs, ResolveInfo rhs) {
            String lhsPackageName=lhs.activityInfo.applicationInfo.packageName;
            String rhsPackageName=rhs.activityInfo.applicationInfo.packageName;
            long lhsUsedTime = getLastUsedTime(lhsPackageName);
            long rhsUsedTime = getLastUsedTime(rhsPackageName);
            return (lhsUsedTime > rhsUsedTime) ? -1 : (lhsUsedTime == rhsUsedTime) ? 0 : 1;
        }

        private long getLastUsedTime(String packageDetails) {
            long appRecentUsedtime = -1;
            if (appRecentTimeUsedMap.containsKey(packageDetails)) {
                appRecentUsedtime = appRecentTimeUsedMap.get(packageDetails);
                }
            return appRecentUsedtime;
        }
    }
    RecentUseComparator mRecentComp = new RecentUseComparator();
    Collections.sort(info, mRecentComp);
    /*Save the updated  LastTimeUsedMap in pref*/
    saveLastTimeUsedMap(lastTimeUsedMap, context);

}

private void updateAppRecentTimeUsedMap(List<UsageStats> usageStatsList,List<ResolveInfo> info){

    String packageName=null;
    if (usageStatsList != null) {
        for (ResolveInfo Rinfo : info) {
            packageName = Rinfo.activityInfo.applicationInfo.packageName;
            boolean added = false;
            for (UsageStats usageStats : usageStatsList) {
                if (packageName.equalsIgnoreCase(usageStats.getPackageName())) {
                    appRecentTimeUsedMap.put(usageStats.getPackageName(), usageStats.getLastTimeUsed());
                    updateLastTimeUsedMap(usageStats.getPackageName(), usageStats.getLastTimeUsed());
                    added=true;
                }
            }
            if (!added && lastTimeUsedMap.containsKey(packageName)) {
                appRecentTimeUsedMap.put(packageName, lastTimeUsedMap.get(packageName));
            }
        }

    }

}

private void updateLastTimeUsedMap(String packageName,Long timeStamp){
    lastTimeUsedMap.put(packageName, timeStamp);
}

/**
 * Return Map containing Package name and recent used time from preference
 *
 * @param context
 * @return Map<String,Long>
 */
private Map<String,Long> loadLastTimeUsedMap(Context context){
    Map<String,Long> outputMap = new HashMap<String,Long>();
    SharedPreferences pSharedPref = context.getSharedPreferences(LAST_TIME_USED_PREFS, Context.MODE_PRIVATE);
    try{
        if (pSharedPref != null){
            String jsonString = pSharedPref.getString(LAST_TIME_USED_MAP, (new JSONObject()).toString());
            JSONObject jsonObject = new JSONObject(jsonString);
            Iterator<String> keysItr = jsonObject.keys();
            while(keysItr.hasNext()) {
                String key = keysItr.next();
                Long value = jsonObject.getLong(key);
                outputMap.put(key, value);
            }
        }
    }catch(Exception e){
        e.printStackTrace();
    }
    return outputMap;
}
/**
 * Save the updated map containing Package name and recent used time in preference
 *
 * @param inputMap
 * @param context
 */
private void saveLastTimeUsedMap(Map<String,Long> inputMap, Context context){
    final SharedPreferences sharedPreferences = context.getSharedPreferences(LAST_TIME_USED_PREFS,Context.MODE_PRIVATE);
    if (sharedPreferences != null){
        JSONObject jsonObject = new JSONObject(inputMap);
        String jsonString = jsonObject.toString();
        final SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.remove(LAST_TIME_USED_MAP).commit();
        editor.putString(LAST_TIME_USED_MAP, jsonString);
        editor.commit();
    }
}

具体的代码我没怎么看,他应该是没做时间跳变的处理。

原因分析

其实是因为绝大多数电视机和盒子都没有做RTC电路来保存时间,导致断电或者重启后使机器恢复到出厂设置的时间。 这样的后果就是:UsageStatsService初始化的时候会删除掉未来的数据,导致应用使用时间丢失,详情见如下代码注释。

https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/usage/java/com/android/server/usage/UsageStatsDatabase.java

public void init(long currentTimeMillis) {
        synchronized (mLock) {
            for (File f : mIntervalDirs) {
                f.mkdirs();
                if (!f.exists()) {
                    throw new IllegalStateException("Failed to create directory "
                            + f.getAbsolutePath());
                }
            }

            checkVersionAndBuildLocked();
            indexFilesLocked();

            //这里会删除掉未来数据
            //比如我使用机器的时候是2022年1月1号,此时记录的是这天的时间,但是重启后时间丢失了,变成了出厂设置时间,例如2018年1月1号,这就造成了数据的未来时
            // Delete files that are in the future.
            for (TimeSparseArray<AtomicFile> files : mSortedStatFiles) {
                final int startIndex = files.closestIndexOnOrAfter(currentTimeMillis);
                if (startIndex < 0) {
                    continue;
                }

                final int fileCount = files.size();
                for (int i = startIndex; i < fileCount; i++) {
                    files.valueAt(i).delete();
                }

                // Remove in a separate loop because any accesses (valueAt)
                // will cause a gc in the SparseArray and mess up the order.
                for (int i = startIndex; i < fileCount; i++) {
                    files.removeAt(i);
                }
            }
        }
    }

解决

知道了原因,那么解决方案就简单了,我们只需要在写入数据的时候记录下时间戳,然后开机的时候,设置回去,就可以避免数据的未来时。

UsageStatsService.java - Android Code Search

 void flushToDisk() {
        synchronized (mLock) {
            // Before flush to disk, report FLUSH_TO_DISK event to signal UsageStats to update app
            // usage. In case of abrupt power shutdown like battery drain or cold temperature,
            // all UsageStats has correct data up to last flush to disk.
            // The FLUSH_TO_DISK event is an internal event, it will not show up in IntervalStats'
            // EventList.
            Event event = new Event(FLUSH_TO_DISK, SystemClock.elapsedRealtime());
            event.mPackage = DEVICE_EVENT_PACKAGE_NAME;
            reportEventToAllUserId(event);
            flushToDiskLocked();
        }
        mAppStandby.flushToDisk();
       //注意有些机器的date命令设置这个时间会减少8小时,所以根据实际情况手动调整一下:SystemProperties.set("persist.sys.reboot.time",  String.valueOf(System.currentTimeMillis() / 1000 + 8 * 60 * 60 + 60));
        SystemProperties.set("persist.sys.reboot.time", String.valueOf(System.currentTimeMillis() / 1000));
    }

时间戳保存了,那么什么时候设置回去呢,因为UsageStatsService的初始化时间比较早,我们不能在framework进行设置,需要在init进程进行设置

#!/system/bin/sh
time=$(getprop persist.sys.reboot.time)
current_time=$(date +%s)
if [ -n "$time" ] && [ "$time" -gt "$current_time" ]; then
	date @$time set
	hwclock -w
fi

init.rc

service set_time /vendor/bin/set_time.sh
    class main
    user root
    group root
    oneshot

此外还需要进行selinux的配置,这里不再列出。

最后,有些厂商可能更改了安卓按键的处理逻辑,导致按电源键执行不到flushToDiskLocked()方法,导致数据没有保存,这也需要注意。

参考

标签: UsageStats使用状态管理 | Coolqi`s Blog

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值