web-dev-qa-db-ja.com

アプリの使用情報を継続的に監視するサービスを作成する方法は?

手元の問題:連続して実行されるServiceを作成する必要があります。このサービスは5つのアプリを監視します5 Androidゲームは携帯電話にインストールされています。このサービスは次の情報を取得する必要があります。1.ゲームを開いて実行する回数は?2。各ゲームが実行されました。

例:このサービスがアプリにインストールされている場合そして、私はそれを1ヶ月間実行させました。アプリの画面にこの種の情報が必要です:

ゲームゲームの実行回数ゲームのプレイ時間

ゲーム1合計15時間20回プレイ

ゲーム2合計16時間25時間プレイ

..

..

ゲーム5合計12時間10回プレイ

Possible Approach:アプリケーションがロードされると、メモリに格納されます。アプリケーションの起動中にシステムが時間を記録することに注意してください。そして、アプリケーションが終了するか、再び時間を記録してバックグラウンドに置かれたとき。

したがって、アプリケーションが午後9時にメモリに持ち込まれ、午後9時30分にバックグラウンドに戻るとすると、ゲームプレイ時間は30分になります。次回アプリケーションが再生されると、何らかの変数などに保存された前の再生から期間が30に追加されます。アプリケーションがメモリに持ち込まれるたびに、再生中のアプリケーションのカウンタが1ずつ増加します。したがって、アプリケーションが再生される回数を提供します。

コーディング:Service in Androidで実際に作業したことがないので、私の問題に関連するチュートリアルは第二に、これを行うことができる別の方法がある場合、それも知りたいと思います。このプロジェクトを開始するために、コードスニペットを実際に使用することができます。

33
D'yer Mak'er

タスクはサードパーティのアプリケーションの監視に関するものであると書いたように、プロセスのリストを定期的に読み取り、フォアグラウンドプロセスを検出する以外に解決策はありません。これにはサービスが必要です。残念ながら、Androidは、フォアグラウンドプロセス変更のブロードキャストイベントなどの手段を提供しません。

タスクには、実際には多くのコードが必要です。少なくとも通常の答えでは考えられないほど多くのコードが必要です。ここにその一部を掲載していますが、起動間の同期や永続的な情報など、舞台裏に残された多くのニュアンスに対処する必要があります。これは単なるスケルトンです。

まず、アプリケーションオブジェクトをコーディングします。これは、すべてのインスタンス関連のものを登録するのに適した場所です。

MonitorApp

public class MonitorApp extends Application
{
  // actual store of statistics
  private final ArrayList<HashMap<String,Object>> processList = new ArrayList<HashMap<String,Object>>();

  // fast-access index by package name (used for lookup)
  private ArrayList<String> packages = new ArrayList<String>();

  public ArrayList<HashMap<String,Object>> getProcessList()
  {
    return processList;
  }

  public ArrayList<String> getPackages()
  {
    return packages;
  }

  // TODO: you need to save and load the instance data
  // TODO: you need to address synchronization issues
}

次に、アクティビティをドラフトします。

MonitorActivity

import static ProcessList.COLUMN_PROCESS_NAME;
import static ProcessList.COLUMN_PROCESS_PROP;
import static ProcessList.COLUMN_PROCESS_COUNT;
import static ProcessList.COLUMN_PROCESS_TIME;

