【问题标题】:How start different activity (and recover state) if service is running?如果服务正在运行,如何启动不同的活动(并恢复状态)?
【发布时间】:2017-09-13 14:53:47
【问题描述】:

我有一个相当完整的位置跟踪应用程序,但一个问题仍然困扰着我。启动时,如果跟踪服务当前正在运行,则应用需要显示第二个活动。

  • 我有 2 个活动(LoginActivity 和 MainActivity)和 2 个服务(LocationService 和 ReportingService - 都在各自的进程中运行)。
  • 当应用跟踪位置时,这 2 个服务必须保持活动状态,但可以终止 MainActivity。
  • Lo​​cationService 创建一个不可删除的通知,以确保用户知道他们正在被跟踪。当用户点击通知时,它将重新启动/创建 MainActivity - Intent 具有包含 MainActivity 所需的所有相关数据(他们有权访问的帐户和资产,以及选择了哪些)的附加内容。

但是,如果应用正在跟踪并且 MainActivity 已被终止,然后用户通过常规启动器图标打开应用,他们将被带到 LoginActivity。 LoginActivity 是应用程序的典型入口点,如清单中所定义:

<intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>

如果 LocationService 当前正在跟踪,我如何让 MainActivity 用作备用入口点?它应该如何恢复之前的状态数据?

  • 是否需要在 LoginActivity 的 onCreate() 中添加一些特殊的逻辑来检查正在运行的服务,然后立即启动 MainActivity?
  • 如果我这样做,我就会遇到另一个问题:MainActivity 将没有状态数据。如果用户不通过通知打开通知,是否有办法访问通知的意图?
  • MainActivity.onSaveInstanceState() 将状态数据保存到一个包中,但我认为在 Activity 被杀死后它就消失了,不是吗?
  • 我曾考虑将状态数据直接保存到 SharedPreferences 中,但这感觉很不合时宜。我的“事物”对象都实现了 Parcelable,因此它们可以放在捆绑包和 Intent Extras 中,但你不能将 Parcelables 放在 SharedPreferences 中。我可以通过对它们进行 JSON 化来解决这个问题,但对象的 JSON 表示仅用于与 RESTful 服务器通信,因此它们不包含一些特定于应用程序的数据。我不喜欢肮脏的黑客,所以我宁愿以正确的方式这样做。

我认为需要的步骤:

  1. 跟踪开始时,MainActivity 将帐户和资产的数据库 ID 存储在 SharedPreferences 中
  2. Lo​​ginActivity创建时,检查LocationService是否正在运行
  3. 如果是这样,请使用特殊的“从 SharedPreferences 恢复”额外启动 MainActivity
  4. MainActivity.onCreate() 显示加载对话框并从 SharedPreferences 获取数据库 ID
  5. 向 ReportingService 广播我们需要获取与 DB ID 对应的对象
  6. 监听响应,然后更新 UI 并取消加载对话框

解决这些问题的最佳方法是什么?

