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

2023-12-26

手头的问题: 我必须创建一个Service连续运行。该服务监控您手机上安装的 5 个应用程序(例如 5 个 Android 游戏)。该服务需要获取以下信息: 1.游戏被打开并运行了多少次? 2. 每场比赛的运行​​时间。

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

Game 游戏运行次数 游戏时长

游戏 1 玩了 20 次,总共 15 小时

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

..

..

游戏 5 玩了 10 次,总共 12 小时

可能的方法: 当应用程序加载时,它就会进入内存。在应用程序启动时注意系统时钟时间。当应用程序结束或置于后台时,再次记录时间。

假设一个应用程序在晚上 9:00 进入内存并在晚上 9:30 退出到后台,那么我们的游戏时间为 30 分钟。下次播放应用程序时,持续时间将从存储在某种变量中的上一次播放的时间添加到 30,依此类推。 每次将应用程序放入内存时,正在播放的计数器应增加一。因此给我们提供了应用程序的播放次数。

Coding:我不知道Service在 Android 中,因为我从未真正研究过它们。任何与我手头的问题相关的教程都会非常有帮助。 其次,是否有其他方法可以做到这一点。我也想知道这一点。我真的可以使用一些代码片段来启动这个项目。


正如您所写,该任务是关于监视第 3 方应用程序的,除了定期读取进程列表并检测前台进程之外,没有其他解决方案。您需要为此提供服务。遗憾的是,Android并没有提供针对前台进程变化的广播事件等手段。

事实上,该任务需要大量代码,至少比普通答案可以包含的代码多得多。我在这里发布了其中的一部分,但您应该解决幕后留下的许多细微差别,例如启动之间的同步和持久信息。这只是一个骨架。

首先,让我们编写一个应用程序对象,这是注册所有实例相关内容的好地方。

监控应用程序

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
}

然后我们起草一个活动。

监控活动

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);
    }
  }

}

该活动启动一个后台工作服务,该服务实际上监视进程。您可以将服务注册从活动移动到应用程序实例中。服务本身是这样的:

监控服务

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);
  }

}

该服务利用辅助类来构建进程列表。

进程列表

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>

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

附录:棒棒糖+

请注意:最新的 Android 版本打破了上述方法。这是官方文档所说的获取运行任务 http://developer.android.com/reference/android/app/ActivityManager.html#getRunningAppProcesses()方法及其他:

从 LOLLIPOP 开始,此方法不再可供第三方应用程序使用:引入以文档为中心的最近记录意味着它可能会将个人信息泄露给调用者。为了向后兼容,它仍然会返回一小部分数据:至少是调用者自己的任务,可能还有一些其他已知不敏感的任务,例如家庭。

我认为这是一种矫枉过正的做法,可以通过更有选择性和更方便的方式来完成。更不用说考虑到谷歌的许多内置功能都存在隐私问题,这似乎过于戏剧化。无论如何,我们对此无能为力。