public class MonitorActivity extends Activity implements MonitorService.ServiceCallback
{
  private ArrayList<HashMap<String,Object>> processList;
  private MonitorService backgroundService;
  private MyCustomAdapter adapter = null;
  private ListView listView = null;

  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main); // TODO: provide your layout
    listView = (ListView)findViewById(R.id.id_process_listview);
    createAdapter();

    this.bindService(
      new Intent(this, MonitorService.class),
      serviceConnection,
      Context.BIND_AUTO_CREATE);
  }

  private void createAdapter()
  {
    processList = ((MonitorApp)getApplication()).getProcessList();
    adapter = new MyCustomAdapter(this, processList, R.layout.complex_list_item,
    new String[]
    {
      COLUMN_PROCESS_NAME,
      COLUMN_PROCESS_PROP, // TODO: you may calculate and pre-fill this field
                           // from COLUMN_PROCESS_COUNT and COLUMN_PROCESS_TIME
                           // so eliminating the need to use the custom adapter
    },
    new int[]
    {
      Android.R.id.text1,
      Android.R.id.text2
    });

    listView.setAdapter(adapter);
  }

  // callback method invoked by the service when foreground process changed
  @Override
  public void sendResults(int resultCode, Bundle b)
  {
    adapter.notifyDataSetChanged();
  }

  private class MyCustomAdapter extends SimpleAdapter
  {
    MyCustomAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
    {
      super(context, data, resource, from, to);
    }

    @Override
    public View getView (int position, View convertView, ViewGroup parent)
    {
      View result = super.getView(position, convertView, parent);

      // TODO: customize process statistics display
      int count = (Integer)(processList.get(position).get(COLUMN_PROCESS_COUNT));
      int seconds = (Integer)(processList.get(position).get(COLUMN_PROCESS_TIME));

      return result;
    }
  }

  private ServiceConnection serviceConnection = new ServiceConnection()
  {
    @Override
    public void onServiceConnected(ComponentName className, IBinder service)
    {
      LocalBinder binder = (LocalBinder)service;
      backgroundService = binder.getService();
      backgroundService.setCallback(MonitorActivity.this);
      backgroundService.start();
    }

    @Override
    public void onServiceDisconnected(ComponentName className)
    {
      backgroundService = null;
    }
  };

  @Override
  public void onResume()
  {
    super.onResume();
    if(backgroundService != null)
    {
      backgroundService.setCallback(this);
    }
  }

  @Override
  public void onPause()
  {
    super.onPause();
    if(backgroundService != null)
    {
      backgroundService.setCallback(null);
    }
  }

}

このアクティビティは、実際にプロセスを監視するバックグラウンドワーカーサービスを起動します。サービス登録をアクティビティからアプリケーションインスタンスに移動できます。サービス自体は次のようなものです。

MonitorService

public class MonitorService extends Service
{
  private boolean initialized = false;
  private final IBinder mBinder = new LocalBinder();
  private ServiceCallback callback = null;
  private Timer timer = null;
  private final Handler mHandler = new Handler();
  private String foreground = null;
  private ArrayList<HashMap<String,Object>> processList;
  private ArrayList<String> packages;
  private Date split = null;

  public static int SERVICE_PERIOD = 5000; // TODO: customize (this is for scan every 5 seconds)

  private final ProcessList pl = new ProcessList(this)
  {
    @Override
    protected boolean isFilteredByName(String pack)
    {
      // TODO: filter processes by names, return true to skip the process
      // always return false (by default) to monitor all processes
      return false;
    }

  };

  public interface ServiceCallback
  {
    void sendResults(int resultCode, Bundle b);
  }

  public class LocalBinder extends Binder
  {
    MonitorService getService()
    {
      // Return this instance of the service so clients can call public methods
      return MonitorService.this;
    }
  }

  @Override
  public void onCreate()
  {
    super.onCreate();
    initialized = true;
    processList = ((MonitorApp)getApplication()).getProcessList();
    packages = ((MonitorApp)getApplication()).getPackages();
  }

  @Override
  public IBinder onBind(Intent intent)
  {
    if(initialized)
    {
      return mBinder;
    }
    return null;
  }

  public void setCallback(ServiceCallback callback)
  {
    this.callback = callback;
  }

  private boolean addToStatistics(String target)
  {
    boolean changed = false;
    Date now = new Date();
    if(!TextUtils.isEmpty(target))
    {
      if(!target.equals(foreground))
      {
        int i;
        if(foreground != null && split != null)
        {
          // TODO: calculate time difference from current moment
          // to the moment when previous foreground process was activated
          i = packages.indexOf(foreground);
          long delta = (now.getTime() - split.getTime()) / 1000;
          Long time = (Long)processList.get(i).get(COLUMN_PROCESS_TIME);
          if(time != null)
          { 
            // TODO: add the delta to statistics of 'foreground' 
            time += delta;
          }
          else
          {
            time = new Long(delta);
          }
          processList.get(i).put(COLUMN_PROCESS_TIME, time);
        }

        // update count of process activation for new 'target'
        i = packages.indexOf(target);
        Integer count = (Integer)processList.get(i).get(COLUMN_PROCESS_COUNT);
        if(count != null) count++;
        else
        {
          count = new Integer(1);
        }
        processList.get(i).put(COLUMN_PROCESS_COUNT, count);

        foreground = target;
        split = now;
        changed = true;
      }
    }
    return changed; 
  }


