Back Stack学习之Android退出方法小结

来源:互联网 发布:java电商框架 编辑:程序博客网 时间:2024/05/10 21:24

在Android的开发过程中,有时会碰到将多个Activity组织在一起给用户提供服务的情况,例如:发送邮件程序,首先是进入邮件主界面,然后启动一个新的Activity用于填写新邮件内容,同时可以调出联系人列表用于插入收件人信息等等。在这个操作过程中 Android平台有一个专门用于管理Activities堆栈的机制,用它可以方便的线性记录Activities实例,当完成某个操作时,可以通过导航功能返回之前的Activity(通过按操作台的“Back”按钮),这个栈在Android中被成为Back Stack。


当一个未曾启动过的应用在HOME屏幕启动时,Android建立一个Back Stack,被启动的Activity成为Back Stack的根。当该Activity启动一个新的Activity,新Activity被压栈,之前的Activity状态被保存。当按下返回键,当前Activity出栈并且标志为可销毁,上一个Activity恢复之前的状态。Back Stack是一个后进先出的数据结构,下图源自Android开发者论坛,阐述了从Activity1跳转到Activity2再到Activity3然后按下返回键,这整个过程中Back Stack的变化。



上图详细阐述可以查考:

http://developer.android.com.nyud.net/guide/topics/fundamentals/tasks-and-back-stack.html

正因为Android的这种Back Stack管理机制的存在,所以如果当前Activity的位置不是在Back Stack栈底的话,直接调用this.finish()(相当于按下导航键的Back按键),是不会退出整个程序的,而只是回退到当前Back Stack栈顶的Activity。那么,当Android程序的Back Stack中有多个Activity的时候,我们如何退出程序呢?下面将结合我自己做的实验,就几种可能的方法进行讨论。为了更好的理解Activity的生命周期,建议参考一下下面链接:

http://blog.csdn.net/pku_android/article/details/7307625

实验环境:Android2.1虚拟机,Android2.2虚拟机

实验步骤截图:


     


实验描述:总共编写了三个Activity,分别是Activity1,Activity2,和Activity3,每次改动Activity中按钮事件的调用方法,进行比较分析。我重点分析了Activity2在整个过程中的状态变化,Activity2的每个方法都有Log输出。

(1)Activity本身的调用

this.finish();

在实验中,确实每次只能结束当前的Activity,不能退出整个应用。使用该方法,得到的状态变化与按键Back是一样的。该调用只是当前的Activity自身的finish方法,不会对其他Activity的工作状态产生影响,调用该方法会触发onDestroy()方法。如果需要的话,倒是可以在每个Activity跳转的时候就调用该方法,这样以来,Back Stack中就可以将每个使用过的Activity弹出,保持当前的Activity位于Back Stack栈顶且唯一,一旦当前Activity再调用finish(),就可以顺利的结束整个应用。但是该方法有一个缺点,那就是使用过的Activity都会被弹出Stack,用户不能再通过Back按钮回退到前面一个Activity的状态。所以只有在使用过的Activity工作状态无需保存的时候才使用这个方法。

(2)Dalvik VM的本地方法

// Java的常规退出方法

System.exit(0);

// Android系统的杀死进程方法

android.os.Process.killProcess(android.os.Process.myPid());

在本次实验中,在Activity3中调用System.exit(0)和android.os.Process.killProcess,其效果一样,也就是Activity3被销毁,但是接着会出现Activity2,仔细对比通过finish()回退,发现这里Activity2的onCreate()被调用并且Activity2被激活。也就是说,Activity3中调用这两种方法,确实结束过Activity2(否则Activity就不需要重新创建了),但是由于当前Back Stack栈顶的Activity2没有被移除,所以又重新onCreate()了一次。网上有评论说, Process.killProcess 最终是调用 linux  API kill() 发送 SIGKILL 信号,并且立即结束进程,但是由于Android中的ActivityManager一直监听着进程状态。如果发现进程被kill,会立即重启进行,并重启之前状态对应的Activity、Service、ContentProvider等。这种说法在一定程度上确实能够解释的通,但是如果真的是这样,那这两种方法的有效性就值得怀疑了。

(3)通过ActivityManager来结束包路径下的所有Activity

首先在AndroidManifest.xml增加权限:

<uses-permissionandroid:name="android.permission.RESTART_PACKAGES" />

然后在退出的地方写上:

ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);

am.restartPackage("lulicheng.android.quit"); // “lulicheng.android.quit”是目标包名

该方法在2.1的模拟器上运行通过,但是该方法在2.2的版本中是不可用的,实际上当我部署到2.2的模拟器上运行时,点击Activity3中的按钮,并不能顺利实现退出,按钮没有响应。查看文档,发现2.2新增了一个方法killBackgroundProcesses(String packageName),但是需要增加权限如下:

<uses-permissionandroid:name="android.permission.KILL_BACKGROUND_PROCESSES" />

我在实验的时候,该方法运行依然没有成功,大家有运行成功的可以分享下。

(4)隐藏式“退出”

IntentMyIntent = new Intent(Intent.ACTION_MAIN);

