Android详细教程(基础篇):三、Activity初步使用及生命周期

Activity初步

java.lang.Object
   android.content.Context
 	   android.content.ContextWrapper
 	 	   android.view.ContextThemeWrapper
 	 	 	   ↳android.app.Activity

通过以上的继承关系可以发现Activity是Context类的子类,Context表示的是一个上下文,指的就是Android上下文

3.1. Android常用组件包

Android常见组件包:

序号 包名称 描述
1 android.app 提供程序主题运行支持类
2 android.content 提供程序和数据交互访问的支持类
3 android.database 提供数据库操作的支持类
4 android.gaphics 底层的图形库,包括画布、颜色过滤、点、矩形,可以将它们直接绘制到屏幕上
5 android.localtion 定位和相关服务的支持类
6 android.media 提供管理多种音频、视频的媒体接口
7 android.net 提供网络访问的支持类
8 android.os 提供系统服务、消息传输和PC机制
9 android.openGL 提供openGL的工具
10 android.provider 提供访问Android内容访问者的类
11 android.telephony 提供与拨打电话相关的API交互
12 android.view 提供基础的用户界面接口框架
13 android.util 涉及工具性的方法,例如时间日期的操作
14 android.weblist 默认浏览器操作接口
15 android.widget 包含各种UI元素(大部分是可见的)在应用程序的布局中使用

3.2. Activity常用的方法:

Activity在包android.app下:

序号 方法 类型 描述
1 public final View findViewById(int id) 普通 格局组件的ID取得组件对象
2 Public void setEnabled(boolean enabled) 普通 设置是否可编辑
3 Public void setFocusable(boolean focusable) 普通 设置是否默认取得焦点
4 Public final void setProgress(int progress) 普通 设置ProgressBar的进度
5 Public final void setSecondaryProgress(int secondaryProgress) 普通 设置第二进度条的进度
6 Public Windos getWindow() 普通 取得一个Windos对象
7 Public void setContentView(ing layoutResID) 普通 设置显示组件
8 Public void setContentView(View vire) 普通 设置显示组件
9 requestWindosFeature(Windos.FEATURE_NO_TITLE) 普通 设置屏幕全屏显示

Activity就是一个基本的组成单元,而Android项目之中会包含多个Activity程序,通过这些程序可以完成一个个的界面显示及事件处理。
findViewById,此方法非常重要,返回的是View(所有的组件,Android中所有的组件都是View的子类),View也是所有布局管理器的父类

3.3. Activity生命周期:

Activity生命周期的各种方法:
Android详细教程(基础篇):三、Activity初步使用及生命周期Android详细教程(基础篇):三、Activity初步使用及生命周期

Activity生命周期的各种状态:
Android详细教程(基础篇):三、Activity初步使用及生命周期
Activity主要的三种状态:

  • Running(运行):在屏幕前台(位于当前任务堆栈的顶部)
  • Paused(暂停):失去焦点但仍然对用户可见(覆盖Activity可能是透明或未完全遮挡)
  • Stopped(停止):完全被另一个Activity覆盖

3.4. Activity事件方法链

  • 进入Activity
          onCreate -> onStart -> onResume
  • BACK键
          onPause -> onStop -> onDestroy
  • HOME键
          Home键退出:onPause -> onStop
          Home键回来:onRestart -> onStart -> onResume
  • 休眠/恢复
          休眠:  onPause
          恢复:  onResume
  • 旋转屏幕
          未设置android:configChanges:
            onPause -> onStop -> onDestory -> onCreate -> onStart -> onResume
          设置了android:configChanges=“orientation|keyboardHidden”:
              不会触发生命周期方法,参见文章这里。
  • 来电
          来电,显示来电界面:
            onPause -> onStop
          关闭电话界面,重新回到当前Activity:
            onRestart -> onStart -> onResume
  • 其他Activity
          进入下一个Activity:
            onPause -> onStop
          从其他Activity返回至当前Acitivity:
            onRestart -> onStart -> onResume

3.5. 与Activity生命周期结合的应用场景

3.5.1. 与广播(Broadcast)结合

在onResume注册广播(registerLinstener),在onPause注销广播(unregisterLinstener)。 例如:
 做"摇一摇"功能(传感器)、监听网络变化,就可以在onResume中注册监听,在onPause里注销掉,已节省
资源提高效率。

3.5.2. 与服务(Service)结合

在onStart绑定服务(bindService),在onStop中取消绑定(unbindService)。 例如:
 需要通过Service定时更新UI上的数据,而Activity的可见周期在onStart与onStop之间,那么就可以再onStart