  public void start()
  {
    if(timer == null)
    {
      timer = new Timer();
      timer.schedule(new MonitoringTimerTask(), 500, SERVICE_PERIOD);
    }

    // TODO: startForeground(srvcid, createNotification(null));
  }

  public void stop()
  {
    timer.cancel();
    timer.purge();
    timer = null;
  }

  private class MonitoringTimerTask extends TimerTask
  {
    @Override
    public void run()
    {
      fillProcessList();

      ActivityManager activityManager = (ActivityManager)MonitorService.this.getSystemService(ACTIVITY_SERVICE);
      List<RunningTaskInfo> taskInfo = activityManager.getRunningTasks(1);
      String current = taskInfo.get(0).topActivity.getPackageName();

      // check if current process changed
      if(addToStatistics(current) && callback != null)
      {
        final Bundle b = new Bundle();
        // TODO: pass necessary info to UI via bundle
        mHandler.post(new Runnable()
        {
          public void run()
          {
            callback.sendResults(1, b);
          }
        });
      }
    }
  }

  private void fillProcessList()
  {
    pl.fillProcessList(processList, packages);
  }

}

このサービスは、プロセスリストの作成にヘルパークラスを使用します。

ProcessList

public abstract class ProcessList
{
  // process package name
  public static final String COLUMN_PROCESS_NAME = "process";

  // TODO: arbitrary property (can be user-fiendly name)
  public static final String COLUMN_PROCESS_PROP = "property";

  // number of times a process has been activated
  public static final String COLUMN_PROCESS_COUNT = "count";

  // number of seconds a process was in foreground
  public static final String COLUMN_PROCESS_TIME = "time";

  private ContextWrapper context;

  ProcessList(ContextWrapper context)
  {
    this.context = context;
  }

  protected abstract boolean isFilteredByName(String pack);

  public void fillProcessList(ArrayList<HashMap<String,Object>> processList, ArrayList<String> packages)
  {
    ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningAppProcessInfo> procInfo = activityManager.getRunningAppProcesses();

    HashMap<String, Object> hm;
    final PackageManager pm = context.getApplicationContext().getPackageManager();

    for(int i = 0; i < procInfo.size(); i++)
    {
      String process = procInfo.get(i).processName;
      String packageList = Arrays.toString(procInfo.get(i).pkgList);
      if(!packageList.contains(process))
      {
        process = procInfo.get(i).pkgList[0];
      }

      if(!packages.contains(process) && !isFilteredByName(process))
      {
        ApplicationInfo ai;
        String applicationName = "";

        for(int k = 0; k < procInfo.get(i).pkgList.length; k++)
        {
          String thisPackage = procInfo.get(i).pkgList[k];
          try
          {
            ai = pm.getApplicationInfo(thisPackage, 0);
          }
          catch(final NameNotFoundException e)
          {
            ai = null;
          }
          if(k > 0) applicationName += " / ";
          applicationName += (String)(ai != null ? pm.getApplicationLabel(ai) : "(unknown)");
        }

        packages.add(process);
        hm = new HashMap<String, Object>();
        hm.put(COLUMN_PROCESS_NAME, process);
        hm.put(COLUMN_PROCESS_PROP, applicationName);
        processList.add(hm);
      }
    }

    // optional sorting
    Comparator<HashMap<String, Object>> comparator = new Comparator<HashMap<String, Object>>()
    {
      public int compare(HashMap<String, Object> object1, HashMap<String, Object> object2) 
      {       
        return ((String)object1.get(COLUMN_PROCESS_NAME)).compareToIgnoreCase((String)object2.get(COLUMN_PROCESS_NAME));
      }
    };
    Collections.sort(processList, comparator);

    packages.clear();
    for(HashMap<String, Object> e : processList)
    {
      packages.add((String)e.get(COLUMN_PROCESS_NAME));
    }
  }

}

