Evernote Export
     活动,四大组件之一,可以提供一个屏幕(screen蛋疼的翻译),用户通过这个屏幕来交互,且可以实现一些功能,比如打电话,拍照,发邮件,看地图。每个活动都会得到一个窗口(window)在这个窗口里去画活动的UI。一般来讲,窗口会填满整个屏幕,有时候也会小于屏幕,浮在其他窗口的上面。

     应用通常会包含多个活动,活动们之间会有一些不太紧密的联系。一般来说,有一个活动会被指定为“主(main)”活动,即用户第一次打开你的应用时显示的活动。每个活动里都可以用代码启动其他活动,每当启动一个新活动时,上一个活动都会被stopped,但是系统仍会把它保留在返回栈(“Back Stack”)中。新启动的那个活动会被压入栈顶,并且获取用户焦点。返回栈也是栈,遵循后进先出规则。所以若用户此时按下返回键,新活动就会出栈,(然后被销毁),然后它前一个活动就回到了栈顶(resume)。

     新活动启动时,上一个活动如果被stopped的话,会通过callback调到它(上一个活动)的生命周期回调方法。回调方法有不少,系统会在状态变化时调用他们,你可以在这些方法里面做一些爱做的事情。如被stopped的时候,可以释放一些大的对象,网络连接啊数据库连接啊什么的。当resume的时候,你就可以重新获取那些需要的资源。这就活动的生命周期里的部分变化。

     创建活动,需定义一个继承Activity的子类(或者某Activity子类的子类)。在子类中,你需要实现那些回调方法,即活动生命周期状态改变时系统会调用的方法。有俩最重要的方法:
~     onCreate()
          这个必须实现。系统在创建活动时调用这个玩意。在方法内部,你可以做一些初始化工作。一般来说最重要的是要调用一下setContentView()方法去指定一下你的活动的UI。
~     onPause()
          在用户将要离开你这个活动时,这个方法往往是作为第一个预示,而被系统调用,当然用户离开这个活动也不见得这个活动就要被销毁。在这个方法内部,你往往需要把你做出的改变提交给系统,比如删除了个什么玩意,增加了个什么什么的。这些改变是你希望永久生效的,所以在pause的时候就提交掉,万一用户离开了就不回来了呢~
     剩下还有几个回调,等下细说。

实现一个UI
     活动的UI是由一个视图(views)等级结构提供的,视图,即继承View类的对象。每一个视图都控制着一个窗口(window,就是每一个活动都会拿到的那个窗口)内部的特殊的矩形区域,在这个区域内这些视图对象可以和用户互动。如,一个Button,用户点击它的时候它会做出些相应的动作。

     安卓提供了很多现成的view,你可以用这些view组织设计你的布局。“widgets控件”是为屏幕(screen。。翻译过来实在不好听)提供可见可交互元素的view,像什么按钮啊,文本框,或者复选框还有图片什么的(都是widget)。“layouts布局”是继承于ViewGroup的、为子视图提供独特的布局模型的view。比如LinearLayout,GridLayout,RelativeLayout。当然, 你也可以手动继承View和ViewGroup类或他们的子类来创建你自己的控件和布局,并且把自创的东西添加到你的活动布局里。
     
     常见的定义布局的方法就是在资源目录下通过xml文件定义布局。通过这种方式,你可以将逻辑和视图分离。通过setContentView()方法可以把定义的布局设定为你的活动的UI,使用该方法需要传入布局文件的资源ID。另外,你同样可以在活动的代码中创建新的视图对象,并且通过把视图对象加到ViewGroup中来构造出视图等级结构。然后把等级结构中根节点ViewGroup传到setContentView()方法作为参数,就可以使用这个新布局了(布局都是继承ViewGroup的)。

Manifest中声明活动:
<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >

     声明活动的方法前面说过了,那么除了活动的名字(name)之外,activity元素中还可以搞点别的属性,比如指定活动的标签,图标什么的,还可以给活动指定一个主题。后面这些都是可选的,即必须的属性只有"android:name"这货。一旦应用发布,这个名字就别再改了,否则会出一些问题。。

