今天来说一说Android四大组件之一的Activity,类似于iOS的UIViewController。当我们新建一个Android项目时,Android Studio默认会为我们创建一个主活动:MainActivity和一个默认的主布局:activity_main.xml。一个活动想要显示在屏幕上,就必须在AndroidManifest.xml中注册。但是这些工作都有IDE帮我们自动完成了。我们来看AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guiyongdong.activitydemo">
<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/AppTheme">
<!--MainActivity-->
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

Activity声明

我们首先声明了程序的包名package="com.guiyongdong.listviewdemo"

<application>

接下来我们又声明了<application>标签,这个标签标示当前程序的配置,例如我们配置了icon(程序的图标)、label(程序的名称)等。

<activity>

然后我们又在<application>标签内添加了一个<activity>标签,这个标签就是声明一个活动(Avtivity)。记住,所有的活动都必须声明在<application>标签内。name属性表示声明的是哪个Activity,因为我们之前已经声明过了当前的包名, 所有以后声明的Activity只要在这个包中,我们只需要声明.MainActivity就可以了。其实它的全称还是com.guiyongdong.listviewdemo. MainActivity。 我们又看到在<activity>标签内,我们有声明了一个标签<intent-filter>,来看看它们是什么。

<intent-filter>

<intent-filter>这个标签表示intent过滤器,至于intent是什么,我们待会再说。先来看看它声明了什么类型的过滤器。
<intent-filter>标签内,我们可以声明三种标签,分别是:<action><category><data>,这三个标签都是可以作为活动的过滤器。这里我们声明了一个<action>android.intent.action.MAIN和一个<category>android.intent.category.LAUNCHER,这两个值都是Android SDK提供给我们的,它表示当前活动为主活动,另一个表示当前活动为启动活动,注意着两种类型的过滤器必须放在一起,不让运行不起来~。当然,我们也可以自定义其他过滤器的值,这个在我们说到隐式Intent时会自定义。

Intent

Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动,启动服务以及发送广播等场景,当前我们主要讲解Activity,所以我们主要看Intent在活动中的应用。

Intent大致可以分为两种:显式Intent隐式Intent

上面我们说到了,如果一个Activity想要作为程序启动时的主Activity,必须声明声明<intent-filter>标签,而且必须在此标签下声明两个值为固定值的<action><category>。一般情况下我们的程序不可能只有一个活动,如果我们想要再开启一个活动时,该怎么办呢?

显式Intent

开启另一个Activity的最简单的方式就是使用显式Intent,我本人没有开发过Android,也不知道这种方式在项目中的使用率,但我感觉它的使用率应该不低,因为它太简单了。

例如我又新建了一个Avtivity(SecondActivity),系统默认为我们在AndroidManifest.xml中注册,如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.guiyongdong.activitydemo">
<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/AppTheme">
<!--MainActivity-->
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!--SecondActivity-->
<activity android:name=".SecondActivity">
</activity>
</application>
</manifest>

当然,这时候我可以在SecondActivity的标签下声明任何的filter标签,不过现在我不需要声明,因为我现在使用的是显式Intent。我们在MainActivity的布局文件中添加一个按钮,并且添加按钮的点击事件,事件是去打开SecondActivity活动,至于如何布局,以后再说。 代码如下:

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
//显式启动SecondActivity
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
}
});
}
}

至于为何这么写,刚开始我也不明白,好在我有一点java基础,这里奉劝想学习Android的同学,必须打好java基础。

大家可以看到,我在Intent的构造函数中传了两个值,一个是MainActivity.this,一个是SecondActivity.class,它的构造函数为:Intent(Context context, Class<?> cls),第一个参数是表示启动活动的上下文,第二个参数Class表示指定想要启动的目标活动,通过这个构造函数,我们就能构建出Intent的意图(就是我要去干什么)。然后我们调用startActivity方法,把这个明显的意图传递过去就行了。

隐式Intent

相比于显式Intent,隐式Intent则含蓄很多了,它并不明确指出我们想要启动哪一个活动,而是指定了一系列更为抽象的actioncategory等信息,让系统去分析这个Intent,并找出合适的活动去启动。

什么叫合适的活动?我们来想一个问题,系统为什么会启动MainActivity作为程序的主界面,是因为我们声明了如下代码:

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

<action>标签中我们指明了当前活动可以响应android.intent.action.MAIN这个action,而<category>标签则包含了一些附加信息,指明当前活动可以响应android.intent.category.LAUNCHER这个category,只有这两个配置同时匹配,这个活动才是合适的活动。

例如我们为SecondActivity添加如下filter:

<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.guiyongdong.activitydemo.second"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>

然后我修改MainActivity中按钮的点击方法:

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
//隐式启动Intent
Intent intent = new Intent("com.guiyongdong.activitydemo.second");
startActivity(intent);
}
});

我们看到,我直接将action的字符串传了过去,表明想要启动能响应com.guiyongdong.activitydemo.second这个action的活动,我们之前说过,只有action和category同时匹配才能响应,但是这里我们并没有指定category,那是因为android.intent.category.DEFAULT是一种默认的category,在我们调用startActivity方法的时候,会自动的添加这个默认的category到Intent中。

