Android安卓-四大启动模式是什么?启动模式详解,场景运用。

转自 “逆水当行舟” 的博客,收藏记录。

启动模式

启动模式是什么

有这样的场景:

  • 几个应用之间,例如我的App要使用拍照功能,我需要调用系统的相机App,这分明就是两个不同的应用程序,分别运行在不同的进程,但是当我调用完成相机后,按下返回键可以返回我的App
  • 还有例如在我们的App打开浏览器、微博之类的应用,然后跳转到浏览器,使用完成浏览器的功能,不断按下返回键,可以回到我们的应用。而不是像点击桌面上浏览器图标启动浏览器那样,不断返回键,最后回到的是桌面。
  • 生成重复页面。 
    以前遇到过这样的Bug: 
    1. 消息推送,通知栏弹出Notification,点击Notification跳转到指定Activity,但是如果我现在页面就停留在那个指定的Activity,会再次打开我当前的Activity,这样返回的时候回退的页面和当前页面一样,感官上就会很奇怪。
    2. 登录的时候,登录成功跳转到主页,按下两次登录按钮,生成了两个主页。一些有启动延迟的页面(往往是动画,网络造成)也会有这样的情况。

为什么要研究启动模式

  1. 有时候我们的App需要生成给其他App调用的Activity,例如浏览器应用,照相机应用
  2. 解决生成重复页面等等Bug
  3. 任务栈过深的时候,避免一直按返回键也退不回想要的页面

任务栈

任务栈Task,是一种用来放置Activity实例的容器,他是以栈的形式进行盛放,也就是所谓的先进后出,主要有2个基本操作:压栈和出栈,其所存放的Activity是不支持重新排序的,只能根据压栈和出栈操作更改Activity的顺序。

启动一个Application的时候,系统会为它默认创建一个对应的Task,用来放置根Activity。默认启动Activity会放在同一个Task中,新启动的Activity会被压入启动它的那个Activity的栈中,并且显示它。当用户按下回退键时,这个Activity就会被弹出栈,按下Home键回到桌面,再启动另一个应用,这时候之前那个Task就被移到后台,成为后台任务栈,而刚启动的那个Task就被调到前台,成为前台任务栈,手机页面显示的就是前台任务栈中的栈顶元素。

standard

Activity默认模式,所有的Activity遵循元素进栈出栈的特性,例如进栈序列为A->B->C->D,D呈现在页面上,按返回键出栈顺序久违D->C->B->A. 

singleTop

栈顶复用模式,如果要开启的activity在任务栈的顶部已经存在,就不会创建新的实例,而是调用 onNewIntent() 方法。避免栈顶的activity被重复的创建。 
在开始处,我们提到的2个Bug,可以用这种模式解决

消息推送

通知栏弹出Notification,点击Notification跳转到指定Activity,但是如果我现在页面就停留在那个指定的Activity,会再次打开我当前的Activity,这样返回的时候回退的页面和当前页面一样,感官上就会很奇怪。

 
 

这里写图片描述


 
 
  1. btnNotification.setOnClickListener( new OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. NoticationUtil.sendNotication(mContext);
  5. }
  6. });

 
 
  1. public class NoticationUtil {
  2. @SuppressLint( "NewApi")
  3. public static void sendNotication(Context context) {
  4. // 在Android进行通知处理,首先需要重系统哪里获得通知管理器NotificationManager,它是一个系统Service。
  5. NotificationManager manager = (NotificationManager) context
  6. .getSystemService(Context.NOTIFICATION_SERVICE);
  7. PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
  8. new Intent(context, MainActivity.class), 0);
  9. // 通过Notification.Builder来创建通知,注意API Level
  10. // API16之后才支持
  11. Notification notify = new Notification.Builder(context)
  12. .setSmallIcon(R.drawable.ic_launcher)
  13. .setTicker( "您有新短消息,请注意查收!").setContentTitle( "我是标题")
  14. .setContentText( "点我打开主页").setContentIntent(pendingIntent)
  15. .setNumber( 1).build();
  16. notify.flags |= Notification.FLAG_AUTO_CANCEL; // FLAG_AUTO_CANCEL表明当通知被用户点击时,通知将被清除。
  17. manager.notify( 1, notify);
  18. }
  19. }

 
 
  1. <activity
  2. android:name= ".aty.MainActivity"
  3. android:launchMode= "standard" >
  4. <intent-filter>
  5. <action android:name="android.intent.action.MAIN" />
  6. <category android:name="android.intent.category.LAUNCHER" />
  7. </intent-filter>
  8. </activity>