Using intent filters
     activity元素也可以包含各种各样的intent filters,通过<intent-filter>标签实现。这个标签声明了其他应用怎么样来启用你的某活动。

     SDK默认创建的活动是这样的:

     <activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
     </activity>
     这里的<action>元素规定了这个活动就是应用的“主”入口。<category>元素规定了这个活动必须在用户的launcher中展示出来,让用户可以直接启动该活动。

     如果你想让你的应用独立的存在与系统且不允许别的应用启动你的活动,那这一个intent filter就够了,你不再需要别的filter了。应用中只允许一个活动拥有main action和launcher category(是吗?文档是这么说的)。而那些你不想让别人启动的活动直接就不需要intent filter了,你可以自己通过显式的intent启动它们。
     
     如果你还是想让别人来启动你的活动,那就需要intent filter。对每一个你想要响应的intent,你都需要一个对应的<intent-filter>标签并且里面带有<action>(必选)和<category>(可选)和<data>(可选)。这个元素指明你的活动可以响应的intent类型。

开启一个活动:
     你可以用startActivity()来启动一个活动,传入一个描述你想要启动什么活动的intent。这个intent可以直接指明你想要启动的具体的一个活动,而可以描述一下你想要启动的活动的类型(然后系统会帮你找到合适的活动,可以是你自己应用的,也可以是别的应用的)。intent也可以携带一些信息传给即将被启动的新活动。     
     显示启动,对应上一段的第一种方法:
     Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);
     一般用来启动你自己应用内部的活动。
     
     但是,偶尔你也会想要在应用里发个邮件、短信或者更新个状态什么的,这种情况下,很可能你自己并没有这样一个活动。。那这个时候就需要求助于手机上的其他应用了,这就体现出了intent真正的价值,你可以创建一个intent并描述你的意图然后系统会自动帮你匹配合适的活动,如果不止一个活动都能做你需要做的事情,需要你选择一个。比如你想发邮件:
     Intent intent = new Intent(Intent.ACTION_SEND);
     intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
     startActivity(intent);
     EXTRA_EMAIL 给intent额外加了一个string数组,指明邮件发送的目标地址。当一个email应用响应这个intent的时候,把你额外的加的string数组读出来,写到邮件的TO:那一栏。在这个例子中,发邮件的活动启动之后,当用户发完邮件,原来的活动会恢复回来。

启动一个会返回结果的活动(...很难翻):
     有些时候,你启动了一个活动并期望从这个活动获取一些结果。这时就要通过startActivityForResult()方法来启动活动。然后可重写onActivityResult()方法来获取结果。当你启动的活动结束之后,会返回一个存储在intent里的结果,intent作为返回参数传入onActivityResult()方法里。

     比如,你想让用户选择一个联系人,然后你可以对该联系人的信息做一些操作。代码如下:
     
     private void pickContact() {
    // Create an intent to "pick" a contact, as defined by the content provider URI  创建一个intent去让用户选择联系人,传入的参数由内容提供者定义
Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
startActivityForResult
(intent, PICK_CONTACT_REQUEST);
} 
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// If the request went well (OK) and the request was PICK_CONTACT_REQUEST
if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
// Perform a query to the contact's content provider for the contact's name(传回的结果是联系人的uri)
Cursor cursor = getContentResolver().query(data.getData(),
new String[] {Contacts.DISPLAY_NAME}, null, null, null);
if (cursor.moveToFirst()) { // True if the cursor is not empty
int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
String name = cursor.getString(columnIndex);
// Do something with the selected contact's name...
}
}
}    

     这个例子展示了一个你处理活动返回的结果的基本逻辑。第一个if条件,检查你的查询是否成功,如果成功,那resultCode就会是RESULT_OK,后面的条件检查返回的这个结果是谁申请的,即requestCode需要匹配某个startActivityForResult()里的requestCode。然后剩下的代码就用返回的数据去查询对应的联系人。

