如果服务是 运行,如何启动不同的 activity(并恢复状态)?

How start different activity (and recover state) if service is running?

我已经有了一个相当完整的位置跟踪应用程序,但仍有一个问题困扰着我。启动时,如果跟踪服务当前 运行ning.

,应用程序需要显示第二个 activity

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

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

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


我认为需要的步骤:

  1. 跟踪开始时,MainActivity 将帐户和资产的数据库 ID 存储在 SharedPreferences
  2. 创建LoginActivity时,检查LocationService是否运行ning
  3. 如果是这样,启动带有特殊 "restore from SharedPreferences" extra
  4. 的 MainActivity
  5. MainActivity.onCreate() 显示加载对话框并从 SharedPreferences
  6. 获取数据库 ID
  7. 向 ReportingService 广播我们需要获取与 DB ID 对应的对象
  8. 监听响应,然后更新UI并取消加载对话框

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

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

感谢 的帮助指导,我解决了我的问题。我已经包含了此答案所需的所有内容,以帮助遇到此问题的其他人。


一旦创建了 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);
    }
}

这是我们用来确定我们是否正在跟踪的 Utilities 函数。它根据 ID 和 Intent 检查是否已经存在匹配的 PendingIntent。如果 PendingIntent 为 null,则表示未找到匹配项,因此我们将其视为通知不存在且我们未进行跟踪。在API23+中,可以直接查看通知是否存在,这样会稍微安全一些(因为如果服务被意外杀死,通知消失后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() 会被 OS 调用。 Android 可以在不调用它的情况下杀死它。它以前台优先级运行,因此不太可能被意外杀死,但它可能会导致在您不跟踪时存在 PendingIntent。

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

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