首先,我知道在Android上不应该真正地关闭/重启应用程序。在我的用例中,我想在服务器向客户机发送一条特定信息的特定情况下对应用程序进行工厂重置。
用户只能使用应用程序的一个实例在服务器上登录(即不允许多个设备)。如果另一个实例获得了“登录”锁,那么该用户的所有其他实例都必须删除他们的数据(工厂重置),以保持一致性。
强制获取锁是可能的,因为用户可能会删除应用程序并重新安装它,这将导致不同的实例id,用户将无法再释放锁。因此,可以强制获取锁。
由于这种强制的可能性,我们需要始终检查一个具体实例是否拥有锁。这是在(几乎)对服务器的每个请求上完成的。服务器可能会发送一个“错误的锁定id”。如果检测到这种情况,客户机应用程序必须删除所有内容。
这就是用例。
我有一个活动A,启动登录活动L或应用程序的主活动B,这取决于一个sharedPrefs值。在启动L或B之后,它关闭自己,以便只有L或B在运行。在用户已经登录的情况下,B正在运行。
B启动C, C调用startService为IntentService服务。
(a) > b > c > d
从D的onHandleIntent方法,一个事件被发送到ResultReceiver R。
R现在通过向用户提供一个对话框来处理该事件,在该对话框中,用户可以选择对应用程序进行工厂重置(删除数据库、sharedPrefs等)。
在工厂重置后,我想重新启动应用程序(关闭所有活动),只启动A,然后启动登录活动L并完成自己:
(a) > l
对话框的onclick方法是这样的:
@Override
public void onClick(DialogInterface dialog, int which) {
// Will call onCancelListener
MyApplication.factoryReset(); // (Deletes the database, clears sharedPrefs, etc.)
Intent i = new Intent(MyApp.getContext(), A.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
MyApp.getContext().startActivity(i);
}
这是MyApp类:
public class MyApp extends Application {
private static Context context;
@Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
public static Context getContext() {
return context;
}
public static void factoryReset() {
// ...
}
}
问题是,如果我使用FLAG_ACTIVITY_NEW_TASK,活动B和C仍在运行。如果我点击登录活动的返回按钮,我看到C,但我想回到主屏幕。
如果我不设置FLAG_ACTIVITY_NEW_TASK我得到错误:
07-07 12:27:12.272: ERROR/AndroidRuntime(9512): android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
我不能使用活动的上下文,因为ServiceIntent D也可能从一个由AlarmManager启动的后台任务中调用。
那么我如何解决这个问题,使活动堆栈变成(A) >l呢?
有一个非常好的技巧。我的问题是一些非常老的c++ jni库泄露了资源。在某一时刻,它停止了运作。用户试图退出应用程序并再次启动它——但没有结果,因为完成一个活动并不等同于完成(或终止)进程。(顺便说一下,用户可以进入正在运行的应用程序列表并从那里停止它——这是可行的,但用户只是不知道如何终止应用程序。)
如果您想观察这个特性的效果,可以向您的活动添加一个静态变量,并在每次按下按钮时增加它。如果退出应用程序活动,然后再次调用应用程序,则此静态变量将保持其值。(如果应用程序真的退出了,变量将被分配初始值。)
(我不得不评论一下为什么我不想修复这个错误。这个库是几十年前编写的,从那时起就泄露了资源。管理层认为这种方法一直有效。提供修复方案而不是变通方案的成本……我想,你们应该明白了。)
现在,我怎么能重置一个jni共享(又名动态,.so)库到初始状态?
我选择将应用程序作为一个新进程重新启动。
诀窍是System.exit()关闭当前活动,Android重新创建应用程序时少了一个活动。
所以代码是:
/** This activity shows nothing; instead, it restarts the android process */
public class MagicAppRestart extends Activity {
// Do not forget to add it to AndroidManifest.xml
// <activity android:name="your.package.name.MagicAppRestart"/>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.exit(0);
}
public static void doRestart(Activity anyActivity) {
anyActivity.startActivity(new Intent(anyActivity.getApplicationContext(), MagicAppRestart.class));
}
}
调用活动只执行代码MagicAppRestart.doRestart(this);,调用活动的onPause()被执行,然后流程被重新创建。不要忘记在AndroidManifest.xml中提到这个活动
这种方法的优点是没有延迟。
UPD:它在Android 2上运行。x,但在Android 4有一些变化。
你可以简单地调用:
public static void triggerRebirth(Context context, Intent nextIntent) {
Intent intent = new Intent(context, YourClass.class);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(KEY_RESTART_INTENT, nextIntent);
context.startActivity(intent);
if (context instanceof Activity) {
((Activity) context).finish();
}
Runtime.getRuntime().exit(0);
}
ProcessPhoenix库中使用的是哪个
作为替代:
这是@Oleg Koshkin的回答的改进版本。
如果您真的想重新启动活动,包括终止当前进程,请尝试以下代码。把它放在一个helper类或者你需要它的地方。
public static void doRestart(Context c) {
try {
//check if the context is given
if (c != null) {
//fetch the packagemanager so we can get the default launch activity
// (you can replace this intent with any other activity if you want
PackageManager pm = c.getPackageManager();
//check if we got the PackageManager
if (pm != null) {
//create the intent with the default start activity for your application
Intent mStartActivity = pm.getLaunchIntentForPackage(
c.getPackageName()
);
if (mStartActivity != null) {
mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//create a pending intent so the application is restarted after System.exit(0) was called.
// We use an AlarmManager to call this intent in 100ms
int mPendingIntentId = 223344;
PendingIntent mPendingIntent = PendingIntent
.getActivity(c, mPendingIntentId, mStartActivity,
PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
//kill the application
System.exit(0);
} else {
Log.e(TAG, "Was not able to restart application, mStartActivity null");
}
} else {
Log.e(TAG, "Was not able to restart application, PM null");
}
} else {
Log.e(TAG, "Was not able to restart application, Context null");
}
} catch (Exception ex) {
Log.e(TAG, "Was not able to restart application");
}
}
这也将重新初始化jni类和所有静态实例。
可以使用Activity的startInstrumentation方法。您需要在manifest中实现空Instrumentation和指向。之后,你可以调用这个方法重新启动你的应用程序。
try {
InstrumentationInfo info = getPackageManager().queryInstrumentation(getPackageName(), 0).get(0);
ComponentName component = new ComponentName(this, Class.forName(info.name));
startInstrumentation(component, null, null);
} catch (Throwable e) {
new RuntimeException("Failed restart with Instrumentation", e);
}
我动态地获得Instrumentation类名,但是你可以硬编码它。有些是这样的:
try {
startInstrumentation(new ComponentName(this, RebootInstrumentation.class), null, null);
} catch (Throwable e) {
new RuntimeException("Failed restart with Instrumentation", e);
}
调用startInstrumentation make reload你的应用程序。阅读这个方法的描述。但如果像杀人程序一样,它可能是不安全的。