16

毎日使用されたすべてのアプリ パッケージとその期間を返すことを目的として、からクエリを実行しようとしていUsageStatsます。UsageStatsManager

コード:

public static List<UsageStats> getUsageStatsList(Context context){
    UsageStatsManager usm = getUsageStatsManager(context);
    Calendar calendar = Calendar.getInstance();
    long endTime = calendar.getTimeInMillis();
    calendar.add(Calendar.DAY_OF_YEAR, -1);
    long startTime = calendar.getTimeInMillis();

    List<UsageStats> usageStatsList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,startTime, endTime);
    return usageStatsList;
}

真夜中の直前に毎日発火するアラームと、クエリの使用状況統計があり、返されたデータを保存します。最初はすべてうまくいっているように見え、パッケージの結果とそのアクティブ時間を取得していましたが、1 時間ごとに結果を確認する機能を追加したところ、ここで奇妙な発見がありました。

の結果は、真夜中ではなく、さまざまな時間にリセットされているように見えました。これは、検索パラメーターとしてUsageStatsManager使用していたことを考えると予想していたことです。INTERVAL_DAILY

パッケージの「時間」結果を保存したデータから、(大まかなタイミング):

  • 午前3時
  • 正午
  • 午後3時
  • 夜中

パッケージのタイミングがいつリセットされるかの間に相関関係があることは認識していますが、これは意図されたものですか?

私はすでに次のスレッドを見て、そこから多くの情報を入手しました: UsageStatsManager の使用方法?

その結果: Android UsageStatsManager が間違った出力を生成しますか? queryUsageStatsコメントでは、から返されたデータは信頼できず、ランダムな結果が 返されていると述べています。

簡単なものが欠けているのか、それともUsageStatsManager正しく機能していないのでしょうか?

4

4 に答える 4

12

私も API 21 でこの動作に気付きました.UsageStats データは API 21 で十分に長く維持されていません.API 22 から正常に動作します.チェックインするとandroid /data/system/usagestats、API 21 に限られたエントリが見つかります. 21.

API 21+ の場合、APIに従ってusagestatsクエリを実行している間、丸一日を取得します。数時間以内にクエリを実行する場合は、独自のロジックでそれらを使用して反復する必要があります。INTERVAL_DAILYUsageStatsManagerqueryEvents

私は次の方法で試しました...

これは、すべてのアプリのデータをキャプチャするためのモーダル クラスです。

private class AppUsageInfo {
        Drawable appIcon;
        String appName, packageName;
        long timeInForeground;
        int launchCount;

        AppUsageInfo(String pName) {
            this.packageName=pName;
        }
}

List<AppUsageInfo> smallInfoList; //global var

これが方法です。簡単です。流れに沿って進みます。

void getUsageStatistics() {

UsageEvents.Event currentEvent;
List<UsageEvents.Event> allEvents = new ArrayList<>();
HashMap<String, AppUsageInfo> map = new HashMap <String, AppUsageInfo> ();

long currTime = System.currentTimeMillis();
long startTime currTime - 1000*3600*3; //querying past three hours

UsageStatsManager mUsageStatsManager =  (UsageStatsManager)
                    mContext.getSystemService(Context.USAGE_STATS_SERVICE);

        assert mUsageStatsManager != null;
UsageEvents usageEvents = mUsageStatsManager.queryEvents(usageQueryTodayBeginTime, currTime);

//capturing all events in a array to compare with next element

         while (usageEvents.hasNextEvent()) {
            currentEvent = new UsageEvents.Event();
            usageEvents.getNextEvent(currentEvent);
            if (currentEvent.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND ||
                    currentEvent.getEventType() == UsageEvents.Event.MOVE_TO_BACKGROUND) {
                allEvents.add(currentEvent);
                String key = currentEvent.getPackageName();
// taking it into a collection to access by package name
                if (map.get(key)==null)
                    map.put(key,new AppUsageInfo(key));
            }
        }

//iterating through the arraylist 
         for (int i=0;i<allEvents.size()-1;i++){
            UsageEvents.Event E0=allEvents.get(i);
            UsageEvents.Event E1=allEvents.get(i+1);

//for launchCount of apps in time range
             if (!E0.getPackageName().equals(E1.getPackageName()) && E1.getEventType()==1){
// if true, E1 (launch event of an app) app launched
                 map.get(E1.getPackageName()).launchCount++;
             }

//for UsageTime of apps in time range
            if (E0.getEventType()==1 && E1.getEventType()==2
                    && E0.getClassName().equals(E1.getClassName())){
                long diff = E1.getTimeStamp()-E0.getTimeStamp();
                phoneUsageToday+=diff; //gloabl Long var for total usagetime in the timerange
                map.get(E0.getPackageName()).timeInForeground+= diff;
            }
        }
//transferred final data into modal class object
        smallInfoList = new ArrayList<>(map.values());

}
于 2017-07-28T18:45:52.420 に答える
7

