一、前提概要
《第一行代码—Android》是Android初学者的最佳入门书。全书由浅入深、系统全面地讲解了Android软件开发的方方面面。这本书是2014年8月上市,到现在我写下这篇博客为止,已经过了两年,目前,它还是很畅销。可见,在一批新的android开发者眼中,它还是一本启蒙书的存在。这本书的作者郭霖,Android软件开发工程师。从事Android开发工作,有着丰富的项目实战经验,负责及参与开发过多款移动应用与游戏,对Android系统架构及应用层开发有着深入的理解,活跃于CSDN,在CSDN上发表Android技术相关博文,受到大量好评。
接下来,我会记录关于阅读《第一行代码》的学习见解与体会,内容框架并不是按照书上目录而来,如有不妥错误之处,可以评论指正以及补充。
二、四大组件之活动-Activity
(一)Intent
Intent 是 Android 程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent 一般可被用于启动活动、启动服务、以及发送广播等场景。Intent 的用法大致可以分为两种,显式 Intent 和隐式 Intent,我们先来看一下显式 Intent如何使用。
1.显式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);
2.隐式Intent
相比于显式 Intent,隐式 Intent 则含蓄了许多,它并不明确指出我们想要启动哪一个活动, 而是指定了一系列更为抽象的 action 和 category 等信息, 然后交由系统去分析这个 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>
在action
标签中我们指明了当前活动可以响应com.example.bin.helloworld.ACTION_START
这个action,而category
标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的Intent 中还可能带有的category。只有action
和category
中的内容同时能够匹配上Intent 中指定的action 和category 时,这个活动才能响应该Intent。
每个Intent 中只能指定一个action,但却能指定多个category。
Intent intent = new Intent("com.example.activitytest.ACTION_START");
startActivity(intent);
android.intent.category.DEFAULT
是一种默认category
, 在调用startActivity()
方法的时候会自动将这个category
添加到Intent 中。可以调用Intent 中的addCategory()
方法来添加一个category
。
3.更多隐式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
除了http 协议外,我们还可以指定很多其他协议,比如geo 表示显示地理位置、tel 表示拨打电话。调用系统拨号界面。
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
4.向下一个活动传递数据
Intent 中提供了一系列putExtra()
方法的重载,可以把我们想要传递的数据暂存在Intent 中,启动了另一个活动后,只需要把这些数据再从Intent 中取出就可以了。putExtra()
方法接收两个参数,第一个参数是键,用于后面从Intent中取值,第二个参数才是真正要传递的数据。
String data = "I am from FirstActivity";
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);
//获得传递过来的意图
Intent intent = getIntent();
//获得意图中的信息,getStringExtra获得字符串类型,getBooleanExtra获得布尔类型。
String data = intent.getStringExtra("extra_data");
另外,通过intent来传递对象有两种方式Serializable方式和Parcelable方式,这里暂且不提,有兴趣得可以Googel用法。
5.返回数据给上一个活动
Activity 中还有一个 startActivityForResult()方法也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。毫无疑问,这就是我们所需要的。
startActivityForResult()
方法接收两个参数,第一个参数还是 Intent,第二个参数是请求码,用于在之后的回调中判断数据的来源。
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
//第一个参数还是Intent
//第二个参数是请求码,用于在之后的回调中判断数据的来源
startActivityForResult(intent,1);
Intent intent = new Intent();
intent.putExtra("data_return","hello FirstActivity");
//setResult()方法接收两个参数
//第一个参数用于向上一个活动返回处理结果, 一般只使用RESULT_OK 或 RESULT_CANCELED(按Back键后返回)
//第二个参数则是把带有数据的Intent 传递回去,
setResult(RESULT_OK,intent);
finish();
由于我们是使用 startActivityForResult()方法来启SecondActivity 的,在 SecondActivity被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在 FirstActivity 中重写这个方法来得到返回的数据:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//第一个参数requestCode,即我们在启动活动时传入的请求码。
//第二个参数resultCode,即我们在返回数据时传入的处理结果。
//第三个参数data,即携带着返回数据的Intent
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnData = data.getStringExtra("data_return");
Toast.makeText(FirstActivity.this, "返回的值:" + returnData, Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
如果用户点击Back键,也需要返回值,我们可以重写SecondActivity中的onBackPressed()
方法。
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
}
也可以重写onKeyDown()
方法
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 是否触发按键为back键
if (keyCode == KeyEvent.KEYCODE_BACK) {
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
setResult(RESULT_OK, intent);
this.finish();
return true;
}else {
return super.onKeyDown(keyCode, event);
}
}
(二)活动的生命周期
1.返回栈
Android 中的活动是可以层叠的。我们每启动一个新的活动,就会覆盖在原活动之上,然后点击 Back 键会销毁最上面的活动,下面的一个活动就会重新显示出来。其实 Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack) 。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下 Back 键或调用 finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。
2.活动状态
每个活动在其生命周期中最多可能会有四种状态。
1.运行状态
当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。
2.暂停状态
当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。你可能会觉得既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域,你很快就会在后面看到这种活动。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种活动。
3.停止状态
当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。
4.销毁状态
当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。
3.活动的生存期
Activity 类中定义了七个回调方法,覆盖了活动生命周期的每一个环节,下面我来一一介绍下这七个方法。
1.onCreate()
这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等。
2.onStart()
这个方法在活动由不可见变为可见的时候调用。
3.onResume()
这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。
4.onPause()
这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU 的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
5.onStop()
这个方法在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行。
6.onDestroy()
这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
7.onRestart()
这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
4.活动被回收了怎么办
打个比方,MainActivity 中有一个文本输入框,现在你输入了一段文字,然后启动NormalActivity,这时MainActivity 由于系统内存不足被回收掉,过了一会你又点击了Back 键回到MainActivity,你会发现刚刚输入的文字全部都没了,因为MainActivity 被重新创建了。
Activity 中还提供了一个onSaveInstanceState()
回调方法,这个方法会保证一定在活动被回收之前调用。
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something you just typed";
outState.putString("data_key", tempData);
}
onSaveInstanceState()
方法会携带一个Bundle 类型的参数,Bundle 提供了一系列的方法用于保存数据,比如可以使用putString()
方法保存字符串,使用putInt()
方法保存整型数据,以此类推。同样可以使用putParcelable()
,putSerializable()
保存对象。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle 中取值,第二个参数是真正要保存的内容。
恢复数据,使用的onCreate()
方法有一个Bundle 类型的参数。这个参数在一般情况下都是null,但是当活动被系统回收之前有通过onSaveInstanceState()
方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。修改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);
}
……
}
Intent 还可以结合Bundle 一起用于传递数据的,首先可以把需要传递的数据都保存在Bundle 对象中,然后再将Bundle 对象存放在Intent 里。到了目标活动之后先从Intent 中取出Bundle,再从Bundle中一一取出数据。
(三)活动的启动模式
启动模式一共有四种,分别是 standard、singleTop、singleTask 和 singleInstance , 可 以 在 AndroidManifest.xml 中 通 过 给 <activity>
标 签 指 定android:launchMode
属性来选择启动模式。
standard,默认启动模式,系统不管此 Activity 是否已经在返回栈中存在,每次启动 Activity 都会创建该 Activity 的一个新的实例。
singleTop,在启动 Activity 时,如果发现返回栈的栈顶是该 Activity&#x