修改为:


 
 
  1. <activity
  2. android:name= ".aty.MainActivity"
  3. android:launchMode= "singleTop" >
  4. <intent-filter>
  5. <action android:name="android.intent.action.MAIN" />
  6. <category android:name="android.intent.category.LAUNCHER" />
  7. </intent-filter>
  8. </activity>

这里写图片描述

登录的时候

登录成功跳转到主页,按下两次登录按钮,生成了两个主页。一些有启动延迟的页面(往往是动画,网络造成)也会有这样的情况。


 
 
  1. btnLogin.setOnClickListener( new OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. // 延迟1000秒,模拟网络超时
  5. v.getHandler().postDelayed( new Runnable() {
  6. @Override
  7. public void run() {
  8. // 登录成功,跳转到主页
  9. LoginSuccessActivity.actionStart(mContext);
  10. }
  11. }, 3000);
  12. }
  13. });

XML清单配置

       <activity android:name=".aty.LoginSuccessActivity" />
 
 
  • 1

这里写图片描述 
把他修改为singleTop


 
 
  1. <activity
  2. android:name= ".aty.LoginSuccessActivity"
  3. android:launchMode= "singleTop" />

这里写图片描述

耗时操作返回页面

还有一种场景 从activity A启动了个service进行耗时操作,或者某种监听,这个时候你home键了,service收集到信息,要返回activityA了,就用singleTop启动,实际不会创建新的activityA,只是resume了。不过使用standard又会创造2个A的实例。

singleTask

栈内复用模式

  1. 如果要启动的Activity在当前栈内启动,activity只会在任务栈里面存在一个实例。如果要激活的activity,在任务栈里面已经存在,就不会创建新的activity,而是复用这个已经存在的activity,调用 onNewIntent() 方法,并且清空这个activity任务栈上面所有的activity。

 
 
  1. <activity android:name=".aty.MainActivity" >
  2. <intent-filter>
  3. <action android:name="android.intent.action.MAIN" />
  4. <category android:name="android.intent.category.LAUNCHER" />
  5. </intent-filter>
  6. </activity>
  7. <activity
  8. android:name= ".aty.Test1Activity"
  9. android:launchMode= "singleTask" />
  10. <activity android:name=".aty.Test2Activity" />
  • XML配置 MainActivitiy,Test2Activity为standard模式,Test1Activity为singleTask模式。 
    入栈情况: 
    MainActivity—>Test1Activity—>Test2Activity—>MainActivity,如果此时启动Test1Activity,会清空Test1顶部元素 栈内情况变为MainActivity—>Test1Activity; 
    这里写图片描述 
    2. 主要就是在清单文件中配置android:taskAffinity="新的包名",因为android:taskAffinity这个字段默认指定的包名为本应用的包名,表示在本应用包名的任务栈内创建应用。如果设置了这个字段,而且还和本应用包名不同,就会在新的任务栈创建任务。例如,修改刚才的代码Test1Activity的配置清单:

 
 
  1. <activity
  2. android:name= ".aty.Test1Activity"
  3. android:launchMode= "singleTask"
  4. android:taskAffinity= "com.test.newlaunch" />
  • 1
  • 2
  • 3
  • 4

指定android:taskAffinity包名为com.test.newlaunch 
这里写图片描述


 
 
  1. 06-03 14:47:46.556: D/displayTask(22127): id: --69
  2. 06-03 14:47:46.556: D/displayTask(22127): numActivities: --1
  3. 06-03 14:47:46.556: D/displayTask(22127): topActivity: --com.example.testlaunch.aty.MainActivity
  4. 06-03 14:47:46.556: D/displayTask(22127): baseActivity: --com.example.testlaunch.aty.MainActivity
  5. 06-03 14:47:48.428: D/displayTask(22127): id: --69
  6. 06-03 14:47:48.428: D/displayTask(22127): numActivities: --2
  7. 06-03 14:47:48.428: D/displayTask(22127): topActivity: --com.example.testlaunch.aty.Test2Activity
  8. 06-03 14:47:48.428: D/displayTask(22127): baseActivity: --com.example.testlaunch.aty.MainActivity
  9. 06-03 14:47:50.080: D/displayTask(22127): id: --69
  10. 06-03 14:47:50.080: D/displayTask(22127): numActivities: --3
  11. 06-03 14:47:50.080: D/displayTask(22127): topActivity: --com.example.testlaunch.aty.MainActivity
  12. 06-03 14:47:50.080: D/displayTask(22127): baseActivity: --com.example.testlaunch.aty.MainActivity
  13. 06-03 14:47:55.164: D/displayTask(22127): id: --71
  14. 06-03 14:47:55.164: D/displayTask(22127): numActivities: --1
  15. 06-03 14:47:55.164: D/displayTask(22127): topActivity: --com.example.testlaunch.aty.Test1Activity
  16. 06-03 14:47:55.164: D/displayTask(22127): baseActivity: --com.example.testlaunch.aty.Test1Activity
  17. 06-03 14:47:56.688: D/displayTask(22127): id: --71
  18. 06-03 14:47:56.688: D/displayTask(22127): numActivities: --2
  19. 06-03 14:47:56.688: D/displayTask(22127): topActivity: --com.example.testlaunch.aty.Test2Activity
  20. 06-03 14:47:56.688: D/displayTask(22127): baseActivity: --com.example.testlaunch.aty.Test1Activity
  21. 06-03 14:47:58.140: D/displayTask(22127): id: --71
  22. 06-03 14:47:58.140: D/displayTask(22127): numActivities: --3
  23. 06-03 14:47:58.140: D/displayTask(22127): topActivity: --com.example.testlaunch.aty.MainActivity
  24. 06-03 14:47:58.140: D/displayTask(22127): baseActivity: --com.example.testlaunch.aty.Test1Activity