关闭一个活动:
     通过在活动内调用finish()方法可以关闭一个活动自身。同时你也可以通过finishActivity()方法来关闭另外一个你之前启动的活动。
     注意:大多数情况下,你尽量不要用这些方法显式的关掉一个活动,android系统会帮你管理活动的生命周期,所以你没必要自己去shutdown你的活动。当且仅当你真的不想让用户再回到这个活动的时候你再手动关掉它,不然的话会导致一些不太好的结果。


管理活动的生命周期:
     开发一个强壮而灵活的应用,相当关键的一点就是通过实现各种回调函数来管理活动的生命周期。而活动的生命周期相当程度上受到它和其他活动的联系的影响,包括它所属的任务及返回栈。
     活动的状态一般来说有三种:
     ~Resumed 
          这个状态即活动处在屏幕的前台并且获得用户的焦点。(有时也把它称作"运行"状态)
     ~Paused
          活动若处于这种状态下,说明另一个活动正在前台并拥有用户焦点,但活动依然是可见的。就是说,那个处于前台的活动并没有占满屏幕或者是虽然占满了但是它有部分是透明的,总之就是没有完全挡住后面的活动,这时后面的活动就处于Paused的状态。
     ~Stopped
          处于此状态的活动完全被另外一个挡住了,不可见了。也可以说它处于后台(background)。处于Stopped状态的活动依然是存活着的(也就是这个Activity对象还在内存里,依然保留着它自己的一些状态信息,但是已经与window manager那里脱离关系了)。但是用户看不到它,并且当系统急需内存的时候很可能优先把它杀掉。
     当一个活动处于paused或stopped状态时,系统可以通过一些方式把它从内存中移除,比如调用它自己的finish()方法,或者直接杀死它所在的进程。当这个活动再次被启动的时候,需要整个重新创建之。

实现生命周期回调方法:
     当一个活动在上述的不同状态来回穿梭的时候,系统会调用不同的回调方法。所有的回调方法你都可以重写,去实现你要的功能。基本的几个回调方法如下:
     public class ExampleActivity extends Activity {
    @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The activity is being created.
}
@Override
protected void onStart() {
super.onStart();
// The activity is about to become visible.
}
@Override
protected void onResume() {
super.onResume();
// The activity has become visible (it is now "resumed").
}
@Override
protected void onPause() {
super.onPause();
// Another activity is taking focus (this activity is about to be "paused").
}
@Override
protected void onStop() {
super.onStop();
// The activity is no longer visible (it is now "stopped")
}
@Override
protected void onDestroy() {
super.onDestroy();
// The activity is about to be destroyed.
}
}
     注意:你自己去实现生命周期回调方法的时候,做任何操作之前要先调一下父类的方法,就像例子里面一样。
     上面这一堆方法就构成了活动的完整生命周期。通过实现这些方法,你可以监测到生命周期中的三个嵌套循环周期:
     完整生命周期:活动的完整生命周期从onCreate开始到onDestroy结束。onCreate方法中你应该做一些全局状态的初始化(加载布局啊什么的),而onDestroy方法中应该做释放资源的操作。比如你的活动里需要有一个后台线程去下载东西,你就可以在onCreate里创建这个线程在onDestroy里停止线程。
     可见生命周期:活动的可见生命周期从onStart到onStop。在此期间,用户可以看到活动并且与之交互(可以交互?)。比如,一个新活动启动了并且完全挡住了后面的活动,用户看不到后面的活动了,然后就会调到后面活动的onStop()方法。在可见周期期间,你可以把需要给用户看到的资源都保留着。比如在onStart方法里你可以注册一个广播接收者监测一下系统的一些变化,从而对应修改你的UI,然后在onStop方法里,由于用户已经看不到你展示的东西了,就把广播接收者取消注册。在完整的生命周期中,系统可能会多次调用onStart() onStop()方法,因为活动有可能时而可见时而不可见。
     前台生命周期:活动的可见生命周期从onResume到onPause。在这个期间,活动处在屏幕的最上层,并且持有用户的输入焦点(就是可以在上面打字)。一个活动可能会频繁的在前台和非前台状态间切换。比如,当设备进入睡眠状态或者某个提示框弹出来的时候,onPause方法就会被调到。由于onResume和onPause这两个方法会经常被调到,所以这两个方法里的代码最好尽量少些,尽量做些轻量级的工作,以免用户在频繁切换的时候需要等待。



     图1.活动的生命周期
     下表列出的方法和图1是一样的,不过有了更加详细的描述且显示出了他们在生命周期中的对应位置,并且指明了某方法执行后系统能不能杀掉活动。 
     
