阅读郭霖《第一行代码》的笔记——第2章 先从看得到的入手,探究活动

一、活动是什么,活动的基本用法

活动(Activity)是最容易吸引到用户的地方了,它是一种可以包含用户界面的组件,主要用于和用户进行交互。一个应用程序中可以包含零个或多个活动,但不包含任何活动的应用程序很少见,谁也不想让自己的应用永远无法被用户看到吧?
Android程序的设计讲究逻辑和视图分离,最好每一个活动都能对应一个布局,布局就是用来显示界面内容的。
创建和加载布局

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
      //setContentView()方法来给当前的活动加载一个布局,而在
      //setContentView()方法中,我们一般都会传入一个布局文件的id。
        setContentView(R.layout.first_layout);
    }

在AndroidManifest文件中注册

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity
            android:name=".FirstActivity"
            android:label="This is FirstActivity">
            <intent-filter>
             <action android:name="android.intent.action.MAIN" />
             <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

可以看到,活动的注册声明要放在标签内,这里是通过标签来对活动进行注册的。首先我们要使用android:name来指定具体注册哪一个活动,那么这里填入的.FirstActivity是什么意思呢?其实这不过就是com.example.activitytest.FirstActivity的缩写而已。由于最外层的标签中已经通过package属性指定了程序的包名是com.example.activitytest,因此在注册活动时这一部分就可以省略了,直接使用.FirstActivity就足够了。然后我们使用了android:label指定活动中标题栏的内容,标题栏是显示在活动最顶部的,待会儿运行的时候你就会看到。需要注意的是,给主活动指定的label不仅会成为标题栏中的内容,还会成为启动器(Launcher)中应用程序显示的名称。之后在标签的内部我们加入了标签,并在这个标签里添加了和这两句声明。这个我在前面也已经解释过了,如果你想让FirstActivity作为我们这个程序的主活动,即点击桌面应用程序图标时首先打开的就是这个活动,那就一定要加入这两句声明。另外需要注意,如果你的应用程序中没有声明任何一个活动作为主活动,这个程序仍然是可以正常安装的,只是你无法在启动器中看到或者打开这个程序。这种程序一般都是作为第三方服务供其他的应用在内部进行调用的,如支付宝快捷支付服务。
隐藏标题栏
隐藏的方法非常简单,打开 FirstActivity,在onCreate()方法中添加如下代码:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);//其中requestWindowFeature(Window.FEATURE_NO_TITLE) 的意思就是不在活动中显示标题栏,注意这句代码一定要在setContentView()之前执行,不然会报错。
    setContentView(R.layout.first_layout);
}

在活动中使用Toast
Toast是Android系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间。
Button button1 = (Button) findViewById(R.id.button_1);//获取到在布局文件中定义的元素,findViewById()方法返回的是一个View对象,我们需要向下转型将它转成Button对象。

 button1.setOnClickListener(new OnClickListener() {//为按钮注册一个监听器
        @Override
        public void onClick(View v) {
            Toast.makeText(FirstActivity.this, "You clicked Button 1",Toast.LENGTH_SHORT).show();//如下
        }
    });

Toast的用法非常简单,通过静态方法makeText()创建出一个Toast对象,然后调用show()将Toast显示出来就可以了。这里需要注意的是,makeText()方法需要传入三个参数。第一个参数是Context,也就是Toast要求的上下文,由于活动本身就是一个Context对象,因此这里直接传入FirstActivity.this即可。第二个参数是Toast显示的文本内容,第三个参数是Toast显示的时长,有两个内置常量可以选择Toast.LENGTH_SHORT和Toast.LENGTH_LONG。
在活动中使用Menu
在main.xml中添加如下代码:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:id="@+id/add_item"
        android:title="Add"/>
    <item
        android:id="@+id/remove_item"
        android:title="Remove"/>
</menu>

这里我们创建了两个菜单项,其中标签就是用来创建具体的某一个菜单项,然后通过android:id给这个菜单项指定一个唯一标识符,通过android:title给这个菜单项指定一个名称。

然后打开FirstActivity,重写onCreateOptionsMenu()方法,代码如下所示:

public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);/*通过getMenuInflater()方法能够得到MenuInflater对象,再调用它的inflate()方法就可以给当前活动创建菜单了。inflate()方法接收两个参数,第一个参数用于指定我们通过哪一个资源文件来创建菜单,这里当然传入R.menu.main,第二个参数用于指定我们的菜单项将添加到哪一个Menu对象当中,这里直接使用onCreateOptionsMenu()方法中传入的menu参数。*/

return true;/*给这个方法返回true,表示允许创建的菜单显示出来,如果返回了false,创建的菜单将无法显示。*/
}

