第4章 Android生命周期
经过上一章的学习,主要了解了Android应用程序设计的基础知识,对Android程序的开发有了一定的了解。在此基础上,本章将对Android系统的进程优先级的变化方式、Android系统的4大基本组件、Activity的生命周期中各个状态的变化关系、Android应用程序的调试方法和工具进行学习。
4.1 程序生命周期
所谓的应用程序生命周期就是应用程序进程从创建到消亡的整个过程。在Android中,多数情况下每个程序都是在各自独立的Linux进程中运行的。当一个程序或其某些部分被请求时,它的进程就“出生”了;当这个程序没有必要再运行下去且系统需要回收这个进程的内存用于其他程序时,这个进程就“死亡”了。可以看出,Android程序的生命周期是由系统控制而非程序自身直接控制。这和编写桌面应用程序时的思维有一些不同,一个桌面应用程序的进程也是在其他进程或用户请求时被创建,但是往往是在程序自身收到关闭请求后执行一个特定的动作(如从main方法中return)而导致进程结束的。
简而言之,程序的生命周期是在Android系统中进程从启动到终止的所有阶段,也就是Android程序启动到停止的全过程,程序的生命周期是由Android系统进行调度和控制的。
但是,一个不容忽视的问题就是,手机的内存是有限的,随着打开的应用程序数量的增多,随之而来的可能会是应用程序响应时间过长或者系统假死的糟糕情况。所以,若将Android应用程序生命周期交由系统处理的话,那么在系统内存不足的情况下,便由Android系统舍车保帅,选择性地来终止一些重要性较次的应用程序,以便回收内存供更重要的应用程序使用。
图4-1 Android系统进程优先级
那么,系统是根据一个什么样的重要性标准来终止Android应用程序的呢?
Android根据应用程序的组件及组件当前运行状态将所有的进程按重要性程度从高到低划分了五个优先级:前台进程、可见进程、服务进程、后台进程、空进程。
以下就按优先级由高到低的顺序介绍Android系统中的进程。
1.前台进程
前台进程是显示在屏幕最前端并与用户正在交互的进程,是Android系统中最重要的进程,包含以下4种情况。
❑ 进程中的Activity正在与用户进行交互。
❑ 进程服务被Activity调用,而且这个Activity正在与用户进行交互。
❑ 进程服务正在执行声明周期中的回调方法,如onCreate()、onStart()或onDestroy()
❑ 进程的BroadcastReceiver正在执行onReceive()方法。
Android系统在多个前台进程同时运行时,可能会出现资源不足的情况,此时会清除部分前台进程,保证主要的用户界面能够及时响应。
2.可见进程
可见进程指部分程序界面能够被用户看见,却不在前台与用户交互,不响应界面事件(其onPause()方法已被调用)的进程。如果一个进程包含服务,且这个服务正在被用户可见的Activity调用,此进程同样被视为可见进程。
Android系统一般存在少量的可见进程,只有在特殊的情况下,Android系统才会为保证前台进程的资源而清除可见进程。
3.服务进程
服务进程是指包含由startService()方法启动服务的进程。它有以下特性:没有用户界面;在后台长期运行。例如,后台MP3播放器或后台上传下载数据的网络服务。
Android系统除非不能保证前台进程或可见进程所必要的资源,否则不强行清除服务进程。
4.后台进程
后台进程是指不包含任何已经启动的服务,而且没有任何用户可见的Activity的进程。这些进程不直接影响用户的体验。
Android系统中一般存在数量较多的后台进程,因此这些进程会被保存在一个列表中,以保证在系统资源紧张时,系统将优先清除用户较长时间没有见到的后台进程。
5.空进程
空进程是不包含任何活跃组件的进程。一般保留这些进程,是为了将其作为一个缓存,在它所属的应用组件下一次需要时,缩短启动的时间。
空进程在系统资源紧张时会被首先清除,但为了提高Android系统应用程序的启动速度,Android系统会将空进程保存在系统内存中,在用户重新启动该程序时,空进程会被重新使用。
问:除了以上的优先级外,还有其他因素决定进程的优先级吗?
答:
❑ 进程的优先级取决于所有组件中的优先级最高的部分。
❑ 进程的优先级会根据与其他进程的依赖关系而变化。
4.2 Android组件
组件是可以调用的基本功能模块。Android应用程序就是由组件组成的,Android系统有4个重要的组件,分别是Activity、Service、BroadcaseReceiver和ContentProvider。
Activity是Android程序的呈现层,显示可视化的用户界面,并接收与用户交互所产生的界面事件。在界面上的呈现形式就是全屏窗体、非全屏悬浮窗体的对话框,与在桌面系统上的独立事业,如办公应用等类似。Activities是可执行的代码块,由用户或者操作系统来进行初始实例化,并在他们被需求时致以运行。Activities可以与用户、请求数据或者其他Activity、Service的服务通过query或Intent进行交互。大部分为Android编写的可执行代码将以Activity的形式执行。对于一个Android应用程序来说,可以包含一个或多个Activity,一般在程序启动后会呈现一个Activity,用于提示用户程序已经正常启动。当它不积极运行时,Activity可以被操作系统终止以节省内存。
Service用于没有用户界面,但需要长时间在后台运行的应用。它类似于桌面应用或者服务器操作系统上的服务或守护进程。Service是在后台运行的可执行的代码块,从它被初始化一直运行到该程序关闭。一个Service的典型的例子是一个MP3播放器,尽管用户已经使用其他应用程序,但仍然需要持续播放文件。你的应用程序可能需要在没有用户界面的情况下一直执行Service来实现后台任务。
Broadcast和Intent Receivers对从其他的应用程序的服务请求做出一个全系统广播的响应,这些广播响应可能来自于Android系统本身或者是任何在其系统上运行的程序。BroadcaseReceiver是用来接受并响应广播消息的组件。它不包含任何用户界面,但可以通过启动Activity或者Notification通知用户接收到重要信息。
问:Notification如何提示用户?
答:闪动背景灯、振动设备、发出声音或在状态栏上放置一个持久的图标。
Activity或Service通过执行一个IntentReceiver为其他应用程序提供了访问其功能的功能。Intent Receiver是一段可执行代码块,对其他Activity的数据或服务请求做出响应。请求的Activity(客户端)生成一个Intent,把其添加至Android Framework中,来指出哪些应用程序(目标程序)接收并对其做出响应。Intent是Android的主要构成元素之一,它从现有的应用程序中创造新的应用程序。Intent实现了应用程序和其他的应用程序和服务交换所需信息的功能
ContentProvider是Android系统提供的一种标准的共享数据的机制,应用程序可以通过ContentProvider访问其他应用程序的私有数据(私有数据可以是存储在文件系统中的文件,也可以是SQLite中的数据库)。Android系统内部也提供一些内置的ContentProvider,能够为应用程序提供重要的数据信息。
所有Android组件都具有自己的生命周期,是从组件建立到组件销毁的整个过程。在生命周期中,组件会在可见、不可见、活动、非活动等状态中不断变化。
4.3 Activity生命周期
Activity生命周期指Activity从启动到销毁的过程。Activity表现为4种状态,分别是活动状态、暂停状态、停止状态和非活动状态。
❑ 活动状态,Activity在用户界面中处于最上层,完全能被用户看到,能够与用户进行交互。
❑ 暂停状态,Activity在界面上被部分遮挡,该Activity不再处于用户界面的最上层,且不能够与用户进行交互;或者屏幕被锁定。
❑ 停止状态,Activity在界面上完全不能被用户看到,也就是说这个Activity被其他Activity全部遮挡。
❑ 非活动状态,不在以上3种状态中的Activity则处于非活动状态。
这四种状态是可以相互转换的,转换关系图如图4-2所示。
图4-2 Activity的4种状态的转换关系图
图4-3 解释了状态之间转化的可能路径。其中着色的椭圆表示活动的主要状态,矩形表示当活动在状态之间转换时会被调用的回调方法。
图4-3 Activity活动周期
Android调用以下的事件回调方法通知Activity从某一状态转变到另一状态。
代码清单4-1事件的回调方法
public class MyActivity extends Activity { protected void onCreate(Bundle savedInstanceState); protected void onStart(); protected void onRestart(); protected void onResume(); protected void onPause(); protected void onStop(); protected void onDestroy(); }
表4-1对各个事件回调方法做出说明。
表4-1 Activity 生命周期的事件回调方法
Activity事件回调方法的调用顺序,如图4-4所示。
图4-4 Activity事件回调方法的调用顺序
Activity的生命周期可分为全生命周期、可视生命周期和活动生命周期。每个生命周期中包含不同的事件回调方法。
4.3.1 全生命周期
全生命周期是从Activity建立到销毁的全部过程,始于onCreate(),结束于onDestroy()。
使用者通常在onCreate()中初始化用户界面,分配引用类变量,绑定数据控件,并创建服务和线程等Activity所能使用的全局资源和状态,并在onDestroy()中释放这些资源,并确保所有外部连接被关闭,例如,网络或数据库的联系等;在一些极端的情况下,Android系统会不调用onDestroy()方法,而直接终止进程。
为了避免创造短期对象和增加垃圾收集的时间,以致对用户体验产生直接影响。如果你的Activity需要创建一些对象的话,最好在onCreate方法中创建,因为它在一个Actvity的完整生命周期中仅调用一次。
4.3.2 可视生命周期
可视生命周期是Activity在界面上从可见到不可见的过程,开始于onStart(),结束于onStop()。
❑ onStart()一般用来初始化或启动与更新界面相关的资源。
❑ onStop()一般用来暂停或停止一切与更新用户界面相关的线程、计时器和服务。
❑ onRestart()方法在onSart()前被调用,用来在Activity从不可见变为可见的过程中,进行一些特定的处理过程。
❑ onStart()和onStop()会被多次调用。
❑ onStart()和onStop()也经常被用来注册和注销BroadcastReceiver或者传感器。
在onStart()和onStop()这两个方法中间,Actvity对用户将会是可见的,尽管它可能部分被遮挡着。在一个Activity完整的生命周期中可能会经过几个Activity可见的生命周期,因为Activity可能会经常在前台和后台之间切换。在极端情况下,系统将销毁掉一个Activity即使它在可见状态并且不调用onStop方法。
4.3.3 活动生命周期
活动生命周期是Activity在屏幕的最上层,并能够与用户交互的阶段,开始于onResume(),结束于onPause()。在Activity的状态变换过程中onResume()和onPause()经常被调用,因此这两个方法中应使用更为简单、高效的代码。
❑ onPause()是第一个被标识为“可终止”的方法。
❑ 在onPause()返回后,onStop()和onDestroy()随时能被Android系统调用。
❑ onPause()常用来保存持久数据,如界面上用户的输入信息等。
当系统而不是用户关闭一个活动来节省内存时,用户可能希望返回到活动且是它之前的状态。为了获得活动被关闭之前的状态,可以执行活动的onSaveInstanceState()方法。Android在活动容易被销毁前调用这个方法,也就是调用onPause()之前。该方法的参数是一个Bundle对象,这个对象可以名值对记录活动的动态状态。当活动再次启动时,Bundle同时被传递到onCreate()和调用onCreate()之后的方法onRestoreInstanceState()。
因为onSaveInstanceState()方法不总被调用,你应该仅使用onSaveInstanceState()来记录活动的临时状态,而不是持久的数据。应该使用onPause()来存储持久数据。
问:onPause()和onSaveInstanceState()这两个函数都可以用来保存界面的用户输入数据,它们有什么区别呢?
答:
❑ onPause()一般用于保存持久性数据,并将数据保存在存储设备上的文件系统或数据库系统中的。
❑ onSaveInstanceState()主要用来保存动态的状态信息,信息一般保存在Bundle中。
➢ Bundle是能够保存多种格式数据的对象。
➢ onSaveInstanceState()保存在Bundle中的数据,系统在调用onRestoreInstanceState()和onCreate()时,会同样利用Bundle将数据传递给函数。
当一个活动启动另一个活动时,这两个活动都经历生命周期转换。一个暂停或是停止,然而被启动的活动则启动。有时,这些活动可能需要协调。当这两个活动在同一个进程中,生命周期的回调顺序是明确界定的:调用当前活动的onPause()方法;然后,按序调用启动活动的onCreate()、onStart()、onResume()方法;之后,如果该活动不需再在屏幕上可见,则调用它的onStop()方法。下面我们就来详细学习一下关于Android如何管理多个Activity。
(1)Android用Activity Stack来管理多个Activity,因此,同一时刻只会有最顶上的Activity是处于active或者running状态。其他的Activity都被压在下面。
(2)如果非活动的Activity仍是可见的(如果上面压着的是一个非全屏的Activity或透明的Activity),它是处于paused状态的。在系统内存不足的情况下,paused状态的Activity是有可能被系统销毁掉的。
注意
因为Android应用程序的生存期并不是由应用本身直接控制的,而是由Android系统平台进行管理的,所以,对于开发者而言,需要了解不同的组件Activity、Service和IntentReceiver的生命,切记:如果组件的选择不当,系统很有可能会关闭一个正在进行重要工作的进程。
4.4 Activity启动模式
Activity作为Android中重要一环,它有4种不同的启动模式,类似于C语言中的局部变量、全局变量及静态变量等。这4种启动模式如下。
❑ standard:标准模式,调用startActivity()方法就会产生一个新的实例。
❑ singleTop:检查是否已经存在了一个实例位于Activity Stack的顶部,如果存在就不产生新的实例,反之则调用Activity的newInstance()方法产生一个新实例。
❑ singleTask:在一个新的Task中产生这个实例,以后每次调用都会使用此实例,而避免产生新的实例。
❑ singleInstance:这个基本上跟singleTask一样,只是有一点不同,那就是在这个模式下的Activity实例所处的Task中,只能有这一个Activity实例,而不能有其他的实例。
这些启动模式在Android清单文件AndroidManifest.xml中,通过<activity>中的launchMode属性进行设置,如代码清单4-2所示。
代码清单4-2 AndroidManifest.xml
<activity android:name=".Activity2" android:launchMode="singleTask"></activity>
也可以在Eclipse ADT图形界面中编辑,如图4-5所示。
图4-5 设置Activity启动模式
下面通过一个简单的例子——LaunchMode_Test来对四种启动模式进行简要分析。在该例中涉及Fx_Main、Activity2及Activity3三个Activity。
下面介绍一下例子中涉及的三个Activity及其界面。
首先是Fx_Main,其界面如图4-6所示。
图4-6 Fx_Main的界面
在图4-6所示的界面中,单击“跳转到AC2”按钮之后,跳转至Activity2,具体代码如代码清单4-3所示。
代码清单4-3 Fx_Main.Activity
import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class Fx_Main extends Activity { /** Called when the activity is first created. */ private Button b; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView tv=(TextView)findViewById(R.id.TextView01); tv.setText("Main---->"+getTaskId()); Log.i("System.out", "Main---->"+this.toString()+"Task ID---->"+getTaskId()); b=(Button)findViewById(R.id.Button01); b.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Fx_Main.this,Activity2.class); startActivity(i); }}); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("System.out", "Fx_Main--->Destory"); } }
其次是Activity2,其界面如图4-7所示。
图4-7 Activity2的界面
在该界面中,单击“跳回到Main”按钮,则跳转至Fx_Main,而单击“跳到本页面”则仍显示Activity2的界面,单击“跳到AC3”则跳转到Activity3的界面,具体代码如代码清单4-4所示。
代码清单4-4 Activity2.Activity
import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class Activity2 extends Activity { private Button b; private Button b2; private Button b3; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity2); b=(Button)findViewById(R.id.Button02); b2=(Button)findViewById(R.id.Button03); b3=(Button)findViewById(R.id.Button04); TextView tv=(TextView)findViewById(R.id.TextView02); tv.setText("Ac2---->"+getTaskId()); Log.i("System.out", "Ac2---->"+this.toString()+"Task ID---->"+getTaskId()); b.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity2.this,Fx_Main.class); startActivity(i); }}); b2.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity2.this,Activity2.class); startActivity(i); }}); b3.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity2.this,Activity3.class); startActivity(i); }}); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("System.out", "Ac2--->destory"); } }
最后是Activity3,其界面如图4-8所示。
图4-8 Activity3的界面
如图4-8所示,单击“返回Main”则跳转至Fx_Main,单击“返回AC2”,则跳转到Activity2。具体代码如代码清单4-5所示。
代码清单4-5 Activity3.Activity
import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class Activity3 extends Activity { private Button b; private Button b2; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity3); b=(Button)findViewById(R.id.Button03); b2=(Button)findViewById(R.id.Button04); TextView tv=(TextView)findViewById(R.id.TextView03); tv.setText("Ac3---->"+getTaskId()); Log.i("System.out", "Ac3---->"+this.toString()+"Task ID---->"+getTaskId()); b.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity3.this,Fx_Main.class); startActivity(i); }}); b2.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity3.this,Activity2.class); startActivity(i); }}); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("System.out", "Ac3--->Destory"); } }
4.4.1 standard标准模式
在standard模式也就是默认模式下,不需要配置launchMode。此时的AndroidManifest.xml如代码清单4-6所示。
代码清单4-6 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="feixun.com.jiang" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Fx_Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Activity2" android:label="@string/Ac2"/ > <activity android:name=".Activity3" android:label="@string/Ac3/> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
运行例子,从Fx_Main开始,一直点回到Activity2按钮时,Log信息如图4-9所示。
图4-9 Standard启动模式下Log信息
发现每次都创建了Activity2的新实例。standard的加载模式就是这样的,Intent将发送给它新的Activity实例。
现在点击Android设备的回退键,可以看到Log信息按照刚才创建Activity实例的倒序依次出现,类似退栈的操作,而刚才操作跳转按钮的过程是压栈的操作。
4.4.2 singleTop
singleTop和standard模式,都会将Intent发送到新的实例(如果已经有了,singleTask模式和singleInstance模式不发送到新的实例)。不过,singleTop要求如果创建intent时栈顶已经有要创建Activity的实例,则将Intent发送给该实例,而不发送给新的实例。
还是用刚才的示例,只需将Activity2的launchMode改为singleTop,就能看到区别。修改后AndroidManifest.xml中代码如代码清单4-7所示。
代码清单4-7 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="feixun.com.jiang" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Fx_Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Activity2" android:label="@string/Ac2" android:launchMode="singleTop"/ > <activity android:name=".Activity3" android:label="@string/Ac3/> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
运行Fx_Main,跳转到Activity2---->Actvity2时会发现,单击多少遍按钮,都是相同的Activity2实例,因为该实例在栈顶,所以不会创建新的实例。如果回退,回到Fx_Main,将退出应用,如图4-10所示。
图4-10 singleTop模式下“跳转到AC2”的Log信息
singleTop模式,可用来解决栈顶多个重复相同的Activity的问题。
如果是Fx_Main跳转到Activity2,再跳转到Fx_Main,行为就和standard一样了,会在Activity2跳转到Fx_Main时创建Fx_Main的新实例,因为当时的栈顶不是Activity2实例,如图4-11所示。
图4-11 singleTop模式下“跳转到AC2”后“跳回到Main”的Log信息
4.4.3 singleTask
singleTask模式和后面的singleInstance模式都是只创建一个实例的。
当Intent到来,需要创建singleTask模式Activity时,系统会检查栈里面是否已经有该Activity的实例。如果有直接将Intent发送给它(注意此时原在此Activity栈中上面的Activity将会被关闭)。
把Activity2的启动模式改成singleTask,修改后AndroidManifest.xml中代码如代码清单4-8所示。
代码清单4-8 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="feixun.com.jiang" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Fx_Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Activity2" android:label="@string/Ac2" android:launchMode="singleTask"/ > <activity android:name=".Activity3" android:label="@string/Ac3/> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
启动Fx_Main,跳转到Activity2---->Activity3---->Actvity2,此时看Log信息,如图4-12所示。
图4-12 singleTask启动模式下Log信息
可见从AC3再跳转到AC2时,因为AC2之前在栈中是存在的所以不生成新的AC2实例,而是在栈中找到此AC2,并将在AC2上面的AC3关闭,所以此时栈中只有Fx_Main和AC2,在AC2点返回会直接退到Fx_Main然后退出。
4.4.4 singleInstance
在singleInstance模式下,加载该Activity时如果没有实例化,它会在创建新的Task后,实例化入栈,如果已经存在,则直接调用onNewIntent,该Activity的Task中不允许启动其他的Activity,任何从该Activity启动的其他Activity都将被放到其他Task中,先检查是否有在应用的Task,没有的话就创建。
在这里介绍一下Task(任务)的概念。按照字面意思,任务就是自己要实现的一个目的,而在Android中的Task的定义是一系列Activity的集合,即要达到自己最终要到的Actvity,之前所有经历过的Actvity的集合。它可以是同一个应用内部的,也可以是两个不同应用的。Task可以认为是一个栈,可放入多个Activity。比如,启动一个应用,那么Android就创建了一个Task,然后启动这个应用的入口Activity,就是intent-filter中配置为main和launch的那个。这个Activity是根(Root)Activity,可能会在它的界面调用其他Activity,这些Activity如果按照上面那3个模式,也会在这个栈(Task)中,只是实例化的策略不同而已。
把Activity2的启动模式改成singleInstance,修改后AndroidManifest.xml中代码如代码清单4-9所示。
代码清单4-9 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="feixun.com.jiang" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Fx_Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Activity2" android:label="@string/Ac2" android:launchMode="singleInstance"/ > <activity android:name=".Activity3" android:label="@string/Ac3/> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
然后进行测试,启动Fx_Main---->Actvity2---->Actvity3然后看一下Log信息,如图4-13所示。
图4-13 singleInstance启动模式下Log信息
可以看到Fx_Main以及Activity3的Task ID为9,而Actvity2的Task ID为10,此时在Actvity3单击“返回”按钮会发现先退到Fx_Main,继续返回会回到Actvity2最后退出。从该过程可以看出:如果从其他应用程序调用singleInstance模式的Activity(Fx_Main),从该Activity开启其他Activity(Activity2)时,会创建一个新的Task(Task ID为10的那个),实际上,如果包含该Activity(Activity2)的Task已经运行的话,他会在该运行的Task中重新创建。
经过上述的介绍,用下面的表格来进行一个简单的总结,如表4-2所示。
表4-2 Activity4种启动模式对比
4.5 程序调试
Android系统提供了两种调试工具LogCat和DevTools,用于定位、分析及修复程序中出现的错误。
4.5.1 LogCat命令行工具
LogCat是可以显示在Eclipse集成开发环境中的用来获取系统日志信息的工具。它的主要功能就是能够捕获包括Dalvik虚拟机产生的信息、进程信息、ActivityManager信息、PackagerManager信息、Homeloader信息、WindowsManager信息、Android运行时信息和应用程序信息等可被捕获的信息。
1.LogCat的使用方法
打开方式:选择“Window”→“Show View ”→“Other”命令,打开Show View的选择菜单,然后在Andoird → LogCat中选择LogCat。打开LogCat后,它便显示在Eclipse的下方区域,其界面如图4-14所示。
图4-14 LogCat界面
从图中我们可以看到LogCat的右上方有5个不同的字母,这5个字母分别表示5种不同类型的日志信息,它们的级别依次增高,表示含义如下。
❑ V:详细(Verbose)信息。
❑ D:调试(Debug)信息。
❑ I:通告(Info)信息。
❑ W:警告(Warn)信息。
❑ E:错误(Error)信息。
在LogCat中,用户可以通过5个字母图标选择显示的信息类型,级别高于所选类型的信息也会在LogCat中显示,但级别低于所选类型的信息则不会被显示。
同时,LogCat提供了“过滤”功能,在右上角的“+”号和“-”号,分别是添加和删除过滤器。用户可以根据日志信息的标签(Tag)、产生日志的进程编号(Pid)或信息等级(Level),对显示的日志内容进行过滤。
2.程序调试原理
❑ 引入android.util.Log包。
❑ 使用Log.v()、 Log.d()、 Log.i() 、Log.w() 和Log.e() 5个方法在程序中设置“日志点”。
➢ Log.v()用来记录详细信息。
➢ Log.d()用来记录调试信息。
➢ Log.i()用来记录通告信息。
➢ Log.w()用来记录警告信息。
➢ Log.e()用来记录错误信息。
❑ 当程序运行到“日志点”时,应用程序的日志信息便被发送到LogCat中。
❑ 判断“日志点”信息与预期的内容是否一致。
❑ 进而判断程序是否存在错误。
下面的例子演示了Log类的具体使用方法。
代码清单4-10 LogCat.java
package com.example.LogCat; import android.app.Activity; import android.os.Bundle; import android.util.Log; public class LogCat extends Activity { final static String TAG = "LOGCAT"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.v(TAG,"Verbose"); Log.d(TAG,"Debug"); Log.i(TAG,"Info"); Log.w(TAG,"Warn"); Log.e(TAG,"Error"); } }
在本段代码中,程序第5行“import android.util.Log;”引入android.util.Log包;第8行定义标签,标签帮助用户在LogCat中找到目标程序生成的日志信息,同时也能够利用标签对日志进行过滤;第14行记录一个详细信息,Log.v()方法的第一个参数是日志的标签,第二个参数是实际的信息内容;第15~18行分别产生了调试信息、通告信息、警告信息和错误信息。
最终运行结果如图4-15所示,从图中还可以看出LogCat对不同类型的信息使用了不同的颜色加以区别。
图4-15 LogCat工程的运行结果
3.添加过滤器
上文中提到LogCat提供了“过滤”功能,下面就来介绍一下LogCat是如何添加过滤器的。
首先,单击右上角的“+”,在弹出的对话框中填入过滤器的名称:LogcatFilter,设置过滤条件为“标签=LOGCAT”即可,操作方法如图4-16所示。
图4-16 添加过滤器
经过上述过滤器过滤后,无论什么类型的日志信息,属于哪一个进程,只要标签为LogCat,都将显示在LogcatFilter区域内。LogCat过滤后的输入结果如图4-17所示。
图4-17 LogCat过滤后的输入结果
4.5.2 DevTools开发调试工具
DevTools是用于调试和测试的工具,它包括了如下所示一系列各种用途的用户小工具:Development Settings、Exception Browser、Google Login Service、Instrumentation、Media Scanner、Package Browser、Pointer Location、Raw Image Viewer、Running processes和Terminal Emulator。
如图4-18 所示,为DevTools使用时的界面。由使用时的界面也可以看出其中的各个小工具。
图4-18 DevTools的使用界面
以下着重讲解Dev Tools的一些小工具。
1.Development Settings
Development Settings中包含了程序调试的相关选项,单击功能前面的选择框,出现绿色的“对号”表示功能启用,模拟器会自动保存设置。
图4-19显示了Development Settings的运行界面。
图4-19 Development Settings运行界面
下面就详细介绍Development Settings中各个选项的含义,如表4-3所示。
表4-3 Development Settings中各选项的含义
2.Pointer Location
Pointer Location是屏幕点位置查看工具,能够显示触摸点的X轴坐标和Y轴坐标,如图4-20所示。
图4-20 Pointer Location的使用画面
3.Running processes
Running processes能够查看在Android系统中正在运行的进程,并能查看进程的详细信息,包括进程名称和进程所调用的程序包。
图4-21 Andoird模拟器默认情况下运行的进程和com.android.phone进程的详细信息
4.Terminal Emulator
Terminal Emulator可以打开一个连接底层Linux系统的虚拟终端,但具有的权限较低,且不支持提升权限的su命令。如果需要使用root权限的命令,可以使用ADB工具。
图4-22是Terminal Emulator运行时的画面,输入ls命令,显示出根目录下的所有文件夹。
图4-22 Terminal Emulator运行时的画面
4.6 本章小结
本章主要介绍了Android系统的进程优先级排序、不同优先级进程之间的变化方式,Android系统的4大基本组件及其用途,Activity的生命周期中各个状态及状态间的变化关系、Android应用程序的调试方法和工具。
关键知识点测评
1.以下有关Android系统进程优先级的说法,不正确的一个是( )。
A.前台进程是Android系统中最重要的进程
B.空进程在系统资源紧张时会被首先清除
C.服务进程没有用户界面并且在后台长期运行
D.Android系统中一般存在数量较多的可见进程
2.以下有关Android组件的叙述,正确的一个是( )。
A.Service是Android程序的呈现层
B.BroadcaseReceiver本身包含界面,用于通知用户接收到重要信息
C.应用程序可以通过ContentProvider访问其他应用程序的私有数据
D.不是所有的Android组件都具有自己的生命周期
3.以下有关Activity生命周期的描述,不正确的是( )。
A.Activity的状态之间是可以相互转换的
B.Activity的全生命周期是从Activity建立到销毁的全部过程,始于onCreate(),结束于onDestroy()
C.活动生命周期是Activity在屏幕的最上层,并能够与用户交互的阶段
D.onPause()函数在Android系统因资源不足终止Activity前调用