方法

描述执行之后能否被系统杀掉下一个回调方法
onCreate()

活动第一次创建时调用。这个方法里你应该做一些正常的静态的初始化。如创建视图,给列表填充数据等等。这个方法会给你传入一个Bundle对象作为参数,该参数包含了活动的之前的一些被保留的状态。
下一个方法只能是onStart()。
不能onStart()

onRestart()
当活动被stop掉(即不可见了)之后,如果要重新回到可见状态,在onStart()之前就会先调这个方法。
下一个方法只能是onStart()。注意它和onCreate()其实代表了两条路径,下一步都是onStart(),并不冲突。
不能onStart()

onStart()
在活动即将、马上就要让用户看到的时候调用这个方法。调用完成之后,如果活动来到前台那么马上再调onResume();如果活动没有来到前台而是被隐藏了,那么就调onStop()不能onResume()或者onStop()


onResume()在活动即将变为前台(可交互)状态时调用。处在这个状态的活动位于活动栈的栈顶,并且拥有着用户的输入焦点。下一个方法只能是onPause()不能onPause()


onPause()当系统要让另一个活动进入前台时,本活动的onPause ()就会被调用。对持久型数据(可以粗略理解为本地数据)做出的改动一般都在这个方法里提交。方法中还可以将一些消耗CPU资源的动画啊其他什么的停掉。当然,此方法中的工作要尽可能的快点执行完,因为执行完毕以后那个新启动的前台活动才能给用户显示出来。
它的下一个方法,如果本活动回到了前台,那就调用onResume(),如果活动没有回到前台而是彻底看不到了,就调用onStop()(注意:调用onPause方法的活动虽然不再处于前台,但是依然可见)。
onResume()或onStop()

onStop()
当活动不再可见时调用此方法。而不可见的原因,或许是因为马上这个活动就要被销毁了,或许是因为另外一个活动来到前台以后完全挡住了本活动。
下一个方法:如果用某个时刻用户又要回到这个活动并且与其进行交互,那在回到这个活动之前就回调onRestart()方法,而如果这个活动是因为马上要被销毁而不可见的话,接着就调用onDestroy() 了
onRestart()或onDestroy()
onDestroy()

