当你决定要学习android的时候,这时候就需要制定一个学习路线,而我们应该从哪里入手呢?你可以想象一下,当你打开一个app的时候,第一眼看到的不是高明的算法或出色的架构,而是界面。那么我们今天的主题自然就要从看的到的界面入手了。
我们将学到什么
一、什么是活动(Activity)
二、Activity的创建方法
三、Activity的生命周期
四、Activity之间的通信
五、Activity的栈式管理
六、Activity的启动模式
七、Activity使用小技巧
一、什么是活动(Activity)
活动(Activity)是最容易吸引到用户的地方了,它是一种可以包含用户界面的组件,
主要用于和用户进行交互。一个应用程序中可以包含零个或多个活动,但不包含任何活动的
应用程序很少见,谁也不想让自己的应用永远无法被用户看到吧?
二、Activity的创建方法
创建一个活动的方法有两种,自动创建和手动创建。
自动创建activity
按照上面两张图一步步做就可以了,新手们最好选择Empty Activity(空活动)
手动创建activity
第一步
先建一个class,包名处右击鼠标,选择New -> Java Class
然后会出现这个界面,取个名字就可以点OK了
会出现这个界面
第二步
在layout里New一个layout resource file
出现这个界面,取个名字点击OK
然后我们的layout就建好了
第三步
将建好的layout添加到class中
1.继承AppCompatActivity(以前是直接继承Activity,两者用法差别不大)
2.重写OnCreate方法
3.设置layout
第四步
在AndroidManifest中注册,打开app -> manifests -> AndroidManifests
加入下图中圈出的代码
由于最外层的<manifest>标签中已经通过 package 属性指定了程序的包名是
com.example.administrator,因此在注册活动时这一部分就可以省略了,直接使用.ThirdActivity就足够了,全称为com.example.administrator.ThirdActivity
MainActivity中的有两句声明,说明了MainActivity是我们这个程序的主活动。
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
如果你想让 ThirdActivity作为我们这个程序的主活动,即点击桌面应用程序图标时首先打开的就是这个活动,可以把这两句声明写到ThirdActivity中,这个声明在一个程序中最多只能有一个。
另外需要注意,如果你的应用程序中没有声明任何一个活动作为主活动,这个程序仍然是可以正常安装的,只是你无法在启动器中看到或者打开这个程序。这种程序一般都是作为第三方服务供其他的应用在内部进行调用的,如支付宝快捷支付服务。
这样我们就手动创建好一个activity了。
三、Activity的生命周期
说道activity的生命周期就不得不看一张超级经典的图了
这张图当年在我刚接触android的时候可是给了我一段很痛苦的回忆,简单的来说就是看不懂,所以我就用小白一点的语言来总结一下吧。
首先,我们可以把activity看成是一个人,人都有出生到死亡的过程,activity也是有这个过程,而人在特定的时间会干一些特定的事,比如6岁上小学,12岁上中学,18岁上大学,activity则是把这些时间点交给了我们这些程序员,而我们可以决定它在这些时间点上干什么事。
activity有多少个我们可以决定的时间点呢?
1.onCreate() -- 创建时
这个方法是只在activity被创建时调用一次,可以做一些数据的初始化工作。。
2.onStart -- 开始时
这个方法在每次启动activity时调用,变成“用户可见不可交互”的状态。
3.onResume() -- 重启时
变成和用户可交互的状态,将当前Activity在放在栈的最上端
4.onPaues() -- 暂停时
到这一步是可见但不可交互的,系统会停止动画等消耗CPU的事情。从上文的描述已经知道,应该在这里保存你的一些数据,因为这个时候你的程序的优先级降 低,有可能被系统收回。在这里保存的数据,应该在onResume里读出来。
5.onStop() -- 停止时
变得不可见 ,被下一个activity覆盖了
6.onDestroy() -- 销毁时
这里主要是做一些释放资源的操作。
四、Activity之间的通信
在一个项目中,我们会用到很多的activity,因此我们需要一种特别的机制帮助我们在 Activity 之间传递消息。
Intent的使用
Intent是一种消息传递的机制,它负责对操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。
我们有两种形式来使用Intent:
1.使用显式Intent
通过指定具体的组件类,通知应用启动对应的组件。
比如我们从MainActivity跳转到SecondActivity,并传一个字符串。
MainActivity中的代码
Intent intent = new Intent();
intent.setClass(MainActivity.this,SecondAcvivity.class);
intent.putExtra("values","传个值");
startActivity(intent);
SecondActivity中的代码
Intent intent = getIntent();
String values = intent.getStringExtra("values");
2.使用隐式Intent
相比于显式 Intent,隐式 Intent 则含蓄了许多,它并不明确指出我们想要启动哪一个活动,而是指定了一系列更为抽象的 action和 category等信息,然后交由系统去分析这个 Intent,并帮我们找出合适的活动去启动。
什么叫做合适的活动呢?简单来说就是可以响应我们这个隐式 Intent的活动,那么目前activity 可以响应什么样的隐式 Intent 呢?额,现在好像还什么都响应不了,不过很快就会有了。
我们还是要从MainActivity跳转到SecondActivity,这时我们可以通过在<activity>标签下配置<intent-filter>的内容,可以指定当前活动能够响应的 action
和 category,打开 AndroidManifest.xml,添加如下代码
<activity android:name=".SecondActivity" >
<intent-filter>
<action android:name="com.example.administrator.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。
然后我们修改MainActivity中的Intent代码:
Intent intent = new Intent("com.example.administrator.ACTION_START");
startActivity(intent);
可以看到,我们使用了 Intent 的另一个构造函数,直接将 action 的字符串传了进去,表
明我们想要启动能够响应 com.example.activitytest.ACTION_START这个 action 的活动。那前面不是说要<action>和<category>同时匹配上才能响应的吗?怎么没看到哪里有指定category 呢?这是因为 android.intent.category.DEFAULT 是一种默认的 category,在调用startActivity()方法的时候会自动将这个 category 添加到 Intent 中。
这种方式同样可以启动 SecondActivity。不同的是,这次你是使用了隐式 Intent 的方式来启动的。
更多隐式 Intent 的用法
使用隐式 Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,
这使得 Android 多个应用程序之间的功能共享成为了可能。比如说你的应用程序中需要展示一个网页,这时你没有必要自己去实现一个浏览器(事实上也不太可能),而是只需要调用系统的浏览器来打开这个网页就行了。
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
这里我们首先指定了 Intent 的 action 是 Intent.ACTION_VIEW,这是一个 Android 系统内置的动作,其常量值为 android.intent.action.VIEW。然后通过 Uri.parse()方法,将一个网址字符串解析成一个 Uri 对象,再调用 Intent的 setData()方法将这个 Uri 对象传递进去。
与此对应,我们还可以在<intent-filter>标签中再配置一个<data>标签,用于更精确地指
定当前活动能够响应什么类型的数据。<data>标签中主要可以配置以下内容。
- android:scheme
用于指定数据的协议部分,如上例中的 http 部分。 - android:host
用于指定数据的主机名部分,如上例中的 www.baidu.com部分。 - android:port
用于指定数据的端口部分,一般紧随在主机名之后。 - android:path
用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。 - android:mimeType
用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
只有<data>标签中指定的内容和 Intent 中携带的 Data完全一致时,当前活动才能够响应该 Intent。不过一般在<data>标签中都不会指定过多的内容,如上面浏览器示例中,其实只需要指定 android:scheme 为 http,就可以响应所有的 http 协议的 Intent 了。
返回数据给上一个活动
既然可以传递数据给下一个活动,那么能不能够返回数据给上一个活动呢?答案是肯定的。不过不同的是,返回上一个活动只需要按一下 Back 键就可以了,并没有一个用于启动活动 Intent 来传递数据。通过查阅文档你会发现,Activity 中还有一个startActivityForResult()方法也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。毫无疑问,这就是我们所需要的。
startActivityForResult()方法接收两个参数,第一个参数还是 Intent,第二个参数是请求码,用于在之后的回调中判断数据的来源.代码如下所示:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivityForResult(intent, 1);
返回代码如下
Intent intent = new Intent();
intent.putExtra("data_return", "Hello MainActivity");
setResult(RESULT_OK, intent);
finish();
可以看到,我们还是构建了一个 Intent,只不过这个 Intent 仅仅是用于传递数据而已,
它没有指定任何的“意图”。紧接着把要传递的数据存放在 Intent 中,然后调用了 setResult()方法。这个方法非常重要,是专门用于向上一个活动返回数据的。setResult()方法接收两个参数,第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_OK或RESULT_CANCELED这两个值,第二个参数则是把带有数据的 Intent 传递回去,然后调用了 finish()方法来销毁当前活动。
由于我们是使用 startActivityForResult()方法来启动 SecondActivity 的,在 SecondActivity被销毁之后会回调上一个活动的 onActivityResult()方法,因此我们需要在 FirstActivity 中重写这个方法来得到返回的数据,如下所示:
@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:
}
}
onActivityResult()方法带有三个参数:
第一个参数 requestCode,即我们在启动活动时传入的请求码。
第二个参数 resultCode,即我们在返回数据时传入的处理结果。
第三个参数 data,即携带着返回数据的 Intent。
由于在一个活动中有可能调用 startActivityForResult()方法去启动很多不同的活动,每一个活动返回的数据都会回调到 onActivityResult()这个方法中,因此我们首先要做的就是通过检查 requestCode 的值来判断数据来源。确定数据是从SecondActivity 返回的之后,我们再通过 resultCode 的值来判断处理结果是否成功。最后从data中取值并打印出来,这样就完成了向上一个活动返回数据的工作。
五、Activity的栈式管理
Android针对Activity的管理使用的是栈,就是说某一个时刻只有一个Activity处在栈顶,当这个Activity被销毁后,下面的Activity才有可能浮到栈顶,或者有一个新的Activity被创建出来,则旧的Activity就被压栈沉下去了。Activity是Android程序的表现层。程序的每一个显示屏幕就是一个Activity。正在运行的Activity处在栈的最顶端,它是运行状态的。
当在程序中调用 Activity.finish()方法时,结果和用户按下 BACK 键一样:它告诉 Activity Manager该Activity实例可以被“回收”。随后 Activity Manager 激活处于栈第二层的 Activity ,把原 Activity 压入到栈的第二层,从 Running 状态转到 Paused 状态。
举个栗子,我们吃冰糖葫芦的时候,都是从最上面的一颗开始吃,只有当最上面的那颗吃完了,我们这才会开始吃下面的一颗,所以我们正在吃的,永远是最上面的那颗。(ˇˍˇ) ~
六、Activity的启动模式
活动的启动模式对你来说应该是个全新的概念,在实际项目中我们应该根据特定的需求为每个活动指定恰当的启动模式。启动模式一共有四种,分别是 standard、singleTop、singleTask和singleInstance,可以在 AndroidManifest.xml 中通 过给<activity> 标签 指android:launchMode属性来选择启动模式。下面我们来逐个进行学习。
1.standard
standard 是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种启动模式。经过上面的学习,你已经知道了 Android 是使用返回栈来管理活动的,在 standard 模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard 模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
2.singleTop
singleTop也是发送新的实例,但不同standard的一点是,在请求的Activity正好位于栈顶时(配置成singleTop的Activity),不会构造新的实例
<activity
android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
3.singleTask
活动在整个应用程序的上下文中只存在一个实例,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
<activity
android:name=".MainActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
4.singleInstance
singleInstance模式应该算是四种启动模式中最特殊也最复杂的一个了,你也需要多花点功夫来理解这个模式。不同于以上三种启动模式,指定为 singleInstance模式的活动会启用一个新的返回栈来管理这个活动,这个模式主要解决了在不同app之间的共享活动实例的问题。
网上找了个栗子,如果开启一个导游服务类的应用程序,里面有个Activity是开启GOOGLE地图的,当按下home键退回到主菜单又启动GOOGLE地图的应用时,显示的就是刚才的地图,实际上是同一个Activity,实际上这就引入了singleInstance。singleInstance模式就是将该Activity单独放入一个栈中,这样这个栈中只有这一个Activity,不同应用的intent都由这个Activity接收和展示,这样就做到了共享。当然前提是这些应用都没有被销毁,所以刚才是按下的HOME键,如果按下了返回键,则无效。
<activity
android:name=".MainActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
七、Activity使用小技巧
你已经掌握了关于活动非常多的知识,但运用的技巧却是多种多样。所以,在这里我准备教你几种关于活动的最佳实践技巧,这些技巧在你以后的开发工作当中将会非常受用。
知晓当前是在哪一个活动
这个技巧将教会你,如何根据程序当前的界面就能判断出这是哪一个活动。
因为在我们真正进入企业后,可能会接手别人写的代码。阅读别人的代码时有一个很头疼的问题,就是你需要在某个界面上修改一些非常简单的东西,但是你半天找不到这个界面对应的活动是哪一个。
我们还是在上面的Demo的基础上修改。首先需要新建一个 BaseActivity 继承自AppCompatActivity,然后在 BaseActivity 中重写 onCreate()方法,如下所示:
public class BaseActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
Log.d("BaseActivity", getClass().getSimpleName());
}
}
接下来我们需要让 BaseActivity 成为 Demo项目中所有活动的父类。修改MainActivity、SecondActivity 和 ThirdActivity 的继承结构,让它们不再继承自 AppCompatActivity,而是继承自 BaseActivity。
这样每当我们进入到一个活动的界面,该活动的类名就会被打印出来,这样我们就可以时时刻刻知晓当前界面对应的是哪一个活动了。
随时随地退出程序
如果目前你手机的界面还停留在 ThirdActivity,你会发现当前想退出程序是非常不方便的,需要连按三次 Back 键才行,如果我们的程序需要一个注销或者退出的功能该怎么办呢?必须要有一个随时随地都能退出程序的方案才行。
其实解决思路也很简单,只需要用一个专门的集合类对所有的活动进行管理就可以了,
下面我们就来实现一下。
public class ActivityCollector {
public static List<AppCompatActivity> activities = new ArrayList<>();
/**
* 添加
*/
public static void addActivity(AppCompatActivity activity) {
activities.add(activity);
}
/**
* 移除
*/
public static void removeActivity(AppCompatActivity activity) {
activities.remove(activity);
}
/**
* 结束所有
*/
public static void finishAll() {
for (AppCompatActivity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
在活动管理器中,我们通过一个 List 来暂存活动,然后提供了一个addActivity()方法用于向 List 中添加一个活动,提供了一个 removeActivity()方法用于从 List 中移除活动,最后提供了一个 finishAll()方法用于将 List 中存储的活动全部都销毁掉。
接下来修改 BaseActivity 中的代码,如下所示:
public class BaseActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
Log.d("BaseActivity", getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
在 BaseActivity 的 onCreate()方法中调用了 ActivityCollector 的addActivity()方法,表明将当前正在创建的活动添加到活动管理器里。然后在 BaseActivity 中重写 onDestroy()方法,并调用了 ActivityCollector 的 removeActivity()方法,表明将一个马上要销毁的活动从活动管理器里移除。
以后不管在什么地方想要退出应用,都可以调用如下代码
ActivityCollector.finishAll();
当然你还可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全退出。
启动活动的最佳写法
启动活动的方法相信你已经非常熟悉了,首先通过 Intent 构建出当前的“意图”,然后调用 startActivity()或 startActivityForResult()方法将活动启动起来,如果有数据需要从一个活动传递到另一个活动,也可以借助 Intent 来完成。
假设 SecondActivity 中需要用到两个非常重要的字符串参数,在启动 SecondActivity 的
时候必须要传递过来,那么我们很容易会写出如下代码:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("param1", "data1");
intent.putExtra("param2", "data2");
startActivity(intent);
这样写是完全正确的,但如果SecondActivity不是你自己写的,而你也不知道需要传递哪些数据,那就尴尬了。
但如果在SecondActivity里有这样一个方法
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 需要传递哪些数据。另外,这样写还简化了启动活动的代码,现在只需要一行代码就可以启动 SecondActivity
SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
我也是刚接触android没多久,如发现有错误或者什么好的建议,欢迎指正!