如何创建一个持续监控应用程序使用信息的服务?

 那些触动你的回忆 发布于 2023-02-12 15:23

手头的问题:我必须创建一个Service连续运行的.这项服务监控5个应用程序说你的手机上安装了5个安卓游戏.此服务需要获取以下信息:1.游戏开启和运行的次数是多少?2.每场比赛的运行​​时间.

例如:说如果我在我的应用程序中安装了此服务.我让它运行了一个月.我需要在应用程序的屏幕上显示此类信息:

游戏 游戏运行次数游戏 持续时间

第1场比赛共20次,共15小时

游戏2共16次,总共玩25小时

..

..

第5场比赛共10次,共12小时

可能的方法:当应用程序加载时,它会进入内存.注意到系统在应用程序启动时计时.当应用程序结束或放在后台注意时间再次.

所以说如果一个应用程序在晚上9点被带到内存并且在晚上9:30退出到后台,这给了我们30分钟的游戏时间.下次播放应用程序时,持续时间将从存储在某种变量中的上一个播放添加到30,依此类推.每次将应用程序带入内存时,正在播放的计数器应该增加1.因此,给我们一个应用程序播放的次数.

编码:Service在Android中根本不知道,因为我从来没有真正研究它们.任何与我手头的问题相关的教程都会非常有用.其次,如果还有另一种方法可以做到这一点.我也想知道.我真的可以使用一些代码片段来启动这个项目.

2 个回答
  • 正如您所写,任务是关于监控第三方应用程序,除了定期读取进程列表和检测前台进程之外,没有其他解决方案.你需要一个服务.不幸的是,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>
    

    正如您所看到的,它已经是很多代码了.它部分地从一个正在运行的应用程序中提取,但我根据您的需求进行了快速更改,因此可能存在拼写错误,所有导入都被跳过等等.不过,我希望这会有所帮助.

    ADDENDUM:Lollipop +

    注意:最新的Android版本打破了上述方法.以下是官方文档中有关getRunningTasks方法和其他方法的内容:

    从LOLLIPOP开始,此方法不再适用于第三方应用程序:引入以文档为中心的最新版本意味着它可以将人员信息泄露给调用者.为了向后兼容,它仍将返回其数据的一小部分:至少是调用者自己的任务,以及可能已知不敏感的其他一些任务,例如home.

    我认为这是一种矫枉过正,可以更有选择性和方便的方式完成.更值得一提的是,考虑到Google的许多内置功能存在隐私问题,这似乎太戏剧化了.无论如何,我们无能为力.

    唯一的解决方法是实现Android辅助功能服务(此处和此处有更多信息)并拦截所有操作,其中应用程序从那里获得并失去焦点.用户应手动启用该服务!您的应用程序应该以某种方式指导用户这样做.

    2023-02-12 15:25 回答
  • 我的想法,

      开发应用程序,将其部分数据公开给其他进程以访问它们.

      谷歌"如何使用内容提供商 ".

      您的每个应用程序都需要记录他们执行的所有活动.这些数据将被公开访问.

      您不需要为此始终运行服务.每当您的监控应用程序打开时.它只需要从您想要的所有应用程序中获取数据并相应地显示它们.

      对于您希望在后台定期在服务器上提交该数据的情况.然后你需要一个服务.但你仍然不需要永远运行它.做这样的事情.

      i)从服务获取数据并上传到服务器.

      ii)停止服务并在T1时间后安排警报再次开始自己的服务以再次执行步骤1.

      iii)T1取决于要求.如何刷新服务器上的数据.

    如果你想要如何实现我上面说的话,我甚至可以告诉你代码片段指针.但我建议你在google上找到它并先自己做.如果您遇到任何困难,请在StackOverflow上发布另一个特定问题.

    希望这可以帮助.:)

    编辑:

    首先.它只有你最终必须编码.从这里获取指针和帮助.不要期望完整的代码.请查看以下详细信息.

    一个)

    您需要将您的应用设为内容提供商.检查以下链接.并按照其中的示例进行操作.您将最终进入一个应用程序,其他人可以使用其数据.它很容易前进.

    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.

    您希望如何实际执行此操作现在取决于您的要求

    希望这可以帮助.尝试一下,让我知道你面临的更多问题

    2023-02-12 15:25 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有