queryUsageStats信頼できる情報源ではないというあなたのコメントに同意します。私はUsageStatsManager少しの間 で遊んでいましたが、時刻に基づいて一貫性のない結果を返します。sを使用しUsageEventて必要な情報を手動で計算すると、(少なくとも毎日の統計の場合) はるかに信頼できることがわかりました。それらは時点であり、同じ入力に対して異なる出力を生成する奇妙な計算エラーがないためです。時間帯によります。

@Vishal の提案されたソリューションを使用して、独自のソリューションを考え出しました。

/**
 * Returns the stats for the [date] (defaults to today) 
 */
fun getDailyStats(date: LocalDate = LocalDate.now()): List<Stat> {
    // The timezones we'll need 
    val utc = ZoneId.of("UTC")
    val defaultZone = ZoneId.systemDefault()

    // Set the starting and ending times to be midnight in UTC time
    val startDate = date.atStartOfDay(defaultZone).withZoneSameInstant(utc)
    val start = startDate.toInstant().toEpochMilli()
    val end = startDate.plusDays(1).toInstant().toEpochMilli()

    // This will keep a map of all of the events per package name 
    val sortedEvents = mutableMapOf<String, MutableList<UsageEvents.Event>>()

    // Query the list of events that has happened within that time frame
    val systemEvents = usageManager.queryEvents(start, end)
    while (systemEvents.hasNextEvent()) {
        val event = UsageEvents.Event()
        systemEvents.getNextEvent(event)

        // Get the list of events for the package name, create one if it doesn't exist
        val packageEvents = sortedEvents[event.packageName] ?: mutableListOf()
        packageEvents.add(event)
        sortedEvents[event.packageName] = packageEvents
    }

    // This will keep a list of our final stats
    val stats = mutableListOf<Stat>()

    // Go through the events by package name
    sortedEvents.forEach { packageName, events ->
        // Keep track of the current start and end times
        var startTime = 0L
        var endTime = 0L
        // Keep track of the total usage time for this app
        var totalTime = 0L
        // Keep track of the start times for this app 
        val startTimes = mutableListOf<ZonedDateTime>()
        events.forEach {
            if (it.eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
                // App was moved to the foreground: set the start time
                startTime = it.timeStamp
                // Add the start time within this timezone to the list
                startTimes.add(Instant.ofEpochMilli(startTime).atZone(utc)
                        .withZoneSameInstant(defaultZone))
            } else if (it.eventType == UsageEvents.Event.MOVE_TO_BACKGROUND) {
                // App was moved to background: set the end time
                endTime = it.timeStamp
            }

            // If there's an end time with no start time, this might mean that
            //  The app was started on the previous day, so take midnight 
            //  As the start time 
            if (startTime == 0L && endTime != 0L) {
                startTime = start
            }

            // If both start and end are defined, we have a session
            if (startTime != 0L && endTime != 0L) {
                // Add the session time to the total time
                totalTime += endTime - startTime
                // Reset the start/end times to 0
                startTime = 0L
                endTime = 0L
            }
        }

        // If there is a start time without an end time, this might mean that
        //  the app was used past midnight, so take (midnight - 1 second) 
        //  as the end time
        if (startTime != 0L && endTime == 0L) {
            totalTime += end - 1000 - startTime
        }
        stats.add(Stat(packageName, totalTime, startTimes))
    }
    return stats
}

// Helper class to keep track of all of the stats 
class Stat(val packageName: String, val totalTime: Long, val startTimes: List<ZonedDateTime>)

いくつかの観察:

  • のタイムスタンプEventは UTC です。これが、クエリの開始/終了時刻をデフォルトのタイム ゾーンから UTC に変換し、各イベントの開始時刻を元に戻す理由です。これはしばらく私を手に入れました...
  • これは、アプリが 1 日の開始前にフォアグラウンドにあった (つまり、ユーザーが真夜中前にアプリを開いた) か、発言の終了後にバックグラウンドに移動した (つまり、ユーザーがまだアプリを持っていた) エッジ ケースを考慮に入れています。その日の午後 11 時 59 分を過ぎると手前に表示されます)。免責事項: これらのエッジ ケースはまだ実際にテストしていません。
  • ユーザーが真夜中過ぎにアプリを使用する場合、終了時間として午後 11 時 59 分 59 秒を選択しました。明らかに、これを午前 0 時から 1 ミリ秒ずれるように変更することも、これを計算する方法に応じて単純に午前 0 時に変更することもできます。を削除して- 1000、必要なものに置き換えるだけです。
  • 私のユースケースでは、合計フォアグラウンド時間 + 開始時間が必要だったので、その情報を収集しました。ただし、Statクラスとコードを微調整して、必要な情報を取得できます。たとえば、必要に応じて、終了時刻や、1 日にアプリが起動された回数を追跡できます。
  • 日付を扱うのが簡単だったので、ここでは Java 8 time ライブラリを使用しています。これを Android で使用するには、ThreeTenABPライブラリを使用します。

これが役立つことを願っています!

于 2018-06-01T16:52:43.873 に答える