Activity是什么?

Activity是最容易吸引到用户的地方了,它是一种可以包含用户界面的组件, 主要用于和用户进行交互。一个应用程序中可以包含零个或多个Activity。

1. Activity的基本用法

手动创建Activity——创建和加载布局Xml——定义布局中的元素——在Activity中加载布局文件——在AndroidManifest文件中注册
(1)手动创建Activity
现在右击 com.example.activitytest 包→New→Class,会弹出新建类的对话框,我们新建 一个名为 FirstActivity 的类,并让它继承自 Activity,点击 Finish 完成创建。你需要知道,项目中的任何活动都应该重写 Activity 的 onCreate()方法,但目前我们的 FirstActivity 内部还什么代码都没有,所以首先你要做的就是在 FirstActivity 中重写 onCreate() 方法,代码如下所示:
package com.example.activitytest;

import android.app.Activity;
import android.os.Bundle;

public class FirstActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

}
可以看到,onCreate()方法非常简单,就是调用了父类的 onCreate()方法。当然这只是默 认的实现,后面我们还需要在里面加入很多自己的逻辑。
(2)创建和加载布局Xml
Android 程序的设计讲究逻辑和视图分离,最好每一个活动都能对应一 个布局,布局就是用来显示界面内容的,因此我们现在就来手动创建一个布局文件。右击 res/layout 目录→New→Android XML File,会弹出创建布局文件的窗口。我们给这 个布局文件命名为 first_layout,根元素就默认选择为 LinearLayout。 这是 ADT 为我们提供的可视化布局编辑器,你可以在屏幕的中央区域预览当前的布局。 在窗口的最下方有两个切换卡,左边是 Graphical Layout,右边是 first_layout.xml。Graphical Layout 是当前的可视化布局编辑器,在这里你不仅可以预览当前的布局,还可以通过拖拽的 方式编辑布局。而 first_layout.xml 则是通过 XML 文件的方式来编辑布局。
(3)定义布局中的元素
对这个布局稍做编辑,添加一个按钮,如下 所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Button" />
</LinearLayout>
这里添加了一个 Button 元素,并在 Button 元素的内部增加了几个属性。android:id 是给 当前的元素定义一个唯一标识符,之后可以在代码中对这个元素进行操作。随后 android:layout_width 指定了当前元素 的宽度,这里使用 match_parent 表示让当前元素和父元素一样宽。android:layout_height 指定 了当前元素的高度,这里使用 wrap_content,表示当前元素的高度只要能刚好包含里面的内 容就行。android:text 指定了元素中显示的文字内容。
(4)在Activity中加载布局文件
重新回到 FirstActivity,在 onCreate()方法中加入如下代码:
package com.example.activitytest;

import android.app.Activity;
import android.os.Bundle;

public class FirstActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    //新增代码,可以看到,这里调用了 setContentView()方法来给当前的活动加载一个布局,
     而在 setContentView()方法中,我们一般都会传入一个布局文件的 id。
    setContentView(R.layout.first_layout); 
    
}

}
在第一章介绍 gen 目录的时 候我有提到过,项目中添加的任何资源都会在 R 文件中生成一个相应的资源 id,因此我们刚 才创建的 first_layout.xml 布局的 id 现在应该是已经添加到 R 文件中了。在代码中去引用布局文件的方法你也已经学过了,只需要调用 R.layout.first_layout 就可以得到 first_layout.xml 布局的 id,然后将这个值传入 setContentView()方法即可。注意这里我们使用的 R,是 com.example.activitytest 包下的 R 文件。
(5)在 AndroidManifest 文件中注册
所有的Activity都要在 AndroidManifest.xml 中进行注册才能生效,那么我们现在就打开 AndroidManifest.xml 来给 FirstActivity 注册吧,代码如下所示:
  
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.My_Applation">
        <activity android:name=".FirstActivity"> //这里修改成了.FirstActivity
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

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

2. Activity的基本使用方法

(1)在Activity中使用Toast
Toast 是 Android 系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的 信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间,我们现 在就尝试一下如何在活动中使用 Toast。
首先需要定义一个弹出 Toast 的触发点,正好界面上有个按钮,那我们就让点击这个按 钮的时候弹出一个 Toast 吧。在 onCreate()方法中添加代码:
   