时启动服务,在onStop时停止服务。为了节约系统资源,除了提高用户体验以外,开发人员应尽可能的优化
程序。

3.5.3. 与Cursor结合

Cursor的详细信息请见以下Cursor详解
使用managedQuery让Activity帮你管理Cursor的生命周期,不用自己去close。但也有一些问题

问题一:Android中Cursor关闭的问题

Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时
不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。
然而如果Cursor的数据量特别大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时
的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代
码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。
所以我们使用Cursor的方式一般如下:

Cursor cursor = null;   
try{   
    cursor = mContext.getContentResolver().query(uri,null,null,null,null);   
    if(cursor != null){   
        cursor.moveToFirst();   
    //do something   
    }   
}catch(Exception e){   
    e.printStatckTrace();   
}finally{   
    if(cursor != null){   
        cursor.close();   
    }   
}  

有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,
CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭。

@Override     
protected void onDestroy() {           
    if (mAdapter != null && mAdapter.getCurosr() != null) {     
        mAdapter.getCursor().close();     
    }     
    super.onDestroy();      
}  

CursorAdapter中的changeCursor函数,会将原来的Cursor释放掉,并替换为新的Cursor,所以你不用担心原来的Cursor没有被关闭。
你可能会想到使用Activity的managedQuery来生成Cursor,这样Cursor就会与Acitivity的生命周期一致了,多么完美的解决方法!然而事实上managedQuery也有很大的局限性。managedQuery生成的Cursor必须确保不会被替换,因为可能很多程序事实上查询条件都是不确定的,因此我们经常会用新查询的Cursor来替换掉原先的Cursor。因此这种方法适用范围也是很小。

问题二:用 managedQuery() 时需要注意的一个陷阱

Activity 里面提供了一个 managedQuery() 方法,按照 Android SDK 里面的说明,“the activity will manage its lifecycle for you.” 听起来很好,Activity 可以替你管理 Cursor 的生命周期了,就不用记着去 close() 了,代码可以更简洁。

但是 Activity 是怎么去管理 Cursor 的生命周期的呢?SDK 文档没说。最近遇到一个 bug,在一个 Activity 中,用 managedQuery() 查询数据库,将查询得到的 Cursor 用 CursorAdapter 与 ListView 绑定。然后在 Activity 里面执行批量删除数据表记录操作,因为耗时比较长,所以用了多线程处理。测试团队发现的 bug 是,在删除操作进行过程中,如果按下 Home 键,应用就崩溃了。崩溃原因是 Cursor 被释放了,导致工作线程的删除操作异常。
看了 Activity.java 的源码之后就明白为什么会崩溃了。managedQuery() 其实无非就是把查询得到的 Cursor 放到了 Activity 类的一个数组成员变量中,然后当 Activity stop 的时候,将这个数组里的每个 cursor 都关掉,以及在 resume 的时候,将数组里的每个 cursor 都重新查询一次。所以在按下 Home 键之后,Activity 被 stop 了,cursor 也就被关闭了,如果有个线程还在继续使用这个 cursor,就会抛异常了。因此,在用 managedQuery() 的时候,需要清楚 cursor 什么时候会被释放,并考虑好自己的代码在 cursor 被释放后不再需要使用这个 cursor.

3.5.4. 释放资源

可以在onDestory中释放一些资源。比如可以在onDestory时调用MediaPlayer的release。

3.6. 使用Activity需注意

1  所有Activity生命周期方法的实现都必须先调用其父类版本。
2  由于Activity经常会在暂停和恢复之间切换,所以onResume和onPause这两个方法应当是轻量级的。
3  系统再某种紧急情况下需要回收内存,onStop、onDestory可能不会被调用,因此需要在onPause中把需要长期保存的数据保存起来。

3.7. Activity 栈

Android 是通过一种 Activity 栈的方式来管理 Activity 的,一个 Activity 的实例的状态决定它在栈中的位置。处于前台的 Activity 总是在栈的顶端,当前台的 Activity 因为异常或其它原因被销毁时,处于栈第二层的 Activity 将被**,上浮到栈顶。当前的 Activity 启动入栈时,原 Activity 会被压入到栈的第二层。一个 Activity 在栈中的位置变化反映了它在不同状态间的转换。Activity 的状态与它在栈中的位置关系如下图所示:

图 2. Activity 的状态与它在栈中的位置关系
Android详细教程(基础篇):三、Activity初步使用及生命周期
如上所示,除了最顶层即处在 Active 状态的 Activity 外,其它的 Activity 都有可能在系统内存不足时被回收,一个 Activity 的实例越是处在栈的底层,它被系统回收的可能性越大。系统负责管理栈中 Activity 的实例,它根据 Activity 所处的状态来改变其在栈中的位置。

