Android开发 - Activity的启动模式和最佳实践

Activity的启动模式

在实际项目中需要根据特定的需求为每一个Activity指定恰当的启动模式。

启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给<activity>标签指定android:launchMode属性选择启动模式

  • standard

standard是Activity默认的启动模式,在不进行显示指定的情况下,所有Activity都使用这种启动模式。由于Android是使用返回栈来管理Activity的,在standard(即默认情况)下,每当启动一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的Activity,系统不会在乎这个Activity是否已经在返回栈中存在,每次启动都会创建该Activity的一个新的实例

在代码中观察一下:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("FirstActivity", this.toString());
        setContentView(R.layout.first_layout);
        Button button1 = (Button)findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }

    // 运行结果
    // D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@bf411c9
    // D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@a01da18
    // D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@c439963

代码看起来有点奇怪,在FirstActivity的基础上启动FirstActivity。从逻辑上讲没什么意义,但对于研究standard模式会很有用,通过在onCreate()添加打印运行的结果来看,每次点击按钮都会创建一个新的FirstActivity实例。此时返回栈也会存在3个FirstActivity的实例,因此需要连续按3次Back键才能退出程序

standard模式的原理示意图:

standard模式的原理示意图.png
  • singleTop

可能会觉得standard模式不太合理,命名已经在栈顶了为什么启动的时候还要创建一个新的实例呢,这只是系统默认的一种启动模式而已,完全可以根据自己的需要进行修改。当Activity的启动模式指定为singleTop,在启动Activity时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的Activity实例

先修改一下AndroidManifrst.xml中FirstActivity的启动模式,如下所示:

<activity android:name=".FirstActivity"
            android:label="This is FirstActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

观察按钮点击后的输出

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("FirstActivity", this.toString());
        setContentView(R.layout.first_layout);
        Button button1 = (Button)findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
                startActivity(intent);
            }
        });
    }

    // 打印结果
    // D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@77e7e6c

运行结果显示不管点击多少次按钮都不会再有新的信息出现,因为此时FirstActivity已经处于返回栈栈顶,每当想再启动一个FirstActivity的时候都会直接使用栈顶的Activity,因此FirstActivity也只会有一个实例,仅按一次Back键即可退出程序。

不过当FirstActivity并未处于栈顶位置的时候,这时再启动FirstActivity,还是会创建新的实例,可以通过以下代码进行演示。

创建SecondActivity,让FirstActivity点击按钮进入SecondActivity,在SecondActivity中的按钮点击又进入FirstActivity

FirstActivity中:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("FirstActivity", this.toString());
        setContentView(R.layout.first_layout);
        Button button1 = (Button)findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }

在SecondActivity中

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("SecondActivity", this.toString());
        setContentView(R.layout.second_layout);
        Button button2 = (Button)findViewById(R.id.button_2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intet = new Intent(SecondActivity.this, FirstActivity.class);
                startActivity(intet);
            }
        });
    }

运行结果:

// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@ee8ec35
// D/SecondActivity: com.zntq.xietao.activitytest.SecondActivity@ecd00ce
// D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@a01da18

从运行结果可以看出创建了两个不同的FirstActivity实例,这是由于在SecondActivity中再次启动FirstActivity时,栈顶Activity是SecondActivity,因此会创建一个新的FirstActivity实例。现在按下Back返回键会返回到SecondActivity,再次按下Back键又返回FirstActivity,再按一次Back键才会退出程序。

singleTop模式的原理示意图:

singleTop模式的原理示意图.png
  • singleTask

使用singleTop模式可以很好的解决重复创建栈顶Activity的问题,但是正如上边看到的,如果该Activity并没有处于栈顶还是会创建多个Activity实例的,那么有没有可以让某个Activity在整个应用程序的上下文中只存在一个实例呢?这就需要借助singleTask模式来实现。

当Activity的启动模式指定为singleTask的时候,每次启动该Activity时系统首先会在栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用这个实例,并把在这个Activity上的所有Activity统统出栈,如果没有发现就会创建一个新的Activity实例。还是通过下边的代码来看一下

修改AndroidManifest.xml中FirstActivity的启动模式:

<activity
            android:name=".FirstActivity"
            android:label="This is FirstActivity"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

然后在FirstActivity中添加onRestart()方法,并打印日志:

 @Override
    protected void onRestart() {
        super.onRestart();
        Log.d("FirstActivity", "onRestart");
    }

最后在SecondActivity中添加onDestroy()方法,并打印日志:

@Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d("SecondActivity", "onDestroy");
    }

