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

时间:2017-09-13 14:53:47

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

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

  • 我有2个活动(LoginActivity和MainActivity)和2个服务(LocationService和ReportingService - 都在他们自己的进程中运行)。
  • 当应用程序跟踪位置时,2个服务必须保持活动状态,但可以杀死MainActivity。
  • LocationService会创建一个不可移动的通知,以确保用户知道他们正在被跟踪。当用户点击通知时,它将重新启动/创建MainActivity - 意图具有包含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()将状态数据保存到一个包中,但我认为在活动被杀之后它已经消失了,不是吗?
  • 我已经考虑过将状态数据直接保存到SharedPreferences,但这会让人感觉不舒服。我的事情&#34;对象都实现了Parcelable,因此可以将它们放在bundle和intent extras中,但是你不能将Parcelables放在SharedPreferences中。我可以通过JSONifying来解决这个问题,但是对象的JSON表示仅用于与RESTful服务器通信,因此它们不包含一些特定于应用程序的数据。我不喜欢肮脏的黑客,所以我宁愿以正确的方式做这件事。

我认为需要采取的措施:

  1. 跟踪开始时,MainActivity将帐户和资产的DB ID存储在SharedPreferences
  2. 创建LoginActivity时,请检查LocationService是否正在运行
  3. 如果是这样,请使用SharedPreferences&#34;中的特殊&#34;还原来启动MainActivity。额外
  4. MainActivity.onCreate()显示加载对话框并从SharedPreferences获取数据库ID
  5. 向ReportingService广播我们需要获取与数据库ID相对应的对象
  6. 收听回复,然后更新用户界面并取消加载对话框
  7. 解决这些问题的最佳方法是什么?

2 个答案:

答案 0 :(得分:1)

LoginActivity.onCreate()中,您应检查跟踪服务是否正在运行,如果是,请立即将用户转发至MainActivity。您希望这样做,就像用户点击Notification一样,这样您就可以使用PendingIntent中存储的Notification中的附加内容。没问题。

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

答案 1 :(得分:1)

由于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)。如果您的代码都在同一个过程中,那么这种方法可能适合您。