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

3
我有一个相当完整的位置追踪应用程序,但仍存在一个问题。当应用程序启动时,如果跟踪服务当前正在运行,则需要显示第二个活动。
我有两个活动(LoginActivity和MainActivity)和两个服务(LocationService和ReportingService - 运行在它们自己的进程中)。当应用程序正在跟踪位置时,两个服务必须保持活动状态,但MainActivity可以被杀死。LocationService创建一个不可删除的通知,以确保用户知道他们正在被跟踪。当用户点击通知时,它将重新启动/创建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作为备用入口点使用?它应该如何恢复先前的状态数据?
步骤可能需要如下:
1. 当跟踪开始时,MainActivity将Accounts和Assets的DB ID存储在SharedPreferences中。 2. 创建LoginActivity时,请检查LocationService是否正在运行。 3. 如果是,则使用特殊的“从SharedPreferences还原”extra启动MainActivity。 4. MainActivity.onCreate()显示加载对话框并从SharedPreferences获取DB ID。 5. 广播到ReportingService,我们需要获取与DB ID相对应的对象。 6. 监听响应,然后更新UI并取消加载对话框。
如果我需要在LoginActivity的onCreate()中添加一些特殊逻辑以检查运行的服务,然后立即启动MainActivity吗?但是这样做会有另一个问题:MainActivity没有状态数据。如果用户不通过通知打开它,是否有一种方法访问通知的意图?
MainActivity.onSaveInstanceState()将状态数据保存到Bundle中,但我认为在活动被杀死后,它就消失了,对吗?我考虑直接将状态数据保存到SharedPreferences中,但这感觉不好。我的“thing”对象都实现了Parcelable,因此可以将它们放入bundle和intent extras中,但是不能将Parcelable放入SharedPreferences中。我可以通过JSON化它们来绕过此问题,但对象的JSON表示仅用于与RESTful服务器通信,因此它们不包括某些应用程序特定数据。我不喜欢肮脏的技巧,所以我宁愿以正确的方式处理这个问题。
最好的方法是什么?
2个回答

1
LoginActivity.onCreate()中,您应该检查跟踪服务是否正在运行,如果是,则立即将用户转发到MainActivity。您希望这样做就像用户单击通知一样,以便您可以使用存储在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();

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为空,则意味着没有找到匹配项,因此我们认为通知不存在且我们没有在跟踪。在API 23+中,您可以直接检查通知是否存在,这比使用PendingNotification更安全(如果服务意外终止,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存在。

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

附注: 我尝试使用静态易失性布尔值来跟踪LocationService中的跟踪状态,但是不同的进程似乎使用不同的类加载器具有自己的内存空间(感谢David)。如果您的代码都在同一个进程中,则该方法可能适用于您。


1
不同的操作系统进程拥有完全不同的内存空间和自己的虚拟机,而不仅仅是不同的类加载器。你不能使用static变量在操作系统进程之间共享数据。 - David Wasser
谢谢您的澄清。一开始看到这个问题时,我忽略了线程的提及,没有考虑到在不同进程中运行服务的影响。对于多线程程序,volatile变量可能已经足够,但也有一些情况需要使用synchronized变量,可以在这里了解更多:Java中进程和线程的区别是什么?(正如David所指出的,这两者都不适用于此处) - CanProgram

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接