当然,仅仅让菜单显示出来是不够的,我们定义菜单不仅是为了看的,关键是要菜单真正可用才行,因此还要再定义菜单响应事件。在FirstActivity中重写onOptionsItemSelected()方法:

 public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add_item:
                Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show();
                break;
            case R.id.remove_item:
                Toast.makeText(this, "You clicked Remove", Toast.LENGTH_SHORT).show();
                break;
            default:
        }
        return true;
    }

在onOptionsItemSelected()方法中,通过调用item.getItemId()来判断我们点击的是哪一个菜单项,然后给每个菜单项加入自己的逻辑处理。
销毁一个活动
只要按一下Back键就可以销毁当前的活动了。不过如果你不想通过按键的方式,而是希望在程序中通过代码来销毁活动,当然也可以,Activity类提供了一个finish()方法,我们在活动中调用一下这个方法就可以销毁当前活动了。

二、使用Intent在活动之间穿梭

使用显式Intent
Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动服务、以及发送广播等场景。
Intent的用法大致可以分为两种,显式Intent和隐式Intent,我们先来看一下显式Intent如何使用。
Intent有多个构造函数的重载,其中一个是Intent(Context packageContext, Class<?> cls)。这个构造函数接收两个参数,第一个参数Context要求提供一个启动活动的上下文,第二个参数Class则是指定想要启动的目标活动,通过这个构造函数就可以构建出Intent的“意图”。然后我们应该怎么使用这个Intent呢?Activity类中提供了一个startActivity()方法,这个方法是专门用于启动活动的,它接收一个Intent参数,这里我们将构建好的Intent传入startActivity()方法就可以启动目标活动了。

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);

使用隐式Intent
相比于显式Intent,隐式Intent则含蓄了许多,它并不明确指出我们想要启动哪一个活动,而是指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动。什么叫做合适的活动呢?简单来说就是可以响应我们这个隐式Intent的活动。
通过在标签下配置的内容,可以指定当前活动能够响应的action和category,打开AndroidManifest.xml,添加如下代码:

<activity android:name=".SecondActivity" >
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

在标签中我们指明了当前活动可以响应com.example.activitytest.ACTION_ START这个action,而标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的Intent中还可能带有的category。只有和中的内容同时能够匹配上Intent中指定的action和category时,这个活动才能响应该Intent。
修改FirstActivity中按钮的点击事件,代码如下所示:

button1.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent("com.example.activitytest.ACTION_START");
            startActivity(intent);
        }
    });

可以看到,我们使用了Intent的另一个构造函数,直接将action的字符串传了进去,表明我们想要启动能够响应com.example.activitytest.ACTION_START这个action的活动。那前面不是说要和同时匹配上才能响应的吗?怎么没看到哪里有指定category呢?这是因为android.intent.category.DEFAULT是一种默认的category,在调用startActivity()方法的时候会自动将这个category添加到Intent中。
每个Intent中只能指定一个action,但却能指定多个category。
更多隐式Intent的用法
使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。比如说你的应用程序中需要展示一个网页,这时你没有必要自己去实现一个浏览器(事实上也不太可能),而是只需要调用系统的浏览器来打开这个网页就行了。

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));

这里我们首先指定了Intent的action是Intent.ACTION_VIEW,这是一个Android系统内置的动作,其常量值为android.intent.action.VIEW。然后通过Uri.parse()方法,将一个网址字符串解析成一个Uri对象,再调用Intent的setData()方法将这个Uri对象传递进去。setData()部分:它接收一个Uri对象,主要用于指定当前Intent正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse()方法中解析产生的。
在标签中再配置一个标签,用于更精确地指定当前活动能够响应什么类型的数据。标签中主要可以配置以下内容。
1.android:scheme :用于指定数据的协议部分,如上例中的http部分。 除了http协议外,我们还可以指定很多其他协议,比如geo表示显示地理位置、tel表示拨打电话。
2.android:host :用于指定数据的主机名部分,如上例中的www.baidu.com部分。
3.android:port:用于指定数据的端口部分,一般紧随在主机名之后。
4.android:path :用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
5.android:mimeType :用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
只有标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。不过一般在标签中都不会指定过多的内容,如上面浏览器示例中,其实只需要指定android:scheme为http,就可以响应所有的http协议的Intent了。
eg.

Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));

向下一个活动传递数据
在启动活动时传递数据的思路很简单,Intent中提供了一系列putExtra()方法的重载,可以把我们想要传递的数据暂存在Intent中,启动了另一个活动后,只需要把这些数据再从Intent中取出就可以了。
FirstActivity:

String data = "Hello SecondActivity";
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("extra_data", data);/*收两个参数,第一个参数是键,用于后面从Intent中取值,第二个参数才是真正要传递的数据。*/
   startActivity(intent);

SecondActivity:

Intent intent = getIntent();//通过getIntent()方法获取到用于启动SecondActivity的Intent
String data = intent.getStringExtra("extra_data");/*调用getStringExtra()方法,传入相应的键值,就可以得到传递的数据了。*/
Log.d("SecondActivity", data);

返回数据给上一个活动
返回上一个活动只需要按一下Back键就可以了,并没有一个用于启动活动Intent来传递数据。通过查阅文档你会发现,Activity中还有一个startActivityForResult()方法也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。
startActivityForResult()方法接收两个参数,第一个参数还是Intent,第二个参数是请求码,用于在之后的回调中判断数据的来源。
修改FirstActivity中按钮的点击事件,代码如下所示:

button1.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
        startActivityForResult(intent, 1);//启动SecondActivity,请求码只要是一个唯一值就可以了,这里传入了1。
    }
});

接下来我们在SecondActivity中给按钮注册点击事件,并在点击事件中添加返回数据的逻辑,代码如下所示:

public class SecondActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.second_layout);
    Button button2 = (Button) findViewById(R.id.button_2);
    button2.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
    Intent intent = new Intent();/*构建了一个Intent,只不过这个Intent仅仅是用于传递数据而已,它没有指定任何的“意图”。*/

    intent.putExtra("data _return", "Hello FirstActivity");
    setResult(RESULT_OK, intent);/*把要传递的数据存放在Intent中,然后调用了setResult()方法。这个方法非常重要,是专门用于向上一个活动返回数据的。setResult()方法接收两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK或RESULT_CANCELED这两个值,第二个参数则是把带有数据的Intent传递回去。*/
            finish();//销毁当前活动。
        }
    });
}

由于我们是使用startActivityForResult()方法来启动SecondActivity的,在SecondActivity被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在FirstActivity中重写这个方法来得到返回的数据,如下所示:

//onActivityResult()方法带有三个参数,
//第一个参数requestCode,即我们在启动活动时传入的请求码。
//第二个参数resultCode,即我们在返回数据时传入的处理结果。
//第三个参数data,即携带着返回数据的Intent。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case 1:
        if (resultCode == RESULT_OK) {
            String returnedData = data.getStringExtra("data_return");
            Log.d("FirstActivity", returnedData);
        }
        break;
    default:
    }
}

由于在一个活动中有可能调用startActivityForResult()方法去启动很多不同的活动,每一个活动返回的数据都会回调到onActivityResult()这个方法中,因此我们首先要做的就是通过检查requestCode的值来判断数据来源。确定数据是从SecondActivity返回的之后,我们再通过resultCode的值来判断处理结果是否成功。最后从data中取值并打印出来,这样就完成了向上一个活动返回数据的工作。
这时候你可能会问,如果用户在SecondActivity中并不是通过点击按钮,而是通过按下Back键回到FirstActivity,这样数据不就没法返回了吗?没错,不过这种情况还是很好处理的,我们可以通过重写onBackPressed()方法来解决这个问题,代码如下所示:

@Override
public void onBackPressed() {
    Intent intent = new Intent();
    intent.putExtra("data_return", "Hello FirstActivity");
    setResult(RESULT_OK, intent);
    finish();
}

这样的话,当用户按下Back键,就会去执行onBackPressed()方法中的代码,我们在这里添加返回数据的逻辑就行了。

三、活动的生命周期

返回栈
Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键或调用finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。
示意图2.19展示了返回栈是如何管理活动入栈出栈操作的。

活动状态
每个活动在其生命周期中最多可能会有四种状态。
1.运行状态 :当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。
2.暂停状态 :当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。你可能会觉得既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域,你很快就会在后面看到这种活动。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种活动。
3.停止状态 :当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。
4.销毁状态 :当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。
活动的生存期
Activity类中定义了七个回调方法,覆盖了活动生命周期的每一个环节,下面我来一一介绍下这七个方法。
1.onCreate() :这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等。
2.onStart() :这个方法在活动由不可见变为可见的时候调用。
3.onResume() :这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。
4.onPause() :这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
5.onStop() :这个方法在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行。
6.onDestroy() :这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
7.onRestart() :这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
以上七个方法中除了onRestart()方法,其他都是两两相对的,从而又可以将活动分为三种生存期。
1.完整生存期 :活动在onCreate()方法和onDestroy()方法之间所经历的,就是完整生存期。一般情况下,一个活动会在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完成释放内存的操作。
2.可见生存期 :活动在onStart()方法和onStop()方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法,合理地管理那些对用户可见的资源。比如在onStart()方法中对资源进行加载,而在onStop()方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。
3.前台生存期 :活动在onResume()方法和onPause()方法之间所经历的,就是前台生存期。在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行相互的,我们平时看到和接触最多的也这个状态下的活动。
为了帮助你能够更好的理解,Android官方提供了一张活动生命周期的示意图,如图2.20所示。