3.8. Activity 的 IntentFilter

Intent Filter 描述了一个组件愿意接收什么样的 Intent 对象,Android 将其抽象为 android.content.IntentFilter 类。
在 Android 的 AndroidManifest.xml 配置文件中可以通过 节点为一个 Activity 指定其 Intent Filter,以便告诉系统该 Activity 可以响应什么类型的 Intent。
当程序员使用 startActivity(intent) 来启动另外一个 Activity 时,如果直接指定 intent 对象的 Component 属性,那么 Activity Manager 将试图启动其 Component 属性指定的 Activity。否则 Android 将通过 Intent 的其它属性从安装在系统中的所有 Activity 中查找与之最匹配的一个启动,如果没有找到合适的 Activity,应用程序会得到一个系统抛出的异常。这个匹配的过程如下:
Activity 中Intent Filter 的匹配过程:
Android详细教程(基础篇):三、Activity初步使用及生命周期

3.9. Activity的Action 匹配

Action 是一个用户定义的字符串,用于描述一个 Android 应用程序组件,一个 Intent Filter 可以包含多个 Action,且至少有一个。
在 AndroidManifest.xml 的 Activity 定义时可以在其 节点指定一个 Action 列表用于标示 Activity 所能接受的“动作”,例如:

<intent-filter> 
<action android:name="android.intent.action.MAIN" /> 
<action android:name="com.zy.myaction" /> 
…… 
</intent-filter>

如果我们在启动一个 Activity 时使用这样的 Intent 对象:

Intent intent =new Intent(); 
intent.setAction("com.zy.myaction");

那么所有的 Action 列表中包含了“com.zy.myaction”的 Activity 都将会匹配成功。
Android 预定义了一系列的 Action 分别表示特定的系统动作。这些 Action 通过常量的方式定义在 android.content. Intent中,以“ACTION_”开头。我们可以在 Android 提供的文档中找到它们的详细说明。

3.10. URI 数据匹配

一个 Intent 可以通过 URI 携带外部数据给目标组件。在 节点中,通过 节点匹配外部数据。
mimeType 属性指定携带外部数据的数据类型,scheme 指定协议,host、port、path 指定数据的位置、端口、和路径。如下:

<data android:mimeType="mimeType" android:scheme="scheme" android:host="host" android:port="port" android:path="path"/>

如果在 Intent Filter 中指定了这些属性,那么只有所有的属性都匹配成功时 URI 数据匹配才会成功。

3.11. Category 类别匹配

节点中可以为组件定义一个 Category 类别列表,当 Intent 中包含这个列表的所有项目时 Category 类别匹配才会成功。

3.12. 一些关于 Activity 的技巧

锁定 Activity 运行时的屏幕方向
Android 内置了方向感应器的支持。在 G1 中,Android 会根据 G1 所处的方向自动在竖屏和横屏间切换。但是有时我们的应用程序仅能在横屏 / 竖屏时运行,比如某些游戏,此时我们需要锁定该 Activity 运行时的屏幕方向,节点的 android:screenOrientation属性可以完成该项任务,示例代码如下:

<activity android:name=".EX01" android:label="@string/app_name" android:screenOrientation="portrait">// 竖屏 , 值为 landscape 时为横屏………… </activity>

全屏的 Activity
要使一个 Activity 全屏运行,可以在其 onCreate()方法中添加如下代码实现:

// 设置全屏模式 
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // 去除标题栏 
requestWindowFeature(Window.FEATURE_NO_TITLE);

在 Activity 的 Title 中加入进度条
为了更友好的用户体验,在处理一些需要花费较长时间的任务时可以使用一个进度条来提示用户“不要着急,我们正在努力的完成你交给的任务”。如下图:
在 Activity 的标题栏中显示进度条不失为一个好办法,下面是实现代码:

// 不明确进度条 
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 
setContentView(R.layout.main); 
setProgressBarIndeterminateVisibility(true);
//明确进度条 
requestWindowFeature(Window.FEATURE_PROGRESS); 
setContentView(R.layout.main); setProgress(5000);

3.13. 小结

  • Android 项目由若干各Activity程序所组成,每一个Activity都是一个java类;

  • 一个Android项目中所有用到的资源都保存在res文件夹中;

  • Android中的组件需要在布局管理器中进行配置,之后在Activity程序中可以使用findViewById()方法查找并进行控制;

  • 在布局管理器中定义的每一个组件都有其对应的操作类,用户可以直接实例化这些类的对象进行组件的定义显示;

  • 标准的Android项目,所有的文字显示信息应该保存在strings.xml文件中。