唯一的解决方法是实现 Android 辅助功能服务(更多信息here http://developer.android.com/guide/topics/ui/accessibility/services.html and here http://developer.android.com/training/accessibility/service.html)并拦截应用程序从那里获得和失去焦点的所有操作。用户应手动启用该服务!您的应用程序应该以某种方式指导用户这样做。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 找不到参数的方法 dependencyResolutionManagement()

    我正在尝试使用老师给我的一个项目 但它显示了一个错误 Settings file Users admin AndroidStudioProjects HTTPNetworking settings gradle line 1 A probl
  • 如何快速自动发送FCM或APNS消息?

    我正在开发一项后端服务 通过 FCM 或 APNS 向移动应用程序发送推送通知 我想创建一个可以在一分钟内运行的自动化测试 并验证服务器是否可以成功发送通知 请注意 我不一定需要检查通知是否已送达 只需检查 FCM 或 APNS 是否已成功
  • android中向sqlite中插入大量数据

    目前 我必须一次向我的 Android 中插入超过 100 亿条数据 然而 内存不足的问题会使程序崩溃 sqlite 插入测试非常简单 只需使用 for 循环生成 sql 插入命令并通过 开始 和 提交 进行包装 private Array
  • Android - 从资产中解析巨大(超大)JSON 文件的最佳方法

    我正在尝试从资产文件夹中解析一些巨大的 JSON 文件 我如何加载并添加到 RecyclerView 我想知道解析这种大文件 大约 6MB 的最佳方法是什么 以及您是否知道可以帮助我处理此文件的良好 API 我建议您使用GSON lib h
  • 无法获取log.d或输出Robolectrict + gradle

    有没有人能够将 System out 或 Log d 跟踪从 robolectric 测试输出到 gradle 控制台 我在用Robolectric Gradle 测试插件 https github com robolectric robo
  • 是否可以将数组或对象添加到 Android 上的 SharedPreferences

    我有一个ArrayList具有名称和图标指针的对象 我想将其保存在SharedPreferences 我能怎么做 注意 我不想使用数据库 无论 API 级别如何 请检查SharedPreferences 中的字符串数组和对象数组 http
  • Adobe 是否为其 PDF 阅读器提供 Android SDK 或 API? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我希望能够在我们的应用程序内的视图中显示本地 PDF 文件 在 Android 4 03 下的平板电脑上运行 目前 我们将 Adob eR
  • 在画布上绘图

    我正在编写一个 Android 应用程序 它可以在视图的 onDraw 事件上直接绘制到画布上 我正在绘制一些涉及单独绘制每个像素的东西 为此我使用类似的东西 for int x 0 x lt xMax x for int y 0 y lt
  • Android 模拟器插件无法初始化后端 EGL 显示

    我在 Cloudbees 上设置了 Jenkins 作业 并且可以在那里成功签出并编译我的 Android 项目 现在我想在 android 模拟器中运行一些 JUnit 测试并添加 Android 模拟器插件 我将 显示模拟器窗口 选项设
  • 在 HTTPResponse Android 中跟踪重定向

    我需要遵循 HTTPost 给我的重定向 当我发出 HTTP post 并尝试读取响应时 我得到重定向页面 html 我怎样才能解决这个问题 代码 public void parseDoc final HttpParams params n
  • 尝试将相机切换回前面但出现异常

    尝试将相机切换回前面 但出现异常 找不到 问题请检查并帮助 error 01 27 11 49 00 376 E AndroidRuntime 30767 java lang RuntimeException Unable to start
  • 无法展开 RemoteViews - 错误通知

    最近 我收到越来越多的用户收到 RemoteServiceException 错误的报告 我每次给出的堆栈跟踪如下 android app RemoteServiceException Bad notification posted fro
  • 原色(有时)变得透明

    我正在使用最新的 SDK 版本 API 21 和支持库 21 0 2 进行开发 并且在尝试实施新的材料设计指南时遇到了麻烦 材料设计说我需要有我的primary color and my accent color并将它们应用到我的应用程序上
  • 如何使用 IF 检查 TextView 可见性

    我有一个 onCheckedChangeListener 来根据选择的单选按钮显示文本视图 我有 1 个疑问和 1 个难题 想知道是否有人可以帮助我 问题 您能否将单选组默认检查值设置为 否 单选按钮 以便一开始就不会检查任何内容 问题 如
  • 如何使用InputConnectionWrapper?

    我有一个EditText 现在我想获取用户对此所做的所有更改EditText并在手动将它们插入之前使用它们EditText 我不希望用户直接更改中的文本EditText 这只能由我的代码完成 例如通过使用replace or setText
  • Android访问远程SQL数据库

    我可以直接从 Android 程序访问远程 SQL 数据库 在网络服务器上 吗 即简单地打开包含所有必需参数的连接 然后执行 SQL 查询 这是一个私人程序 不对公众开放 仅在指定的手机上可用 因此我不担心第三方获得数据库访问权限 如果是这
  • .isProviderEnabled(LocationManager.NETWORK_PROVIDER) 在 Android 中始终为 true

    我不知道为什么 但我的变量isNetowrkEnabled总是返回 true 我的设备上是否启用互联网并不重要 这是我的GPSTracker class public class GPSTracker extends Service imp
  • 如何根据 gradle 风格设置变量

    我想传递一个变量test我为每种风格设置了不同的值作为 NDK 的定义 但出于某种原因 他总是忽略了最后味道的价值 这是 build gradle apply plugin com android library def test andr
  • 如何在Xamarin中删除ViewTreeObserver?

    假设我需要获取并设置视图的高度 在 Android 中 众所周知 只有在绘制视图之后才能获取视图高度 如果您使用 Java 有很多答案 最著名的方法之一如下 取自这个答案 https stackoverflow com a 24035591
  • android sdk 的位置尚未在 Windows 操作系统的首选项中设置

    在 Eclipse 上 我转到 windows gt Android SDK 和 AVD Manager 然后弹出此消息 Android sdk 的位置尚未在首选项中设置 进入首选项 在侧边栏找到 Android 然后会出现一个 SDK 位

随机推荐

  • WPF 如何存储语言字典?

    根据https msdn microsoft com en us library system windows controls spellcheck v vs 110 aspx https msdn microsoft com en us
  • Python Pandas 跨列累积和并在另一个新列中获取结果

    我有包含 col1 col10 的数据框 我想计算跨列的累积总和并动态创建新列 即 cum col1 cum col10 我研究了 cumsum 但这给出了最终的累积和 如何在创建新列时实现累积总和 数据框看起来像 id col1 col2
  • imagecreatefrompng 根本不起作用

    我已经用 mime 类型检查了文件 如果是 jpg 或 gif 则可以完美使用 src imagecreatefromjpeg tmpName and src imagecreatefromgif tmpName 但如果图像是png src
  • 重复单词的正则表达式

    我是正则表达式新手 我不太清楚如何编写一个正则表达式来 匹配 任何重复的连续单词 例如 巴黎在the the spring Not 那个那个相关的 你笑什么 是my my正则表达式那么糟糕 是否有一个正则表达式可以匹配上面所有的粗体字符串
  • 使用 Notepad++ 通过 FTP 连接到服务器

    我正在使用 Notepad 6 5 3 版本 来编辑我的文件 我想使用 Notepad 连接到我的服务器 我使用这个方法 打开 Notepad gt 插件 gt NppFTP gt 显示 NppFTP 窗口 在 NppFTP 窗口中 Cli
  • 在 .NET 中将复杂的布尔条件从字符串转换为布尔值

    我需要将复杂的表达式从字符串解析为布尔值 它只能包含 布尔值 真 假 括号 AND OR 操作数 Eg bool Parse true false false false true false 知道如何实现这一目标吗 这是一个狡猾的评估器类
  • Elasticsearch - IndicesClient.put_settings 不起作用

    我正在尝试更新我原来的索引设置 我的初始设置如下所示 client create index movies body settings number of shards 1 number of replicas 0 analysis fil
  • iPhone“书签到主屏幕”会删除 cookie 和会话吗?

    现在我正在开发一个基于网络的应用程序 用户必须首先登录 当我通过 iPhone Safari 打开页面 登录并重新启动 Safari 时 我仍然处于登录状态 Cookie 和会话 ID 仍然设置 但是 当我使用 添加到主屏幕 添加此页面时
  • spring Net 与企业库

    我一直在网上搜索有关网络框架的信息 从现在起我可以在我的项目中使用它来 保留它 我几乎已经做出了决定 但我想要一些关于该方向的参考信息市场正在继续发展 我在谷歌中找不到任何关于哪个是最常用的 哪个最有未来等等的信息 我也一直在阅读其他框架
  • CoreAnimation CALayer 和 CATextLayer 组合

    I am just playing around with CA lately Now I am kind of stuck This is the thing I want to animate 就目前而言 我已经让圆形动画正常工作了 我
  • gradle.buildStarted 未触发

    我无法在我的 gradle 构建中触发 buildStarted 不确定我做错了什么 我有一个像这样的根项目 gradle 文件 版本 1 0 buildscript repositories maven url http repo jfr
  • 如何在极坐标中的多个条件下使用“when”、“then”和“otherwise”?

    我有一个包含三列的数据集 将检查 A 列中的字符串 如果字符串匹配foo or spam 同一行中其他两列的值L and G应该改为XX 为此我尝试了以下方法 df pl DataFrame A foo ham spam egg L A54
  • Netbeans 代码生成问题:如何编辑自动生成的代码?

    好的 由于 GUI 设计器自动生成的代码 这里出现了一个 netbeans 问题 我使用 netbeans UI 设计器设计了一个 GUI 它是为 java 1 6 编译的 完成 UI 后 我意识到我应该为 Java 1 4 而不是 1 6
  • 函数式编程:列表是否只包含唯一项?

    我有一个未排序的列表 想知道其中的所有项目是否都是唯一的 我天真的做法是 val l List 1 2 3 4 3 def isUniqueList l List Int new HashSet l size l size 这是我能想到的最
  • 每 3 个 div 包裹在一个 div 中

    是否可以使用nth child使用选择器包装 3 个 div wrapAll 我似乎无法算出正确的方程式 so div div div div div div div div div div div div div div 变成 div d
  • 在适用于 Android 的 OpenGL ES 2.0 中激活/使用 GL_TEXTURE1

    我正在尝试使用 GL TEXTURE1 纹理单元来绘制一个简单的形状 我知道如何使用标准 GL TEXTURE0 绘制它 但是当更改它时 有些东西不起作用 我认为从下面的代码中 我只需更改以下内容 glActiveTexture GL TE
  • 如何在android中自动启动服务?

    在Android应用程序中 总是扩展Activity 入口是onCreate 所以看来用户必须选择应用程序并单击才能启动它 如果错误 请告诉我 抱歉 那么 如何实现一个无需用户点击启动应用程序而在后台运行的服务呢 使用 IntentRece
  • js-xlsx :写入 .xlsx 文件时保留单元格样式

    我已手动创建了一个 xlsx 文件 我已向某些行添加了不同的颜色 并且某些单元格具有自定义日期格式 我正在使用 js xlsx npm 模块从 xlsx 文件读取数据 在写回同一 xlsx 文件以更新某些单元格值时 所选颜色和日期格式会丢失
  • 如何为网站添加浏览器选项卡图标(favicon)?

    我一直在开发一个网站 我想在浏览器选项卡中添加一个小图标 我如何在 HTML 中执行此操作以及我需要将其放置在代码中的何处 例如标头 我有一个 png我想将其转换为图标的徽标文件 有关的 HTML 在浏览器选项卡上设置图像 https st
  • 如何创建一个持续监控应用程序使用信息的服务?

    手头的问题 我必须创建一个Service连续运行 该服务监控您手机上安装的 5 个应用程序 例如 5 个 Android 游戏 该服务需要获取以下信息 1 游戏被打开并运行了多少次 2 每场比赛的运行 时间 例如 假设我在我的应用程序中安装