最开始的栈id为69,然后在06-03 14:47:55.164: D/displayTask(22127): topActivity:--com.example.testlaunch.aty.Test1ActivityTest1Activity启动的时候又新建了一个栈id为71.同时在查看后台程序,看到出现了2个后台任务都是我们的应用—TestLanuch。 
这里写图片描述

作用

  1. 做浏览器、微博之类的应用,比如其他App需要打开我们的浏览器页面,就可以配置他为singleTask模式,保证他只有一个唯一实例,节约内存同时按下返回键后的感官也更顺畅。但是需要注意,提供给人调用的页面最好是栈底元素。因为,如果自己的客户端处于运行状态,按下Home键后台挂起。此时如果使用如果其他应用(比如说QQ)调起自己的客户端某个页面,不做任何处理的情况下,按下回退或者当前 Activity.finish(),页面都会停留在自己的客户端(因为自己的Application回 退栈不为空),这明显不符合逻辑的。 
    对于我的应用TestLaunch XML清单配置

 
 
  1. <activity android:name=".aty.MainActivity" >
  2. <intent-filter>
  3. <action android:name="android.intent.action.MAIN" />
  4. <category android:name="android.intent.category.LAUNCHER" />
  5. </intent-filter>
  6. </activity>
  7. <activity
  8. android:name= ".aty.Test1Activity"
  9. android:launchMode= "singleTask" >
  10. <intent-filter>
  11. <action android:name=".aty.Test1Activity" />
  12. <category android:name="android.intent.category.DEFAULT" />
  13. </intent-filter>
  14. </activity>
  15. <activity android:name=".aty.Test2Activity" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在其他应用 “我是QQ”中打开指定页面Test1Activity


 
 
  1. btnSkip.setOnClickListener( new OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. Intent intent = new Intent();
  5. intent.setAction( ".aty.Test1Activity");
  6. try {
  7. startActivity(intent);
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. });

错误重现

a. 模拟操作TestLanuch 
这里写图片描述 
b. 使用我是QQ打开TestLanuch的Test1Activity,然后按返回键退栈 
这里写图片描述 
要解决这个问题有两种方式:

利用singleTask清除这个activity任务栈上面所有的activity特性

我们提供的分享页面始终是我们栈底的元素,只要他一启动就会清空任务栈内其他Activity,保证只有他一个实例。

android:taskAffinity=”要打开本应用的其他应用包名”

例如在本应用真就设置:


 
 
  1. <activity
  2. android:name= ".MainActivity"
  3. android:label= "@string/app_name"
  4. android:taskAffinity= "com.test.newlaunch" >
  5. <intent-filter>
  6. <action android:name="android.intent.action.MAIN" />
  7. <category android:name="android.intent.category.LAUNCHER" />
  8. </intent-filter>
  9. </activity>

这样在Test1Activity启动的时候,如果com.test.newlaunch没启动就新建一个任务栈,如果com.test.newlaunch启动了就直接加入它的任务栈。

SingleInstance

单一实例模式,整个手机操作系统里面只有一个实例存在。不同的应用去打开这个activity 共享公用的同一个activity。他会运行在自己单独,独立的任务栈里面,并且任务栈里面只有他一个实例存在。应用场景:呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。 
可以得出以下结论: 
1. 以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例。 
2. 以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。 
3. 以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中。 
4. 被singleInstance模式的Activity开启的其他activity,能够在新的任务中启动,但不一定开启新的任务,也可能在已有的一个任务中开启。

换句话说,其实SingleInstance就是我们刚才分析的SingleTask中,分享Activity为栈底元素的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值