android:launchMode小分析

473 查看

前言

新年伊始,本打算大展宏图的本人却一直处于项目经理的忽视之中,终于的终于,本人迎来了新年的第一个重磅需求。
作为一个拥有上亿用户的APP,本APP的用户条款竟然未受到任何投诉和质疑,已经被告上法庭的某APP决定也使用我们的用户条款。

设计

Android松耦合的应用组织方式一直是开发者的小确幸,理所当然,我将原来的用户条款dialog封装至Activity里,Activity背景做成透明,再将Acitivty提供给第三方APP调用,这样就大功告成啦。

代码

Activity需要在menifest中声明,恩,经验老道的我当然知道,直接copy一个过来,再把名字改一下。

<activity
    android:name="com.moven.launchmode.DialogActivity" 
    android:launchMode="singleTask">
    <intent-filter>
        <action android:name="com.moven.noticedialog" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
public class DialogActivity extends Activity
{
    private AlertDialog noticeDialog;
    
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        showNoticeDialog();
    }
    
    private void showNoticeDialog()
    {
        AlertDialog.Builder builder = new AlertDialog.Builder(DialogActivity.this);
        builder.setTitle(getString(R.string.title));
        builder.setMessage(R.string.xxx_content);
        builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener()
        {
            
            @Override
            public void onClick(DialogInterface dialog, int which)
            {
                DialogActivity.this.finish();
            }
        });
        builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener()
        {
            
            @Override
            public void onClick(DialogInterface dialog, int which)
            {
                DialogActivity.this.finish();
            }
        });
        noticeDialog = builder.create();
        noticeDialog.show();
    }
}

再在第三方应用和自己的应用里分别写一个测试调用的Acitivity,分别叫AlphaActivity、BetaActivity,Alpha背景色为橙色,Beta为绿色,BetaActivity的顶部有一段透明区域。run一下看看,(1)进入B应用,点击BetaActivity中的按钮,弹出对话框,取消,按返回键退出应用B;(2)点击A应用桌面图标进入AlphaActivity,点击按钮,弹出对话框。

(1)

(2)

一切正常,恩,下班找MM去咯。...要不再试一次吧,增加成就感,也避免老是被老大说粗心大意,
(1)进入B应用,点击BetaActivity中的按钮,弹出对话框,按Home键返回桌面;(2)点击A应用桌面图标进入AlphaActivity,点击按钮,弹出对话框。

咦,调用对话框怎么把BetaActivity也拉起来了。

分析

首先看看DialogActivity的声明,声明中有这样一个东西,

android:launchMode="singleTask"

看名字好高深,赶紧去developers补补脑,恩,原来是这样,

1.Task 和 BackStack

Task是用户所交互的activity的序列,通常情况下,一个Task是从用户点击app图标首次运行一个app开始的,在不返回Home界面的情况下,后续所跳转的所有Activity都属于该Task。

一个Task中的所有Activity都是放在一个栈中去管理的,这个栈就是BackStack,每一个Task都有一个BackStack。在简单的场景下,用户每打开一个新的Activity,这个Activity就会被Push到BackStack顶部,用户按Back键退出一个Acticity时,这个Activity会被Pop出BackStack并Destroy。

Task的状态也分为前台和后台(Background),当我们按Home键时,整个Task都会被切换到后台,当Task处于后台时,Task中所有Activity都处于stopped状态。

盗一张developers的图:

2.launchMode

launchMode用来定义Activity加载进Task的方式,它有以下四种:

(1)standard
如果不声明的话默认使用的就是这种方式,当你使用Intent启动一个Activity时,系统每次都会创建一个新的实例,并将该实例放入当前的BackStack中。在这种模式下,一个Activity可以被实例化多次,一个Task中可以有该Activity的多个实例,多个实例也可能在不同的Task中。

(2)singleTop
在这种模式下,如果某个Activity的实例处于BackStack顶部,那么当有Intent启动该Activity时,系统将会使用顶部的该Activity实例而不会创建新的实例。如果某个Activity的实例并不处于栈顶的话,系统将会创建新的实例,和standard一样。

(3)singleTask
在该模式下,一个Activity只存在于一个Task中,当有Intent启动该Activity时,系统会将该Activity所在Task的BackStack Push到调用该Activity的Task的栈顶,看google的一张图可以清晰的理解该方式:

(4)singleInstance
该模式与singleTask的唯一区别就是,一个Task中只存在一个Avtivity,换句话说,只要一个Activity不存在,系统创建它时都会为它创建一个Task。

问题回溯

再回头来看看我们的问题,我将DialogActivity的launchmode声明为singleTask,当BetaActivity调用DialogActivity后,栈是这样的,打开Hierarchy Viewwer看看,

如果我们按返回键,DialogActivity将会出栈并destroy,

而如果我们按home键,DialogActivity将会移向栈底,launcher重新压入栈顶,

第二次测试时,我正是按home键而不是back键返回桌面的,再从A应用进入AlphaActivity, AlphaActivity被压入栈顶,

点击按钮,打开对话框,

这时我们看到DialogActivity的整个Task栈(BetaActivity在栈底)被压入了栈顶,这就出现了我们不希望看到的场景。

我们将launchmode改为standard看看,

AlphaActivity拉起了一个新的DialogActivity实例,这次就满足要求啦。

最后再看看singleInstance,

只有DialogActivity被重新压入了栈顶,BetaActivity仍在栈的底部,这样也满足要求。

结语

Android是弱化了Application这个概念的,一个应用可以理解为一串Acitivity的交互序列,而不局限于一个进程本身。当一个Activity可能被不同应用调用时,如何设计launchmode很重要,如果Acitivity在不同应用中的调用层次不同,就应谨慎使用SingleTask。
最后祝大家周末开(jia)心(ban)。