重新运行程序,在FirstActivity界面点击按钮进入SecondActivity,然后在SecondActivity界面点击按钮又会重新进入到FirstActivity

// 打印结果
    // D/FirstActivity: com.zntq.xietao.activitytest.FirstActivity@ee8ec35
    // D/SecondActivity: com.zntq.xietao.activitytest.SecondActivity@ecd00ce
    // D/FirstActivity: onRestart
    // D/SecondActivity: onDestroy

从打印结果可以看出,在SecondActivity中启动FirstActivity时,会发现返回栈中已经存在一个FirstActivity的实例了,并且是在SecondActivity的下面,于是SecondActivity会从返回栈中出栈,而FirstActivity重新成为栈顶Activity,因此FirstActivity的onRestart()方法和SecondActivity的onDestroy()方法会得到执行。现在返回栈中支撑下一个FirstActivity的实例了,按一下Back键就可以退出程序。

singleTask模式的原理示意图:

singleTask模式的原理示意图.png
  • singleInstance

singleInstance模式是4种启动模式中最特殊也是最复杂的一个。不同于以上3种启动模式,指定为singleInstance模式的Activity会启用一个新的返回栈来管理这个Activity。这样做的意义在于当程序中有一个Activity是允许其他程序调用的,那么想实现其他程序和我们的程序可以共享这个Activity的实例,就可以通过singleInstance模式来实现了,而其他3种模式均无法做到,因为每个应用程序都有自己的返回栈,同一个Activity在不同的返回栈中入栈时必然是创建了新的实例。singleInstance这种模式会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用同一个返回栈,也就解决了共享Activity实例的问题。同样通过以下代码来验证。

修改AndroidManifest.xml中SecondActivity的启动模式,同时创建ThirdActivity

<activity android:name=".SecondActivity"
            android:launchMode="singleInstance">
            
</activity>

修改FirstActivity中onCreate()方法的代码,打印当前返回栈的id

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("FirstActivity", "Task id is" + this.getTaskId());
        setContentView(R.layout.first_layout);
        Button button1 = (Button)findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }

然后修改SecondActivity中的onCreate()方法,打印当前返回栈的id,同时点击按钮启动ThirdActivity

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("SecondActivity", "Task id is" + this.getTaskId());
        setContentView(R.layout.second_layout);
        Button button2 = (Button)findViewById(R.id.button_2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intet = new Intent(SecondActivity.this, ThirdActivity.class);
                startActivity(intet);
            }
        });
    }

最后修改ThirdActivity中的onCreate()方法,同样打印当前返回栈的信息

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("ThirdActivity", "Task id is" + this.getTaskId());
        setContentView(R.layout.third_layout);
    }

运行结果

// 打印结果
    // D/FirstActivity: Task id is17
    // D/SecondActivity: Task id is18
    // D/ThirdActivity: Task id is17

通过运行结果可以看到,SecondActivity的Task id不同于FirstActivity和ThirdActivity,这说明SecondActivity确实是存放在一个单独的返回栈里,而且这个返回栈只有SecondActivity这一个Activity

然后按下Back键进行反悔,会发现ThirdActivity竟然直接返回到了FirstActivity,再按下Back返回键又返回到SecondActivity,再按下Back键才会退出程序。这个原因其实也很简单,由于FirstActivity和ThirdActivity是存放在一个返回栈中的,当在ThirdActivity中按下Back键,ThirdActivity从返回栈中出栈,那么FirstActivity久成为了栈顶Activity显示在界面上,因此就出现了从ThirdActivity直接返回到FirstActivity的情况。然后在FirstActivity界面再次按下Back键,这时当前的返回栈已经空了,于是就显示了另一个返回栈的栈顶Activity,即SecondActivity。最后再按下Back键,这时所有的返回栈都已经空了,也就自然退出程序了。

singleInstance模式的原理示意图:

singleInstance模式的原理示意图.png

Activity的最佳实践

知晓当前是在哪一个Activity

这个技巧将会教会你如何根据程序当前的界面就能判断出是哪一个Activity
创建一个BaseActivity类,因为不需要让BaseActivity在AndroidManifest.xml中注册,因此创建一个普通的Java类就可以了,然后让BaseActivity继承自AppCompatActivity,并重写onCreate()方法,如下:

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
    }
}

在BaseActivity中的onCreate()方法中获取了当前实例的名字,并通过Log打印出来。接下来让FirstActivity和SecondActivity继承自BaseActivity。由于BaseActivity又是继承自AppCompatActivity的,所以项目中所有Activity的现有功能不会受影响,仍然完全继承了Activity中的所有特性.