当然,现在我们也可以添加任意自定义的category:

<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.guiyongdong.activitydemo.second"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.guiyongdong.category.my_second"/>
</intent-filter>
</activity>

然后在MainActivity中添加如下代码:

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent("com.guiyongdong.activitydemo.second");
intent.addCategory("com.guiyongdong.category.my_second");
startActivity(intent);
}
});

我们依然能够启动SecondActivity

当然,我们前面也说到了<data>这个标签,它用于更精确地指定当前活动能响应什么类型的数据,<data>标签中主要可以匹配一下内容:

  • android:scheme 用于指定数据协议部分,如http
  • android:host 用于指定数据的主机名部分,如www.baidu.com
  • android:port 用于指定数据的端口部分
  • android:path 用于指定主机名和端口之后的部分
  • android:mimeType 用于指定可以处理的数据类型

至于详细的该怎么用,大家可以去试试。这里列一下打电话的代码:

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
});

当然,前提是我们在AndroidManifest.xml添加打电话的权限
<uses-permission android:name="android.permission.CALL_PHONE"/>

Activity生命周期

当我们启动一个新的Activity的时候,它覆盖在了原来的Activity上,我们想一想,Android是通过上面来管理这些Activity的呢?

这里要说明一种数据结构:栈,栈是一种先进后出的数据结构。我们发现,当我们点击返回键时,最先消失的活动是我们最新添加显示的活动。

Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称为返回栈(Back Stack)。

活动状态
  • 运行状态
    当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动。

  • 暂停状态
    当一个活动不再处于栈顶位置,但仍是可见的,这时活动就进入了暂停状态,因为有时候开启一个新的活动,这个活动并不是占满整个屏幕的。处于暂停状态的活动仍然是完全活着的,系统也不愿意去回收这种活动,只有在内存极低的情况下,系统才会去考虑回收这种活动。

  • 停止状态
    当一个活动不再处于栈顶位置,并且完全不可见时,活动就进入了暂停状态,系统仍然会为此活动保存相应的状态和成员变量,但是,这并不是完全可靠,当内存不足,处于停止状态的活动有可能被系统回收。

  • 销毁状态
    当一个活动从返回栈中移除后就成了销毁状态。系统最倾向于回收处于这种状态的活动。

活动的生存期

Activity类中定义了7个回调方法,覆盖了活动生命周期的每一个环节。

  • onCreate()
    这个方法我们已经见过很多次了,每个活动中我们都重写了这个方法,它会在活动第一次被创建的时候调用。我们应该在这个方法中完成活动的初始化操作,比如加载布局,绑定事件等。

  • onStart()
    这个方法在活动由不可见变为可见的时候调用。

  • onResume()
    这个方法在活动准备好喝用户交互的时候调用。此时活动一定位于返回栈的栈顶,并且处于运行状态。

  • onPause()
    这个方法在系统准备去启动或者恢复另外一个活动的时候调用。通常会在这个方法中奖一些消耗CPU的资源释放掉,但是这个方法的执行速度一定要快,不然会影响到新的活动的使用。

  • onStop()
    这个方法在活动完全不可见的时候调用。它和onPause()方法的区别主要在于,如果启动的新活动是一个对话框式的活动,那么OnPause()方法会得到执行,而onStop()方法并不会执行。

  • onDestory()
    这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。

  • onRestart()
    这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。

看完以上的7个方法(发现这些方法和iOS好相似啊),可以将活动分为3种生存期。

  • 完整生存期。活动在onCreate()方法和onDestroy()方法之间所经历的,就是完整生存期。一般情况下,都是在onCreate()中完成各种初始化,在onDestroy()中完成释放内存的操作。

  • 可见生存期。活动在onStart()方法和onStop()方法之间所经历的,就是可见生存期。我们可以通过这两个方法,合理的管理那些对用户可见的资源。

  • 前台生存期。活动在onResume()方法和onPause()方法之间所经历的就是前台生存期。

如图,完整的描述了Activity的整个生命周期:



活动的启动模式

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

standard

standard是活动的默认启动模式,每当启动一个新的活动,它都会在返回栈中如栈,每次启动都会创建该活动的一个新的实例。

singleTop

加入我们在SecondActivity活动中再启动一个SecondActivity,这时候系统是不会再去创建一个新的SecondActivity活动的。singleTop模式下,当启动活动时如果发现返回栈的栈顶已经是该类型的活动,则认为可以直接使用它,不会再去创建新的活动实例。

singleTask

使用singleTask可以很好的解决重复创建栈顶活动的问题,每次启动该活动时系统首先会在返回栈中检查是否有存在该活动的实例,如果发现已经存在则直接使用该实例,并把这个活动之上的所有活动统统出栈,如果没有发现,就会创建一个新的活动实例。

singleInstance

singleInstance不同于上面的三种模式,它表示活动会启用一个新的返回栈来管理这个活动,因为程序中可以存在多个返回栈。一般情况下,我们希望几个程序共享某个活动时会使用到此模式。

小结

关于Activity的知识还有很多,只有在开发中才能遇到各种问题,继续吧,骚年~