package com.example.activitytest;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class FirstActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //加载一个布局,传入布局ID R.layout.activity_main
        setContentView(R.layout.first_layout);
        Button toast_button = (Button) findViewById(R.id.toast_button);
        /*通过button触发Toast 通过onCreate方法中添加代码
        *先通过findViewById()获得toast_button这个元素实例,由于这个方法返回的是view对象,需要向下转型成Button对象
        *用setOnClickListener() 给这个实例注册监听器  OnClickListener()  使用语句为 View.OnClickListener()
        * 为什么前面要加个View呢,原因就是后面的OnClickListener是个View类内部的接口,如果直接使用是找不到这个接口的。
        */
        toast_button.setOnClickListener(new View.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。
效果如下图所示:
(2)销毁一个活动:如何销毁一个活动呢? 

其实答案非常简单,只要按一下 Back 键就可以销毁当前的活动了。不过如果你不想通 过按键的方式,而是希望在程序中通过代码来销毁活动,当然也可以,Activity 类提供了一 个 finish()方法,我们在活动中调用一下这个方法就可以销毁当前活动了。 

修改按钮监听器中的代码,如下所示:

public class FirstActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //加载一个布局,传入布局ID R.layout.activity_main
        setContentView(R.layout.first_layout);
        Button toast_button = (Button) findViewById(R.id.toast_button);
        toast_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

(3)使用 Intent 在Activity之间穿梭:那么怎样才能由主Activity跳转到其他Activity呢? 使用显示Intent或隐式Intent

  那我们现在快速地在 ActivityTest 项目中再创建一个Activity。新建一个 second_layout.xml 布局文件,代码如下:  
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Button" />
</LinearLayout>
  我们还是定义了一个按钮,按钮上显示 Button 2。然后新建SecondActivity 继承自 Activity,代码如下:
package com.example.activitytest;

import android.app.Activity;
import android.os.Bundle;

public class SecondActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_layout);
    }
}
  最后在 AndroidManifest.xml 中为 SecondActivity 进行注册。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.My_Applation">
        <activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>
由于 SecondActivity 不是主Activity,因此不需要配置<intent-filter>标签里的内容,注册活 动的代码也是简单了许多。现在第二个活动已经创建完成,剩下的问题就是如何去启动这第 二个活动了,这里我们需要引入一个新的概念,Intent。
Intent 是 Android 程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组 件想要执行的动作,还可以在不同组件之间传递数据。Intent 一般可被用于启动活动、启动 服务、以及发送广播等场景,由于服务、广播等概念你暂时还未涉及,那么本章我们的目光 无疑就锁定在了启动Activity上面。
(1)显示Intent
Intent 的用法大致可以分为两种,显式 Intent 和隐式 Intent,我们先来看一下显式 Intent 如何使用。
Intent 有多个构造函数的重载,其中一个是 Intent(Context packageContext, Class<?> cls)。 这个构造函数接收两个参数,第一个参数 Context 要求提供一个启动活动的上下文,第二个 参数 Class 则是指定想要启动的目标Activity,通过这个构造函数就可以构建出 Intent 的“意图”。 然后我们应该怎么使用这个 Intent 呢?Activity 类中提供了一个 startActivity()方法,这个方法 是专门用于启动Activity的,它接收一个 Intent 参数,这里我们将构建好的 Intent 传入 startActivity()方法就可以启动目标Activity了。修改 FirstActivity 中按钮的点击事件,代码如下所示:
Button button1 = (Button) findViewById(R.id.toast_button);
button1.setOnClickListener(new View.OnClickListener() {
    @Override
public void onClick(View v) {
    Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
} });
我们首先构建出了一个 Intent,传入 FirstActivity.this 作为上下文,传入 SecondActivity.class 作为目标Activity,这样我们的“意图”就非常明显了,即在 FirstActivity 这个活动的基础上打 开 SecondActivity 这个活动。然后通过 startActivity()方法来执行这个 Intent。重新运行程序,在 FirstActivity 的界面点击一下按钮,可以看到,我们已经成功启动 SecondActivity 这个活动了。如果你想要回到上一个活动 怎么办呢?很简单,按下 Back 键就可以销毁当前活动,从而回到上一个活动了。使用这种方式来启动活Intent 的“意图”非常明显,因此我们称之为显式 Intent。
(2)隐式Intent
相比于显式 Intent,隐式 Intent 则含蓄了许多,它并不明确指出我们想要启动哪一个活 动,而是指定了一系列更为抽象的 action 和 category 等信息,然后交由系统去分析这个 Intent, 并帮我们找出合适的活动去启动。
什么叫做合适的活动呢?简单来说就是可以响应我们这个隐式 Intent 的活动,那么目前 SecondActivity 可以响应什么样的隐式 Intent 呢?额,现在好像还什么都响应不了,不过很 快就会有了。
通过在<activity>标签下配置<intent-filter>的内容,可以指定当前活动能够响应的 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>
在<action>标签中我们指明了当前活动可以响应 com.example.activitytest.ACTION_ START 这个 action,而<category>标签则包含了一些附加信息,更精确地指明了当前的活动 能够响应的 Intent 中还可能带有的 category。只有<action>和<category>中的内容同时能够匹 配上 Intent 中指定的 action 和 category 时,这个活动才能响应该 Intent。
修改 FirstActivity 中按钮的点击事件,代码如下所示:
Button button1 = (Button) findViewById(R.id.toast_button);
button1.setOnClickListener(new View.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 的活动。那前 面不是说要<action>和<category>同时匹配上才能响应的吗?怎么没看到哪里有指定 category 呢?这是因为 android.intent.category.DEFAULT 是一种默认的 category,在调用 startActivity()方法的时候会自动将这个 category 添加到 Intent 中。
 
每个 Intent 中只能指定一个 action,但却能指定多个 category。目前我们的 Intent 中只有一个默认的 category,那么现在再来增加一个吧。
修改 FirstActivity 中按钮的点击事件,代码如下所示:
button1.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
          Intent intent = new Intent("com.example.activitytest.ACTION_START");
          intent.addCategory("com.example.activitytest.MY_CATEGORY");
          startActivity(intent);
      }
  });