在活动被销毁之前调用。这个调用是活动的一个完整生命周期里的最后一个调用(调完之后这个活动就来生再见了,以后即便被创建也是新的生命周期)。调用原因可能是代码当中调到了finish()方法,也可能是系统为了节省内存把它杀掉。你可以用isFinishing()方法区分这两种原因。(废话)没了
表中前三列分了3个层次,对应着上面提到的三种嵌套的生命周期。

     表中提到的“执行完之后能否被系统杀掉”是说:在执行完这个方法之后,系统能不能不执行这个活动的任意一行代码就直接杀掉活动所在的进程。有三个方法标识了“能”(onPause()、onStop()、onDestroy()),onPause()居三者之首,所以当一个活动被创建起来后,onPasue()方法就是系统在杀掉进程之前唯一一个保证会调到的方法,因为某些情况下系统极度需要内存,可能就直接onPasuse()之后就杀掉进程了,onStop()和onDestroy()都不会调到,所以你应该在onPause()里面做一些重要数据的保存(用户数据啊,配置啊,有时候要保留一些用户的输入啊什么的)。另外,对于一些系统资源在onPause()之后是否保留,这个你自己来决定(意思就是你都Pause了就不要再占着太多资源了),因为占太多了话会影响到新启动的前台活动,比如你onPause()里有个阻塞方法,然后一阻塞就会使得新活动也启不起来了。。影响用户体验。

     表中剩下几个方法在这一项都标识着“不能”。意思是从调用我这个方法开始,(一直到标着“能”的那三个方法为止)系统都不能直接杀掉活动所在的进程。所以,一个活动的“可以被杀周期”是从onPause()方法返回的时候开始,到下次onResume()的调用结束,然后从这里开始一直到下次onPause()方法返回,这段时间就是“不可被杀周期”。
     注意:所谓“不可被杀”的活动,在某些情况下依然是会被系统杀掉的,当然这些情况都是极端的不能再极端了。

保存活动的状态:
     上面关于管理生命周期的那段提了一下当活动被pause或者stop的时候,活动的状态会被保留下来。因为在这些情况下活动对象(活动都是继承Activity类的对象)依然保留在内存中,所有关于对象的成员的信息或者当前的状态都还保留着。所以用户对活动做出的一些改动都被保留了下来,当活动再次恢复到前台(即Resume)的时候,这些改动依然还在。
     不过,若系统为了清理内存而把活动销毁掉了,这时就不能简单的恢复活动并且保留着那些改动了——当用户需要回到之前的活动时,系统会重新创建一个活动对象。但是用户可不知道系统已经把之前的活动销毁了,所以当系统创建一个新对象的时候,用户依然期待着能看到自己之前做出的改动。这时候,你需要用到onSaveInstanceState()这个回调方法,它允许你保存一些活动的状态信息(便于日后恢复)。
     在使活动变为“可被杀”状态之前,系统自动调用onSaveInstanceState()方法,并传入一个Bundle对象,你可以用它来以键值对的形式保存一些信息(通过putString()、putInt()等方法)。之后如果系统杀掉了应用进程(应用在进程里运行,活动属于应用的一部分,ok?)然后用户又要回到你的活动里,系统会重新创造一个活动对象,并把那个持有信息的Bundle作为参数,传到onCreate()和onRestoreInstanceState()方法里。通过两方法中任一个你都可以取出你之前保存的信息。
不过,如果这个Bundle里面没有存任何信息,那么给两个方法中传入的参数都是null(而不是空的Bundle,直接就是null),这种情况往往发生在第一次创建活动的时候。