最後に、マニフェスト。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
    package="com.yourpackage"
    Android:versionCode="1"
    Android:versionName="1.0" >

    <uses-sdk Android:minSdkVersion="8" Android:targetSdkVersion="18" />

    <uses-permission Android:name="Android.permission.GET_TASKS" />

    <application
        Android:icon="@drawable/ic_launcher"
        Android:label="@string/app_name" >
        <activity
            Android:name=".MonitorActivity"
            Android:label="@string/app_name"
            Android:configChanges="orientation|keyboardHidden" >
            <intent-filter>
                <action Android:name="Android.intent.action.MAIN" />
                <category Android:name="Android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service Android:name=".MonitorService" />
    </application>

</manifest>

ご覧のとおり、既に多くのコードがあります。動作中のアプリケーションから部分的に抽出されていますが、ニーズに合わせて迅速に変更を加えたため、タイプミスがあったり、すべてのインポートがスキップされたりする可能性があります。

補足:Lollipop +

注意してください:最新のAndroid=バージョンは上記のアプローチを破りました。公式ドキュメントが getRunningTasks メソッドなどについて述べていることは次のとおりです。

Lollipopの時点では、このメソッドはサードパーティのアプリケーションでは使用できなくなりました。ドキュメント中心の最近のものが導入されたことにより、発信者に個人情報が漏洩する可能性があります。下位互換性のために、データの小さなサブセットを引き続き取得します。少なくとも呼び出し元のタスクと、場合によっては機密性がないことがわかっているホームなどのその他のタスクです。

これは行き過ぎであり、より選択的で便利な方法で行うことができると思います。言うまでもなく、これはプライバシー上の懸念があるGoogleの多くの組み込み機能を考慮すると、あまりにも演劇的と思われます。とにかく、これでは何もできません。

唯一の回避策は、Androidアクセシビリティサービス(詳細情報 here および here )を実装し、アプリケーションがフォーカスを取得および失うことですべてのアクションをインターセプトすることです。ユーザーはサービスを手動で有効にする必要があります!アプリケーションは何らかの方法でユーザーに有効にするよう指示する必要があります。

29
Stan

私の考え、

  1. 一部のデータを他のプロセスに公開してアクセスするアプリケーションを開発します。

    Google「使い方 コンテンツプロバイダー「。

    各アプリケーションは、実行したすべてのアクティビティの記録を保持する必要があります。このデータは公開され、アクセスされます。

  2. 常にサービスを実行する必要はありません このため。監視アプリが開かれるたびに。必要なすべてのアプリケーションからそのデータをフェッチし、それに応じて表示するだけです。

  3. ケースの場合、サーバー上のそのデータを定期的にバックグラウンドで送信します。 次に、サービスが必要です。しかし、まだあなた 永遠に実行する必要はありません。このようなことをしてください。

    i)サービスからデータを取得し、サーバーにアップロードします。

    ii)一定時間T1後にサービスを停止し、アラームをスケジュールして、ステップ1を再度実行するために再度独自のサービスを開始します。

    iii)T1は要件に依存します。サーバー上で必要なデータの更新方法。

上記で述べたことを実装する方法が必要な場合は、コードピースポインターを伝えることもできます。しかし、私はあなたがグーグルでそれを見つけ、最初に自分でそれを行うことをお勧めします。問題が発生した場合は、StackOverflowに別の具体的な質問を投稿してください。

お役に立てれば。 :)

編集:

第一に。最後にコーディングする必要があるのはあなただけです。ここからポインタとヘルプを入手してください。完全なコードを期待しないでください。以下の詳細を確認してください。

A)

アプリをコンテンツプロバイダーとして作成する必要があります。以下のリンクを確認してください。そして、その例に従ってください。他の人がデータを利用できるアプリになります。簡単です。