可以调用 Intent 中的 addCategory()方法来添加一个 category,这里我们指定了一个自定 义的 category,值为 com.example.activitytest.MY_CATEGORY。现在重新运行程序,在 FirstActivity 的界面点击一下按钮,你会发现,程序崩溃了!这 是你第一次遇到程序崩溃,可能会有些束手无策。别紧张,其实大多数的崩溃问题都是很 好解决的,只要你善于分析。在 LogCat 界面查看错误日志,你会看到如图 2.13 所示的错误 信息。
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.example.activitytest.ACTION_START cat=[com.example.activitytest.MY_CATEGORY] }
        at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2067)
错误信息中提醒我们,没有任何一个活动可以响应我们的 Intent,为什么呢?这是因为 我们刚刚在 Intent 中新增了一个 category,而 SecondActivity 的<intent-filter>标签中并没有声 明可以响应这个 category,所以就出现了没有任何活动可以响应该 Intent 的情况。现在我们 在<intent-filter>中再添加一个 category 的声明,如下所示:
<activity android:name=".SecondActivity">
            <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>

(3) Intent的其他用法

  • 启动其他程序的Activity
  • 向下一个Activity传递数据
  • 返回数据给上一个Activity
3. Activity的生命周期
(1)返回栈
Android 是使用任务(Task)来管理Activity的,一个任务就是一组存放在栈里的Activity的集合,这个栈也被称作返回栈(Back Stack)。栈是一种后进先出的数据结构,在默认情况 下,每当我们启动了一个新的Activity,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键或调用 finish()方法去销毁一个Activity时,处于栈顶的Activity会出栈,这时前一个入栈的Activity就会重新处于栈顶的位置。系统总是会显示处于栈顶的Activity给用户。
(2)Activity状态
  每个Activity在其生命周期中最多可能会有四种状态。
  • 运行状态

  当一个Activity位于返回栈的栈顶时,这时Activity就处于运行状态。系统最不愿意回收的就是处于运行状态的Activity,因为这会带来非常差的用户体验。

  • 暂停状态
当一个Activity不再处于栈顶位置,但仍然可见时,这时Activity就进入了暂停状态。你可能会觉得既然Activity已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个Activity都会占满整个屏幕的,比如对话框形式的Activity只会占用屏幕中间的部分区域,你很快就会在后面看到这种Activity。处于暂停状态的Activity仍然是完全存活着的,系统也不愿意去回收这种Activity(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只 有在内存极低的情况下,系统才会去考虑回收这种Activity。
  • 停止状态
当一个Activity不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种Activity保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的Activity有可能会被系统回收。
  • 销毁状态
当一个Activity从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的Activity,从而保证手机的内存充足。

(3)Activity 的生命周期 

  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所示。

 

 (4)活动被回收了怎么办 

前面我们已经说过,当一个活动进入到了停止状态,是有可能被系统回收的。那么想象 以下场景,应用中有一个活动 A,用户在活动 A 的基础上启动了活动 B,活动 A 就进入了 停止状态,这个时候由于系统内存不足,将活动 A 回收掉了,然后用户按下 Back 键返回活 动 A,会出现什么情况呢?其实还是会正常显示活动 A 的,只不过这时并不会执行 onRestart() 方法,而是会执行活动 A 的 onCreate()方法,因为活动 A 在这种情况下会被重新创建一次。

这样看上去好像一切正常,可是别忽略了一个重要问题,活动A中是可能存在临时数据 和状态的。打个比方,MainActivity 中有一个文本输入框,现在你输入了一段文字,然后 启动 NormalActivity,这时 MainActivity 由于系统内存不足被回收掉,过了一会你又点击了 Back 键回到 MainActivity,你会发现刚刚输入的文字全部都没了,因为 MainActivity 被重新创建了。

如果我们的应用出现了这种情况,是会严重影响用户体验的,所以必须要想想办法解决 这个问题。查阅文档可以看出,Activity 中还提供了一个 onSaveInstanceState()回调方法,这 个方法会保证一定在活动被回收之前调用,因此我们可以通过这个方法来解决活动被回收时 临时数据得不到保存的问题。onSaveInstanceState()方法会携带一个 Bundle 类型的参数,Bundle 提供了一系列的方法用于保存数据,比如可以使用 putString()方法保存字符串,使用 putInt()方法保存整型数据, 以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle 中取值, 第二个参数是真正要保存的内容。 

(5)Activity的启动模式 

 

posted on 2021-07-09 15:27  夜尽天明00  阅读(141)  评论(0编辑  收藏  举报