打印结果
D/BaseActivity: FirstActivity
D/BaseActivity: SecondActivity

现在每当我们进入到一个Activity的界面,该Activity的类名就被打印出来,这样就可以时时知晓当前界面对应的哪一个Activity了。

随时随地退出程序

当页面停留比较深的时候,想退出程序需要按下多次Back键。按HOME键只是把程序挂起来,并没有真正退出程序,因此需要有一个随时随地都能退出程序的方案,思路就是用一个专门的集合类对所有的Activity进行管理就可以了。

新建一个ActivityController类作为Activity管理器,代码如下:

public class ActivityController {
    public static List<Activity> activities = new ArrayList<>();
    
    public static void addActivity(Activity activity) {
        activities.add(activity);
    }
    
    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }
    
    public static void finishAll() {
        for (Activity activity: activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
    }
}

在活动管理器中,通过一个List来暂存活动,然后提供一个addActivity()方法用于向List中添加一个Activity,提供了一个removeActivity()方法用于从List中移除Activity,最后提供了一个finishAll()方法用于将List中存储的活动全部销毁掉。

接下来修改BaseActivity中的代码如下:

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
        ActivityController.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityController.removeActivity(this);
    }
}

在BaseActivity的onCreate()方法中调用了ActivityController的addActivity()方法,表明将当前正在创建的活动添加到活动管理器里。然后在BaseActivity中重写onDestroy()方法,并调用ActivityController的removeActivity()方法,表明将一个马上要销毁的Activity从Activity从活动管理器中移除。

以后无论想在任何地方退出程序,只需要调用ActivityController的finishAll()方法就可以了,如在SecondActivity中想要退出程序,只需要写如下代码就可以了。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_layout);
        // 给按钮注册点击事件,并在点击事件中添加返回数据的逻辑
        Button button2 = (Button)findViewById(R.id.button_2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ActivityController.finishAll();
            }
        });
    }

当然还可以在销毁所有的代码后边加上杀掉当前进程的代码,以保证程序完全退出,杀掉进程的代码如下:

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

其中,killProcess()方法用于杀掉一个进程,它接收一个进程id参数,我们可以通过myPid()方法来获取当前程序的进程id。这个只能傻吊当前程序的进程,不能使用这个方法杀掉其他程序的进程。

启动Activity的最佳写法

启动Activity的方法之前已经说过,首先通过Intent构建出当前的“意图”,然后调用startActivity()或startActivityForResult()方法将Activity启动起来,如果有数据需要从一个Activity传递到另一个Activity,也可以借助Intent来完成。

假设SecondActivity中需要用到两个非常重要的字符串参数,在启动SecondActivity的时候必须要传递过来,那么在FirstActivity中很容易会写下如下代码:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button = (Button)findViewById(R.id.button_1);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                intent.putExtra("param1", "data1");
                intent.putExtra("param2", "data2");
                startActivity(intent);
            }
        });
    }

这样写是完全OK的,只是如果在真正的项目开发中遇到SecondActivity并不是由你开发,但现在你负责的部分需要启动SecondActivity这个功能,而你却不清楚启动这个Activity需要传递哪些数据。这时,无非两种方法,一是自己阅读SecondActivity的代码,二是询问负责编写SecondActivity的同事,这样的话就会比较麻烦。现在换一种写法来解决以上问题。

修改SecondActivity中的代码:

public class SecondActivity extends BaseActivity {
    
    public static void actionStart(Context context, String data1, String data2) {
        Intent intent = new Intent(context, SecondActivity.class);
        intent.putExtra("param1", data1);
        intent.putExtra("param2", data2);
        context.startActivity(intent);
    }
    ...

}

在SecondActivity中添加了一个actionStart()方法,这个方法中完成了Intent的构建,另外所有SecondActivity中需要的数据都是通过actionStart()方法的参数传递过来的,然后把它们存储到Intent中,最后调用startActivity()方法启动SecondActivity。

这样写的好处在于一目了然,SecondActivity所需要的数据在方法参数中全部体现出来了,这样即使不用阅读SecondActivity中的代码,不去询问负责SecondActivity开发的相关同事,也可以知道启动SecondActivity需要传递哪些数据。这样还简化了启动Activity的代码,现在在FirstActivity中只需要一行代码就可以启动SecondActivity了。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first_layout);
        Button button = (Button)findViewById(R.id.button_1);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
            }
        });
    }

养成良好的习惯,给编写的每一个Activity都添加类似的启动方法,这样就可以让启动Activity变得非常简单,还可以节省不少有同事过来询问你的时间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容