注意:系统从来不保证会在销毁活动前一定调用onSaveInstanceState()方法,因为某些情况下没必要存什么信息(比如用户显式的关掉了你的活动(通过返回键))。如果真的调了的话,那一定会在onStop()之前调用,可能在onPasue()之前(不一定)。

     然而,就算你没有实现onSaveInstanceState()方法,或者实现了一个空的方法,Activity类里对onSaveInstanceState()方法的默认实现依然会保存一些活动状态信息。具体来说,这个onSaveInstanceState()的默认实现里调用了View类对应的onSaveInstanceState()方法,可以让每一个view对象提供一些想要被保存的信息。android framework中几乎每个控件(widget)都适当地实现了onSaveInstanceState()方法,这样的话你对UI做出的一些可见的改动在下次活动被创建的时候依然可以保留着。比如,EditText控件就保留着用户的输入,CheckBox会记住选择框被选中了没有,对你来说你需要做的唯一事情就是对于你想要它保存信息的控件,给它提供一个唯一的ID(通过android:id属性),如果一个控件没有ID,那就没法存状态信息了。当然,如果你就是不想让控件存信息,有ID也不存,你需要用到android:saveEnabled的属性,设为false就好了。或者在代码中动态设置调用setSavedEnabled()方法效果也一样。一般来说不太建议你强制不让控件存状态信息,不过。。你高兴就好(或者你有特殊的恢复控件状态的技巧)。
     虽然默认的onSaveInstanceState()实现能够帮你存一些关于你的活动UI的信息,但是如果这些信息不够,你需要存更多的信息的话,还是得重写这个方法自己来实现想要的功能。比如你想保存一些成员变量的值,这些值会随着生命周期而变化,或许正好是和UI中存储的一些值有着某种对应关系,可惜这些成员系统是默认不给你恢复的,所以你需要重写onSavedInstanceState()自己去保存。
     在重写onSaveInstanceState()的时候,确保在一开始就调用父类的onSaveInstanceState(),和生命周期那里很像。同样的,重写onRestoreInstanceState()方法时也要先调super.onRestoreInstanceState(),以便于默认的实现能够起作用。
     注意:之前提到过,onSaveInstanceState方法是不保证一定会被调用的,所以在这个方法里,你就存一些临时的数据就好了(就算丢了也不可惜的数据,UI的状态什么的),而不要存持久化的数据(丢了会出事的数据,比如要存到数据库的数据),持久化的数据用前面提到的onPause方法来存。
     可以通过一个简单的方式来测试一下你的应用那方面的能力行不行(保存&恢复数据方面的能力...),就是把手机屏幕横过来试试。在屏幕的方向改变的时候,系统会销毁当前的活动并重新创建一个活动,因为横屏竖屏之间很多的配置资源什么的都不一样,系统需要通过销毁&创建的过程改变这些配置。所以,若单独考虑这个情况的话,你的活动应该把所有的状态都保留下来,因为用户在使用中可能会经常旋转屏幕,可别让用户发现转了一下屏幕之后之前写的好多东西都白写了。。

处理设备配置的变化:
     某些设备的配置会在运行过程中改变(比如上面说的屏幕方向,还有键盘的弹出与弹回,还有系统的语言)。当这些状况发生的时候,android系统会重新创建一次正在运行的活动(就是立马调一次onDestroy然后又调onCreate)。此举的设计目的是为了帮助你的应用在不同配置之间调整,通过你针对不同配置提供的对应资源(比如不同屏幕方向下的布局,不同屏幕大小的布局,你都可以设置,然后系统会对应自身的情况使用你的这些设置)。
     如果你能很完善的设计你的活动使它能应对像屏幕方向改动的这种事件,那你的应用在面对生命周期中的一些突发事件时会更有弹性。
     像屏幕方向改变的这种情况最好的处理方式是用onSavedInstanceState和onRestoreInstanceState(或者onCreate)。

协调的活动们(Coordinating activities):
     当某活动内启动了一个新的活动,两者都会经历生命周期的变化:当第二个活动被创建的时候,第一个活动先Pause再Stop(当然,如果它没有被完全挡住,仍然可见的话就不会被Stop)。如果这两个活动之间会同时往磁盘或者其他地方存数据的话,这时就有必要充分理解两活动发生的变化:在第二个活动完成创建过程之前,第一个活动是不会Stop的(针对完全挡住的情况,没挡住就不说了),创建第二个活动的过程和stop第一个活动的过程是有重叠的。
     处于同一进程的两活动,当一个活动启动另一个的时候,生命周期回调方法的顺序是有着明确的规定的,A活动启动B活动时回调顺序如下:
     1.A活动的onPause()被调用
     2.B活动的onCreate(),onStart(),onResume()方法依次被调用。然后B活动就拥有了用户的焦点。
     3.然后,如果A活动被完全挡住的话,就调用其onStop()方法。
     这种有律可循的回调顺序让你能处理活动与活动之间信息的传递。比如,你想在第一个活动停止前将某数据写入数据库以便第二个活动能读到它,那这时你肯定要在第一个活动的onPause()方法里去写数据库,而不是onStop()。
     


     



     





 posted on 2015-07-29 16:03  絮状颗粒  阅读(238)  评论(0)    收藏  举报