MyIntent.addCategory(Intent.CATEGORY_HOME);

startActivity(MyIntent);

这种退出只是表面意义上的退出,它只是跳转到桌面而已,当你点击应用图标重启应用的时候,发现还是原来那个状态,所以这不是真正意义上的结束程序。

小结:上面四种方法都是尝试使用Android自身的方法调用来结束应用,但是有的只能局限于当前单个的Activity,有的虽然能够结束整个应用程序(例如“restartPackage(String packagename)”),但是由于Android系统出于安全考虑等原因,已经在后续版本中被标记为“deprecated”。我们知道,Android系统设计的初衷就是希望将内存管理的部分交给Android虚拟机,换句话说,就是程序员只需要关注应用程序的“生”,而不需要关注应用程序的“死”,所以并不是Google它提供不了一个完美的解决方案,更大可能是它觉得没必要。而如果我们觉得“必要”,那么就需要充分利用Android系统本身的特性或者是Back Stack的特点来针对性的编码实现。

下面整理了几种可用的方法来实现应用的退出。

(A)利用Android本身的广播机制。

首先在每个需要结束的Activity中定义一个私有的BroadcastReceiver,并且进行注册,然后在需要退出的地方发送一个全局广播,其核心代码如下:

// 定义一个广播接收器

private BroadcastReceivermFinishReceiver = new BroadcastReceiver() {

   @Override

   public voidonReceive(Context context, Intent intent) {

      if ("finish".equals(intent.getAction())){

         finish();

      }

      unregisterReceiver(this);

   }

};

 

@Override

public voidonCreate(Bundle savedInstanceState) {

   super.onCreate(savedInstanceState);

   setContentView(R.layout.sample_a);

   // 注册广播

   IntentFilter filter = new IntentFilter();

   filter.addAction("finish");

   registerReceiver(mFinishReceiver, filter);

……

}

 

   // 7. 发送广播,利用广播机制来退出

getApplicationContext().sendBroadcast(new Intent("finish"));

 

(B)自定义一个Activity管理类,使用一个List存储所有待结束的Activity,并且在需要的地方调用相应方法进行遍历结束。核心代码如下:

创建一个Activity管理工具

publicclass ActivityTerminator {

   privatestatic List<Activity>activityList = newArrayList<Activity>();

 

   publicstaticvoidremove(Activity activity) {

      activityList.remove(activity);

   }

 

   publicstaticvoidadd(Activity activity) {

      activityList.add(activity);

   }

 

   publicstaticvoidfinishProgram() {

      for(Activity activity :activityList) {

        activity.finish();

      }

      System.exit(0);

   }

}

然后在每个Activity初始化时将它添加到管理列表中,在其销毁时,从管理列表中移除:

 

   @Override

   public voidonCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);

      setContentView(R.layout.sample_a);

      // 添加到管理的List

      ActivityTerminator.add(this);

      ……

   }

 

   @Override

   protected void onDestroy(){

      super.onDestroy();

      ActivityTerminator.remove(this);

   }

最后在需要退出的地方添加如下代码:

ActivityTerminator.finishProgram();

 

(C)设置一个全局变量Flag,然后利用Android生命周期的特点,在相应的方法里面进行判断,如果符合就调用本地的finish(),从而实现依次退出的功能。

仔细分析Activity的生命周期特点:






发现假如我们结束当前BackStack栈顶的Activity3的话,那么新的栈顶Activity2会获得焦点,进而处于激活状态,但这个过程中必须经过方法onRestart()的回调,那么在退出的Activity3之前,我们设置一个全局的Flag标识,然后在每个Activity的onRestart()函数中进行判定,标志符合就退出。

首先创建一个类存储该共用的Flag变量:

publicclass ActivityTerminator {

   publicstaticbooleanterminatorFlag =false;  

}

然后在每一个Activity中重写onRestart()方法,加入判定标识的语句:

   @Override

   protected void onRestart(){

      if (true ==ActivityTerminator.terminatorFlag) {

        this.finish();

      }

      // TODOAuto-generated method stub

      super.onRestart();

   }

最后在需要退出的地方设定该标志的值为true,并且调用finish().

   ActivityTerminator.terminatorFlag =true;

this.finish();

如此,便可以简便的实现BackStack中Activity的依次退出。

 

后话:针对Activity的退出,网上还有其他的一些方法,比如制造空指针异常等等,但是个人感觉这个方法不符合软件工程的规范,而且主动制造一个异常可能会给捕获处理该异常的功能模块带来未知的问题。

 

参考文献:

[1]http://developer.android.com.nyud.net/guide/topics/fundamentals/tasks-and-back-stack.html

[2] http://developer.android.com/reference/android/app/Activity.html

[3] http://blog.csdn.net/pku_android/article/details/7307625

[4] http://developer.android.com/reference/android/app/ActivityManager.html#restartPackage(java.lang.String)

[5] http://www.android123.com.cn/kaifafaq/670.html

[6] http://www.cnblogs.com/wader2011/archive/2011/10/10/2205161.html