http://developer.Android.com/guide/topics/providers/content-providers.html

http://developer.Android.com/guide/topics/providers/content-provider-creating.html

http://www.vogella.com/articles/AndroidSQLite/article.html

ゲームアプリはすべて、アクセスするためにデータを公開する必要があります。これで完了です。

監視アプリのすべてのデータにアクセスして表示するだけです。

B)

あなたがサービスを介してこれを行いたい場合、私が言ったように。永久に実行する必要はありません。一度サービスを開始し、コンテンツプロバイダーからデータをロードして、必要な操作を実行するだけです。同じ操作を再実行するために、後の段階でサービスを呼び出すアラームを設定します。

10分ごとに、サービスがコンテンツプロバイダー(監視するゲームアプリ)からデータを取得できると仮定します。次のようになります。

public class MonitoringService extends Service {

    private static final String ACTION_FETCH = "action_fetch";
    private static final int REPEAT_DATALOAD_INTERVAL_MS = 10 * 60 * 1000; // 10 Min

    private static PendingIntent makeSelfPendingIntent(Context context) {
        PendingIntent intent = PendingIntent.getService(
                           context, 0, makeSelfIntent(context), 0);
        return intent;
    }

    private static Intent makeSelfIntent(Context context) {
        Intent intent = new Intent(context, MonitoringService.class);
        intent.setAction(ACTION_FETCH);
        return intent;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (intent != null && intent.getAction() != null) {
            String action = intent.getAction();
            if (action.equals(ACTION_FETCH)) {
                loadDataFromContentProviderDoWhateverYouWantThen();
                setAlarmToRedoLoad();
                stopSelf();

            }
        }
        return Super.onStartCommand(intent, flags, startId);
    }

    private void setAlarmToRedoLoad() {
        AlarmManager alarmManager = (AlarmManager) 
                                     getSystemService(Context.ALARM_SERVICE);
        alarmManager.set(AlarmManager.RTC_WAKEUP,
                     System.currentTimeMillis() + REPEAT_DATALOAD_INTERVAL_MS,
                     makeSelfPendingIntent(getApplicationContext()));
    }

    private void loadDataFromContentProviderDoWhateverYouWantThen(){
        check this link how to load data from content provider.
        as your games app are content providers. It should be loaded easily.
        then do whatever you want display, upload anything

        http://developer.Android.com/guide/topics/providers/content-provider-basics.html
    }

    // Call this method from onCreate of your monitoring app
    public static void start(Context context) {
        Intent intent = makeSelfIntent(context);
        context.startService(intent);
    }

}

C)監視アプリのユーザーがいつでもこのサービスを停止できるようにしてください。その場合、アラームをキャンセルすることを忘れないでください。そのため、バックグラウンドで永久に実行されることはありません。

D)放送ベースのものを作成することもできます。ゲームアプリがデータを保存するたびに、このイベントをブロードキャストし、そのブロードキャストをリッスンした後にサービスが呼び出されるようにする必要があります。それだけをロードします。

E)Phillが述べたように、Google Analyticsも利用できます。

実際にこれをどのように行うかは、要件によって異なります

お役に立てれば。それを試して、あなたが直面している他の問題を教えてください

9
Javanator

現在実行中のアプリケーションを取得するには、次を使用できます。

ActivityManager am = (ActivityManager) mContext.getSystemService(Activity.ACTIVITY_SERVICE);
String packageName = am.getRunningTasks(1).get(0).topActivity.getPackageName();

これは、実行中のサービスから定期的に確認できます。マニフェストで必要な権限を定義する必要もあります:Android.permission.GET_TASKS

1
peter.bartos

監視しようとしている5つのアプリをすべて所有しているという前提に基づいたソリューションを以下に示します。それ以外の場合は、別のソリューションを提供します。

ここで問題を分析しましょう。

  1. アプリの実行に関する情報サービス。
  2. 設計サービス
  3. データを保存する

Intentを使用して、実行中のアプリに関するメッセージをブロードキャストする必要があります。アクティビティonPauseおよびonResumeを使用してメッセージをブロードキャストします。