活动被回收了怎么办
Activity中还提供了一个onSaveInstanceState()回调方法,这个方法会保证一定在活动被回收之前调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型数据,以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值,第二个参数是真正要保存的内容。
在MainActivity中添加如下代码就可以将临时数据进行保存:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    String tempData = "Something you just typed";
    outState.putString("data_key", tempData);
}

数据是已经保存下来了,那么我们应该在哪里进行恢复呢?细心的你也许早就发现,我们一直使用的onCreate()方法其实也有一个Bundle类型的参数。这个参数在一般情况下都是null,但是当活动被系统回收之前有通过onSaveInstanceState()方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。
修改MainActivity的onCreate()方法,如下所示:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "onCreate");
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null) {
        String tempData = savedInstanceState.getString("data_key");
        Log.d(TAG, tempData);
    }
    ……
}

取出值之后再做相应的恢复操作就可以了,比如说将文本内容重新赋值到文本输入框上,这里我们只是简单地打印一下。

四、活动的启动模式

在实际项目中我们应该根据特定的需求为每个活动指定恰当的启动模式。启动模式一共有四种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来选择启动模式。
standard
standard是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种启动模式。在standard模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
standard模式的原理示意图,如图2.31所示。

singleTop
当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。

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

singleTop模式的原理示意图,如图2.34所示。

singleTask
使用singleTop模式可以很好地解决重复创建栈顶活动的问题,但是正如你在上一节所看到的,如果该活动并没有处于栈顶的位置,还是可能会创建多个活动实例的。那么有没有什么办法可以让某个活动在整个应用程序的上下文中只存在一个实例呢?这就要借助singleTask模式来实现了。当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。

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

singleTask模式的原理示意图,如图2.36所示。

singleInstance
singleInstance模式应该算是四种启动模式中最特殊也最复杂的一个了,你也需要多花点功夫来理解这个模式。不同于以上三种启动模式,指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。那么这样做有什么意义呢?想象以下场景,假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,应该如何实现呢?使用前面三种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。

<activity
    android:name=".SecondActivity"
    android:launchMode="singleInstance" >
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="com.example.activitytest.MY_CATEGORY" />
    </intent-filter>
</activity>

singleInstance模式的原理示意图,如图2.38所示。

五、活动的最佳实践

在这里我准备教你几种关于活动的最佳实践技巧,这些技巧在你以后的开发工作当中将会非常受用。
知晓当前是在哪一个活动
新建一个BaseActivity继承自Activity,然后在BaseActivity中重写onCreate()方法,如下所示:

public class BaseActivity extends Activity {

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

}

我们在onCreate()方法中获取了当前实例的类名,并通过Log打印了出来。

接下来我们需要让BaseActivity成为ActivityTest项目中所有活动的父类。修改FirstActivity、SecondActivity和ThirdActivity的继承结构,让它们不再继承自Activity,而是继承自BaseActivity。虽然项目中的活动不再直接继承自Activity了,但是它们仍然完全继承了Activity中的所有特性。
随时随地退出程序
只需要用一个专门的集合类对所有的活动进行管理就可以了。
新建一个ActivityCollector类作为活动管理器,代码如下所示:

public class ActivityCollector {

    public static List<Activity> activities = new ArrayList<Activity>();//暂存活动

    public static void addActivity(Activity activity) {//向List中添加一个活动
        activities.add(activity);
    }

    public static void removeActivity(Activity activity) {//从List中移除活动
        activities.remove(activity);
    }

    public static void finishAll() {//将List中存储的活动全部都销毁掉
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
    }

}

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

public class BaseActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("BaseActivity", getClass().getSimpleName());
        ActivityCollector.addActivity(this);//表明将当前正在创建的活动添加到活动管理器里
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);//表明将一个马上要销毁的活动从活动管理器里移除
    }

}

从此以后,不管你想在什么地方退出程序,只需要调用ActivityCollector.finishAll()方法就可以了。
当然你还可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全退出。
启动活动的最佳写法
假设SecondActivity中需要用到两个非常重要的字符串参数,在启动SecondActivity的时候必须要传递过来,那么我们很容易会写出如下代码:

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);

这样写是完全正确的,不管是从语法上还是规范上,只是在真正的项目开发中经常会有对接的问题出现。比如SecondActivity并不是由你开发的,但现在你负责的部分需要有启动SecondActivity这个功能,而你却不清楚启动这个活动需要传递哪些数据。这时无非就有两种办法,一个是你自己去阅读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需要传递哪些数据。另外,这样写还简化了启动活动的代码,现在只需要一行代码就可以启动SecondActivity,如下所示:

button1.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
    }
});

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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值