本节书摘来自异步社区《Android UI基础教程》一书中的第2章,第2.5节理解活动,作者 【美】Jason Ostrander,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.5 理解活动
Android UI基础教程
2.5.1 声明活动
所有的活动都必须在应用程序的清单文件中声明。如果没有声明,在应用第一次运行时会导致抛出异常。下面是时间跟踪应用的一个示例活动的清单条目:
`<activity android:name=".TimeTrackerActivity"`
` `` `` `` android:label="@string/app`_`name">`
` `` `` `` <intent-filter>`
` `` `` `` `` `` `` `` <action android:name="android.intent.action.MAIN" />`
` `` `` `` `` `` `` `` `` `` `` `` <category android:name="android.intent.category.LAUNCHER" />`
` `` `` `` </intent-filter>`
`</activity>`
该清单条目包含诸如类名称和用户标签之类的活动的基本信息。注意到名称属性里的“.”了吗?这是使用列在<application>元素里的完整包名称的一个简洁方法。这个活动同样声明了一个意图过滤器,用于响应系统发送的意图。在本例中,声明了这是应用的主要活动并且应当响应Androdi.intent.category.LAUNCHER意图,当点击主屏幕上的应用图标时,会发送这个意图。运行活动的意图应当在清单文件中被声明。
2.5.2 了解活动的生命周期
活动的生命周期很短—它们不断地被创建和销毁。开发者需要在用户使用应用程序时妥善处理这些转换。你通过继承活动类创建一个活动,并且当活动在不同状态之间跳转时需要实现一系列系统调用的回调函数。活动有3大基本状态,如表2.2所示。
当一个活动第一次被系统实例化时会调用onCreate方法。你应该一直保持实现这个方法。在onCreate方法中你将需要执行活动的基本设置:往视图中绑定数据,为活动设置布局,初始化线程等。你同样应该实现onPause方法。当活动从前台转换到后台时onPause方法是第一个被触发的回调函数,这时应该保存用户进入应用之后所产生任何变化的数据。
提示: onPause方法里应该保存用户期望保存的任何数据。例如,如果你创建了一个电子邮件的应用程序,用户输入的任何文本内容都应该在调用onPause回调函数时保存进数据库中。
你应该始终调用所实现的活动回调函数的超类实现。如果你不这样做,活动会抛出一个异常。
作为在3个状态间跳转的活动,该Activity类会经常触发回调函数。图2.11给出了回调函数的概览以及它们何时会被触发。关于活动要记住两个重要的事情:当活动对于用户不可见时系统将会积极销毁它,回调函数都运行在主线程上,所以你不应在其中执行任何长时间运行或者大量计算的操作。
时间跟踪应用将会需要重写onCreate方法。当你创建定时器逻辑时同样需要重写onDestroy方法。随后,当你要实现数据库时,你将要重写onPause方法来保存任何用户输入的数据。
1.在com.example包中,创建一个名为TimeTrackerActivity.java的文件:
public class TimeTrackerActivity extends Activity {
2.重写onCreate方法并用下面的代码设置视图:
`@Override`
` `` `` ``public void onCreate(Bundle savedInstanceState) {`
` `` `` `` `` ``super.onCreate(savedInstanceState);`
` `` `` `` `` ``setContentView(R.layout.main);`
` `` `` `` `` ``// Initialize the timer`
` `` `` `` `` ``TextView counter = (TextView) findViewById(R.id.counter);`
` `` `` `` `` ``counter.setText(DateUtils.formatElapsedTime(0));`
` `` `` ``if (mTimeListAdapter == null)`
` `` `` `` `` `` `` ``mTimeListAdapter = new TimeListAdapter(this, 0);`
` `` `` `` `` ``ListView list = (ListView) findViewById(R.id.time`_`list);`
` `` `` `` `` ``list.setAdapter(mTimeListAdapter);`
` `` `` ``}`
`}`
此代码通过调用setContentView方法为你的活动设置XML布局文件。SetContentView方法扩展了XML布局并将其添加到活动的视图层次。接下来,findViewById方法检索保存当前时间的TextView的引用。它使用DateUtils格式设置默认时间。最后,会在ListView实例上创建并设置ListAdapter,你会用其往ListView中加载数据。
你应当在活动的onCreate方法中调用setContentView。在onCreate中你可能会多次调用它,但是只会执行最近的调用。一旦视图层次被加载,你就不能再调用setContentView了。然而,你仍然可以自由使用Java API来更新布局。
2.5.3 了解任务和返回堆栈
Android应用通常由一系列的活动构成。系统将这些活动按照任务进行分组。每一个任务都是一个堆栈,代表了一组活动,当用户使用时活动入栈,用户离开时活动出栈(图2.12)。这被叫做返回堆栈。当用户
打开一个和当前活动没有联系的新活动时,新的任务会被创建。每一个任务都有自己的返回堆栈。
关于主线程的一个注解
一个Android应用程序的主线程或者UI线程是触发所有UI事件的地方。每一个你按下的按钮都会产生一个通过主线程派遣的事件。出于这个原因,使用工作者线程来处理长时间的操作就很重要。尝试从工作者线程中更新UI将会导致异常。Android提供了很多处理这个问题的API: Activity.runOnUiThread方法 View.post,View.postDelayed以及View.postInvalidate方法 AsyncTask类 消息处理程序 如果你需要更新应用程序的UI,需要确保是从UI线程更新或者使用上面的API。在本章后面你将学到更多关于AsyncTask的知识。
用一个简单的例子来展示(图2.13)。
1.用户打开一个应用程序。这会创建一个新任务。例子中是ListView。
2.用户通过点击一个列表项进入到一个新的活动。
3.用户点击主页,然后打开一个新的应用。这又会创建一个任务。
4.用户进入到这个任务的一个新的活动,再次按下列表项。
现在有两个任务以及两个返回堆栈。用户可以通过点击主页并点击运行其中的一个应用在两个任务之间切换。另外,在Android 4.0以及之后的版本中,用户可以点击任务切换按钮来切换任务。返回按钮会操作活动的堆栈并从任务中弹出用户正在看的活动。
在返回堆栈中同一个活动出现多次是可能的。当相同的活动能够从不同的地方开始时这就会发生。你应当注意到这些情况,因为存储相同活动的拷贝会很容易就耗费大量的内存空间。对于用户来说这同样是恼人的事情,因为他们不得不一遍遍地按返回按钮以退出应用。
2.5.4 处理配置更改
Android应用常常会面临处理设备配置更改的情况。什么是设备配置更改?最基本的一种就是当旋转设备时朝向的改变。这会把屏幕从竖屏变到横屏。这个设置更改会导致当前活动被摧毁并重新创建。记住活动的生命周期很短,像转动设备这样简单的操作都会导致一个新的活动被创建。
当活动被销毁并重新创建时如何保持输入数据和应用的状态呢?通常你会在活动的onPause方法里保存数据。然而,这仅用于对于用户来说重要的数据。在本例中,你想要保存只和现有存在的活动实例有关的数据。要做到这一点,你可以在onSaveInstanceState回调函数中保存活动的当前状态。与onPause不同,onSaveInstance State并不经常被系统调用。它仅仅在活动被销毁并且可能会被重新建立时被调用。你应当在活动被销毁之前就使用onSaveInstanceState来重建状态。一个好用的方法就是保存当前列表的滚动位置,这样一来你就可以在朝向改变时保存列表位置。下面是一个保存当前列表位置的例子:
`@Override`
`protected void onSaveInstanceState(Bundle outState) {`
` ListView list = (ListView) findViewById(R.id.time`_`list);`
` int pos = list.getFirstVisiblePosition();`
` outState.putInt("first`_`position", pos);`
` super.onSaveInstanceState(outState);`
`}`
当onCreate被创建时,从输入束中检索列表并重新滚动列表。处理像这样的细节会让应用更加用户友好。