次に、この関数をonPauseおよびonResumeから呼び出します。

   public void broadcastMessage(String method_name){
        Intent intent=new Intent("com.service.myservice");
        Bundle bundle=new Bundle();
        bundle.putString("app_name", this.getApplicationInfo().packageName);
        bundle.putLong("time", System.currentTimeMillis());
            //just to keep track if it is on pause or in on resume
        bundle.putString("method", method_name);
        intent.putExtras(bundle);
        this.startService(intent);
    }

これを次のように呼び出すことができます

public void onPause(){
        super.onPause();
        broadcastMessage(Thread.currentThread().getStackTrace()[1].getMethodName());
    }

または

public void onPause(){
        super.onPause();
        broadcastMessage("onPause");
    }

ゲーマーはこれらの関数呼び出しの間でのみゲームをプレイしているため、なぜonPauseとonResumeなのでしょうか。

デザインサービス

理由を知りたい場合は、IntentServiceを使用して this を読んでください。

@Override
    protected void onHandleIntent(Intent intent) {
        // TODO Auto-generated method stub
        Bundle bundle=intent.getExtras();
        String app_name=bundle.getString("app_name");
        long time=bundle.getLong("time");
        //then do whatever you wanna do with this data

    }

保存中

ContentProviderはデータの保存に適したオプションであり、非常に強力です。 Androidでの統合と拡張はうまくいきますが、この場合、非常に単純な情報のみを保存したいようで、非常に単純な情報を選択することもできます。

以下のコードはSharedPreferencesを使用します。

 protected void storeData(String app_name, long time) {
        if ((app_name==null)||(time<=0)) return;
        SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(getApplicationContext());
        Editor editor = sharedPreferences.edit();
              //concatenating app name just to make key unique
        long start_time=sharedPreferences.getLong(app_name+"_start_time", -1);
        if (start_time==-1){
            //start time is empty means this is my start time
            // and user just started the app
            editor.putLong(app_name+"_start_time", time);
            editor.putLong(app_name+"_counter", sharedPreferences.getLong(app_name+"_counter", 0)+1);
        }else{
            //user is now closing the app. 
            long time_played=time-start_time;
            long total_time=sharedPreferences.getLong(app_name+"_total_time", 0);
            total_time=total_time+time_played;
            //saving total time
            editor.putLong(app_name+"_total_time", time);
            //removing start time for next pass
            editor.remove(app_name+"_start_time");
        }
        editor.commit();
    }
    public long getTotalTimeUserSpent(String app_name){
        SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(getApplicationContext());
        return sharedPreferences.getLong(app_name+"_total_time", 0);
    }
    public long numberOfTimeUserPlayed(String app_name){
        SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(getApplicationContext());
        return sharedPreferences.getLong(app_name+"_counter", 0);
    }

onHandleIntentからこれらのメソッドを呼び出すだけです。

パズルの最後のピースは配布です。これは、個別のアプリまたはこれら5つのアプリの一部で配布できます。個別のアプリが最も簡単な方法です。ただし、これら5つのアプリすべての一部としてこれを使用することを選択した場合は、 this の説明をお読みください。

0
minhaz

このサンプルコードを使用して、ブラウザが実行されているかどうかを確認します。

_ActivityManager activityManager = (ActivityManager) this.getSystemService( ACTIVITY_SERVICE );
    List<RunningAppProcessInfo> procInfos = actvityManager.getRunningAppProcesses();
    for(int i = 0; i < procInfos.size(); i++){
        if(procInfos.get(i).processName.equals("com.Android.browser")) {
            Toast.makeText(getApplicationContext(), "Browser is running", Toast.LENGTH_LONG).show();
        }
    }
_

サービスを作成するには、Service基本クラスから拡張するクラスを作成し、while(true)関数のonStartCommand()などの無限ループに関数を実装する必要があります。 _<service></service>_タグの間にあるマニフェストファイルにサービスを追加することを忘れないでください。 http://www.vogella.com/articles/AndroidServices/article.html

0
insomniac