【问题讨论】:

    标签: android android-intent android-activity android-manifest


    【解决方案1】:

    LoginActivity.onCreate() 中,您应该检查跟踪服务是否正在运行,如果是,请立即将用户转发到MainActivity。您希望像用户单击Notification 一样执行此操作,以便可以使用存储在Notification 中的PendingIntent 中的额外内容。没问题。

    LoginActivity.onCreate() 中这样做:

    // Find the PendingIntent that is stored in the Notification
    Intent notificationIntent = new Intent(this, MainActivity.class);
    // Add any ACTION or DATA or flags that you added when you created the
    //  Intent and PendingIntent when you created the Notification
    
    // Now get the `PendingIntent` that is stored in the notification
    //  (make sure you use the same "requestCode" as you did when creating
    //  the PendingIntent that you stored in the Notification)
    PendingIntent pendingIntent = PendingIntent.getActivity(this,
        requestCode, notificationIntent, PendingIntent.FLAG_NO_CREATE);
    // Now start MainActivity with the Intent wrapped in the PendingIntent
    //  (it contains the extras)
    pendingIntent.send();
    // Finish LoginActivity (if you usually do that when you launch MainActivity)
    finish();
    

    【讨论】:

      【解决方案2】:

      感谢David Wasser 的帮助,我解决了我的问题。我已在此答案中包含了所有需要的内容,以帮助遇到此问题的其他人。


      LoginActivity 创建后,它(间接)检查我们是否正在跟踪:

      @Override
      protected void onCreate(Bundle savedInstanceState)
      {
          super.onCreate(savedInstanceState);
      
          // It's possible that we're already tracking. If so, we want to skip LoginActivity and start MainActivity.
          if(Utilities.doesTrackingPendingIntentExist(this))
          {
              if(Utilities.isLocationServiceRunning(this))
              {
                  recreateMainActivity();
              }
              else
              {
                  Log.e(TAG, "TRACKING? PendingIntent exists, but LocationService isn't running.");
                  Utilities.deleteTrackingPendingIntent(this);
              }
          }
      
          // LocationService wasn't running, so we can display the login screen and proceed as normal
          setContentView(R.layout.activity__login);
          ...
      

      如果是这样,它会获取 LocationService 为通知创建的 PendingIntent,并使用它来启动 MainActivity。

      private void recreateMainActivity()
      {
          // This intent is an abstract description of what we want to accomplish: starting MainActivity
          Intent intentToStartMainActivity = new Intent(this, MainActivity.class);
      
          // Get the PendingIntent that's stored in the notification (using the "requestCode" that LocationService used
          // when it created the PendingIntent)
          PendingIntent pendingIntent = PendingIntent.getActivity
          (
              this, LocationService.NOTIFICATION_ID, intentToStartMainActivity, PendingIntent.FLAG_NO_CREATE
          );
      
          try
          {
              Log.i(TAG, "LocationService is running. Attempting to recreate MainActivity!");
      
              // Now start MainActivity with the Intent wrapped in the PendingIntent (which also contains the required data in extras)
              pendingIntent.send();
              finish();
          }
          catch(PendingIntent.CanceledException e)
          {
              Log.e(TAG, "It seems that our PendingIntent was cancelled. Hmmm....", e);
          }
      }
      

      这是我们用来确定是否正在跟踪的实用程序函数。它根据 ID 和 Intent 检查是否已经存在匹配的 PendingIntent。如果 PendingIntent 为 null,则表示未找到匹配项,因此我们认为这意味着通知不存在并且我们不跟踪。在 API 23+ 中,您可以直接检查通知是否存在,这会比这稍微安全一些(因为如果服务意外终止,通知消失后 PendingNotification 可能会继续存在)。

      public static boolean doesTrackingPendingIntentExist(Context context)
      {
          Intent intentToStartMainActivity = new Intent(context, MainActivity.class);
      
          // Get the PendingIntent that's stored in the notification (using the "requestCode" that LocationService used
          // when it created the PendingIntent)
          PendingIntent pendingIntent = PendingIntent.getActivity
          (
              context, LocationService.NOTIFICATION_ID, intentToStartMainActivity, PendingIntent.FLAG_NO_CREATE
          );
      
          if(pendingIntent == null)
          {
              Log.i(TAG, "TRACKING? No matching PendingIntent found. LocationService probably isn't running.");
              return false;
          }
          else
          {
              Log.i(TAG, "TRACKING? A matching PendingIntent was found. LocationService seems to be running.");
              return true;
          }
      }
      

      另一种方法,通过遍历所有正在运行的服务以查找名称匹配来检查服务是否正在运行。由于我的 LocationService 并不总是在 onDestroy() 之后立即死亡,因此仅此一项并不是检查我们是否正在跟踪的完全可靠的方法。可以与其他方法结合使用,更加确定跟踪状态。

      public static boolean isLocationServiceRunning(Context context)
      {
          Log.i(TAG, "TRACKING? Reviewing all services to see if LocationService is running.");
      
          ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
      
          // Go through every service until we find LocationService
          for(ActivityManager.RunningServiceInfo service : activityManager.getRunningServices(Integer.MAX_VALUE))
          {
              Log.v(TAG, "TRACKING?    service.getClassName() = " + service.service.getClassName());
      
              if(LocationService.class.getName().equals(service.service.getClassName()))
              {
                  Log.i(TAG, "TRACKING? LocationService is running!");
                  return true;
              }
          }
      
          Log.i(TAG, "TRACKING? LocationService is NOT running.");
          return false;
      }
      

      注意: LocationService 在完成跟踪时取消 PendingIntent 非常重要,否则这将不起作用。不幸的是,不能保证 LocationService.onDestroy() 会被操作系统调用。 Android可以在不调用它的情况下杀死它。它以前台优先级运行,因此不太可能被意外杀死,但可能会导致 PendingIntent 在您不跟踪时存在。

      结合这两个实用功能是确定我们是否正在跟踪的最安全方法。

      旁注: 我尝试使用静态 volatile 布尔值来跟踪 LocationService 中的跟踪状态,但不同的进程似乎使用不同的 ClassLoaders 有自己的内存空间(感谢 David)。如果您的代码都在同一个进程中,那么该方法可能适合您。

      【讨论】:

      • 不同的操作系统进程有完全不同的内存空间和他们自己的虚拟机,而不仅仅是不同的类加载器。您不能在操作系统进程之间使用static 变量共享数据。
      • 感谢您的澄清。在第一次研究这个问题时,我忽略了线程的提及,而没有考虑让我的服务在不同进程中运行的含义。可变变量对于多线程程序可能就足够了,尽管在某些情况下需要同步变量,正如这里所解释的(对于任何想要了解更多信息的人):What is the difference between a process and a thread in Java?(正如大卫指出的那样,这些都不适用于这里)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多