如果服务是 运行,如何启动不同的 activity(并恢复状态)?
How start different activity (and recover state) if service is running?
我已经有了一个相当完整的位置跟踪应用程序,但仍有一个问题困扰着我。启动时,如果跟踪服务当前 运行ning.
,应用程序需要显示第二个 activity
- 我有 2 个活动(LoginActivity 和 MainActivity)和 2 个服务(LocationService 和 ReportingService - 运行 在它们自己的进程中)。
- 当应用程序跟踪位置时,2 个服务必须保持活动状态,但 MainActivity 可以被杀死。
- LocationService 创建一个不可移除的通知以确保用户知道他们正在被跟踪。当用户点击通知时,它将 restart/create 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() 中添加一些特殊逻辑来检查 运行ning 服务然后立即启动 MainActivity?
- 如果我这样做,我就会遇到另一个问题:MainActivity 将没有状态数据。如果用户没有通过通知打开它,是否有办法访问通知的意图?
- MainActivity.onSaveInstanceState() 将状态数据保存到一个包中,但我认为在 activity 被杀死后它就消失了,不是吗?
- 我曾考虑过将状态数据直接保存到 SharedPreferences,但这感觉太老套了。我的 "thing" 对象都实现了 Parcelable,因此它们可以放在 bundle 和 intent extras 中,但你不能将 Parcelables 放在 SharedPreferences 中。我可以通过 JSON 化它们来解决这个问题,但是对象的 JSON 表示仅用于与 RESTful 服务器通信,因此它们不包含一些特定于应用程序的数据.我不喜欢肮脏的技巧,所以我宁愿以 正确的 方式来做这件事。
我认为需要的步骤:
- 跟踪开始时,MainActivity 将帐户和资产的数据库 ID 存储在 SharedPreferences
- 创建LoginActivity时,检查LocationService是否运行ning
- 如果是这样,启动带有特殊 "restore from SharedPreferences" extra
的 MainActivity
- MainActivity.onCreate() 显示加载对话框并从 SharedPreferences
获取数据库 ID
- 向 ReportingService 广播我们需要获取与 DB ID 对应的对象
- 监听响应,然后更新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)。如果您的代码都在同一个进程中,这种方法可能适合您。
我已经有了一个相当完整的位置跟踪应用程序,但仍有一个问题困扰着我。启动时,如果跟踪服务当前 运行ning.
,应用程序需要显示第二个 activity- 我有 2 个活动(LoginActivity 和 MainActivity)和 2 个服务(LocationService 和 ReportingService - 运行 在它们自己的进程中)。
- 当应用程序跟踪位置时,2 个服务必须保持活动状态,但 MainActivity 可以被杀死。
- LocationService 创建一个不可移除的通知以确保用户知道他们正在被跟踪。当用户点击通知时,它将 restart/create 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() 中添加一些特殊逻辑来检查 运行ning 服务然后立即启动 MainActivity?
- 如果我这样做,我就会遇到另一个问题:MainActivity 将没有状态数据。如果用户没有通过通知打开它,是否有办法访问通知的意图?
- MainActivity.onSaveInstanceState() 将状态数据保存到一个包中,但我认为在 activity 被杀死后它就消失了,不是吗?
- 我曾考虑过将状态数据直接保存到 SharedPreferences,但这感觉太老套了。我的 "thing" 对象都实现了 Parcelable,因此它们可以放在 bundle 和 intent extras 中,但你不能将 Parcelables 放在 SharedPreferences 中。我可以通过 JSON 化它们来解决这个问题,但是对象的 JSON 表示仅用于与 RESTful 服务器通信,因此它们不包含一些特定于应用程序的数据.我不喜欢肮脏的技巧,所以我宁愿以 正确的 方式来做这件事。
我认为需要的步骤:
- 跟踪开始时,MainActivity 将帐户和资产的数据库 ID 存储在 SharedPreferences
- 创建LoginActivity时,检查LocationService是否运行ning
- 如果是这样,启动带有特殊 "restore from SharedPreferences" extra 的 MainActivity
- MainActivity.onCreate() 显示加载对话框并从 SharedPreferences 获取数据库 ID
- 向 ReportingService 广播我们需要获取与 DB ID 对应的对象
- 监听响应,然后更新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)。如果您的代码都在同一个进程中,这种方法可能适合您。