美文网首页
Android开发 - Activity的启动模式和最佳实践

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

作者: 寒桥 | 来源:发表于2017-09-01 01:57 被阅读80次

    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变得非常简单,还可以节省不少有同事过来询问你的时间。

    相关文章

      网友评论

          本文标题:Android开发 - Activity的启动模式和最佳实践

          本文链接:https://www.haomeiwen.com/subject/cfhhjxtx.html